Remove runtime-compiled Cocoa IME regression tests

This commit is contained in:
白鸽 2026-09-14 20:37:02 +08:00
parent 4ef2df194a
commit 7cbea2f539
3 changed files with 0 additions and 260 deletions

View file

@ -138,24 +138,6 @@ class TestBuild(BaseTest):
self.assertEqual(cp.returncode, 0, cp.stdout)
self.assertIn('dictation forwarding probe passed', cp.stdout)
def test_macos_ime_commit(self) -> None:
from kitty.constants import glfw_path, is_macos
if not is_macos or not shutil.which('clang'):
self.skipTest('Cocoa IME regression test is macOS only and requires clang')
cocoa_module = glfw_path('cocoa')
src = os.path.join(os.path.dirname(__file__), 'cocoa_ime_commit.m')
symbols = subprocess.check_output(['nm', '-g', cocoa_module], text=True)
asan_flags = ['-fsanitize=address,undefined', '-fno-omit-frame-pointer'] if '__asan_init' in symbols else []
with tempfile.TemporaryDirectory() as tdir:
exe = os.path.join(tdir, 'cocoa_ime_commit')
cmd = ['clang', '-D_GLFW_COCOA', '-fno-objc-arc', '-framework', 'Cocoa', '-framework', 'Carbon']
cp = subprocess.run(cmd + asan_flags + [src, '-o', exe], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, timeout=60)
self.assertEqual(cp.returncode, 0, cp.stdout)
cp = subprocess.run([exe, cocoa_module], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, timeout=30)
self.assertEqual(cp.returncode, 0, cp.stdout)
self.assertTrue(json.loads(cp.stdout)['all_passed'], cp.stdout)
def test_glfw_modules(self) -> None:
from kitty.constants import glfw_path, is_macos

View file

@ -1,202 +0,0 @@
// License: GPL v3
// Headless regression test for the actual Cocoa module's IME receiver.
// Build/run instructions and fixture limits: cocoa_ime_commit.md.
#import <Cocoa/Cocoa.h>
#import <objc/runtime.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#undef MAX
#undef MIN
#include "../glfw/internal.h"
static id<NSTextInputClient> view;
static Ivar handlerIvar;
static NSMutableData *delivered;
static NSMutableArray *results;
static NSUInteger commitCount, clearCount, previewBytes;
static BOOL allPassed = YES;
static void
capture(GLFWwindow *window, GLFWkeyevent *event) {
(void)window;
if (event->ime_state == GLFW_IME_COMMIT_TEXT) {
++commitCount;
if (event->text) [delivered appendBytes:event->text length:strlen(event->text)];
} else if (event->ime_state == GLFW_IME_PREEDIT_CHANGED) {
if (!event->text || !event->text[0]) ++clearCount;
else previewBytes = strlen(event->text);
}
}
static void
set_handler(int mode) {
memcpy((char *)view + ivar_getOffset(handlerIvar), &mode, sizeof(mode));
}
static NSString *
repeat(NSString *unit, NSUInteger n) {
NSMutableString *s = [NSMutableString string];
for (NSUInteger i = 0; i < n; ++i) [s appendString:unit];
return s;
}
static void
reset_observed(void) {
[delivered setLength:0];
commitCount = clearCount = 0;
}
static void
prepare(NSString *marked) {
set_handler(0);
// Reset both the native text buffer and preedit through the real method.
[view setMarkedText:@"reset" selectedRange:NSMakeRange(5, 0) replacementRange:NSMakeRange(NSNotFound, 0)];
[view setMarkedText:marked selectedRange:NSMakeRange(marked.length, 0) replacementRange:NSMakeRange(NSNotFound, 0)];
reset_observed();
}
static void
insert(id text) {
[view insertText:text replacementRange:NSMakeRange(NSNotFound, 0)];
}
static void
check(NSString *name, NSString *expected, NSUInteger commits, NSUInteger clears, BOOL marked) {
NSData *bytes = [expected dataUsingEncoding:NSUTF8StringEncoding];
BOOL same = [delivered isEqualToData:bytes];
BOOL passed = same && commitCount == commits && clearCount == clears && [view hasMarkedText] == marked;
allPassed &= passed;
[results addObject:@{
@"case" : name,
@"expected_utf8_bytes" : @(bytes.length),
@"committed_utf8_bytes" : @(delivered.length),
@"commit_events" : @(commitCount),
@"preedit_clear_events" : @(clearCount),
@"has_marked_text_after" : @([view hasMarkedText]),
@"content_matches" : @(same),
@"passed" : @(passed)
}];
}
int
main(int argc, const char **argv) {
@autoreleasepool {
if (argc != 2) {
fprintf(stderr, "usage: cocoa_ime_commit /path/to/glfw-cocoa.so\n");
return 2;
}
void *module = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL);
if (!module) {
fprintf(stderr, "dlopen: %s\n", dlerror());
return 2;
}
Class klass = objc_getClass("GLFWContentView");
Ivar windowIvar = class_getInstanceVariable(klass, "window");
Ivar markedIvar = class_getInstanceVariable(klass, "markedText");
handlerIvar = class_getInstanceVariable(klass, "in_key_handler");
if (!klass || !windowIvar || !markedIvar || !handlerIvar) {
fprintf(stderr, "native receiver layout missing\n");
return 2;
}
view = class_createInstance(klass, 0);
_GLFWwindow *fixtureWindow = calloc(1, sizeof(*fixtureWindow));
if (!view || !fixtureWindow) return 2;
fixtureWindow->callbacks.keyboard = capture;
memcpy((char *)view + ivar_getOffset(windowIvar), &fixtureWindow, sizeof(fixtureWindow));
object_setIvar(view, markedIvar, [[NSMutableAttributedString alloc] init]);
delivered = [[NSMutableData alloc] init];
results = [[NSMutableArray alloc] init];
NSArray *cases = @[
@[ @"ascii-510", repeat(@"a", 510) ],
@[ @"ascii-511", repeat(@"a", 511) ],
@[ @"ascii-512", repeat(@"a", 512) ],
@[ @"ascii-513", repeat(@"a", 513) ],
@[ @"han-170-bytes-510", repeat(@"文", 170) ],
@[ @"han-171-bytes-513", repeat(@"文", 171) ],
@[ @"mixed-bytes-511", [repeat(@"文", 170) stringByAppendingString:@"a"] ],
@[ @"mixed-bytes-512", [repeat(@"文", 170) stringByAppendingString:@"ab"] ],
@[ @"han-383-bytes-1149", repeat(@"文", 383) ],
@[ @"emoji-bytes-512", repeat(@"😀", 128) ],
@[ @"han-bytes-90000", repeat(@"文", 30000) ],
@[ @"short-after-long", @"done" ]
];
for (NSArray *c in cases) {
NSString *input = c[1];
prepare(input);
BOOL previewMatches = previewBytes == [input lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
insert(input);
check(c[0], input, 1, 1, NO);
if (!previewMatches) {
allPassed = NO;
fprintf(stderr, "Full preedit was not delivered for %s\n", [c[0] UTF8String]);
}
}
prepare(@"");
NSString *unmarked = repeat(@"x", 2048);
insert(unmarked);
check(@"unmarked-long", unmarked, 1, 0, NO);
prepare(@"候选");
NSString *attributedText = repeat(@"文😀e\u0301\n", 128);
insert([[[NSAttributedString alloc] initWithString:attributedText] autorelease]);
check(@"attributed-long", attributedText, 1, 1, NO);
prepare(@"");
insert(@"甲");
insert(@"乙");
check(@"separate-event-loop-commits", @"甲乙", 2, 0, NO);
prepare(@"待确认");
insert(nil);
check(@"nil-keeps-preedit", @"", 0, 0, YES);
insert(@"");
check(@"empty-keeps-preedit", @"", 0, 0, YES);
insert(@"\n");
check(@"control-keeps-preedit", @"", 0, 0, YES);
insert(@"\x7f");
check(@"delete-keeps-preedit", @"", 0, 0, YES);
// Simulate insertText callbacks while keyDown is collecting text. They
// must not bypass the surrounding key's dispatch by emitting commits.
prepare(@"");
set_handler(1);
insert(@"甲");
insert(@"乙");
check(@"key-handler-defers-two-callbacks", @"", 0, 0, NO);
// Use the existing modifier branch to observe the pending accumulator;
// this is a fixture probe, not a simulated physical modifier gesture.
set_handler(2);
insert(@"丙");
check(@"existing-buffer-still-accumulates", @"甲乙丙", 1, 0, NO);
prepare(@"候选");
set_handler(1);
insert(@"已确认");
check(@"key-handler-unmarks-without-early-commit", @"", 0, 0, NO);
// The narrowly scoped fix intentionally leaves modifier buffering alone.
prepare(@"");
set_handler(2);
insert(repeat(@"m", 511));
check(@"modifier-short-behavior", repeat(@"m", 511), 1, 0, NO);
prepare(@"");
set_handler(2);
insert(repeat(@"m", 512));
check(@"modifier-overflow-unchanged", @"", 0, 0, NO);
NSDictionary *report = @{
@"native_module" : @(argv[1]),
@"all_passed" : @(allPassed),
@"cases" : results,
@"capture_boundary" : @"native GLFW keyboard callback; no input method, GUI or terminal child",
@"key_handler_tests" : @"receiver callback state fixtures; not full physical key dispatch"
};
NSData *json = [NSJSONSerialization dataWithJSONObject:report options:NSJSONWritingPrettyPrinted error:nil];
fwrite(json.bytes, 1, json.length, stdout);
fputc('\n', stdout);
return allPassed ? 0 : 1;
}
}

View file

@ -1,40 +0,0 @@
# Cocoa IME commit regression
This standalone macOS test calls the actual `GLFWContentView` methods from a
specified `glfw-cocoa.so`. It does not start an input method or open a window.
After building from source, run it through the normal test entry point:
```sh
./test.py macos_ime_commit
```
The runner skips this test outside macOS or if `clang` is unavailable. It also
links the probe with the sanitizer runtime when the Cocoa module uses ASan.
For a standalone run against an unsanitized module from a matching checkout:
```sh
mkdir -p build/ime-test
clang -D_GLFW_COCOA -fno-objc-arc -framework Cocoa -framework Carbon \
kitty_tests/cocoa_ime_commit.m -o build/ime-test/cocoa_ime_commit
build/ime-test/cocoa_ime_commit "$PWD/kitty/glfw-cocoa.so"
```
The test covers UTF-8 length boundaries, long Chinese and emoji commits,
attributed strings with combining characters and embedded newlines,
preedit clearing, separate callbacks, and existing control/empty input handling.
It also checks that key-handler callbacks continue staging text and that the
modifier branch retains its previous behavior, including its existing limit.
The change under test only removes the limit on event-loop commits.
The fixture allocates the native receiver without initializing NSView/window
lifecycle, and supplies only the ivars and keyboard callback used by the tested
methods. These small fixture objects deliberately live until process exit;
NSView destruction is not invoked on the uninitialized view. No NSApplication,
NSWindow, input context, Accessibility action or synthetic GUI event is created.
Callback mode fixtures test staging within `insertText`, not a complete physical
keyDown/flagsChanged sequence. Delivery is observed at the native GLFW callback,
not at a terminal child or editor. Run each native module in a separate process
to avoid Objective-C class-name collisions. Against the pre-fix module this test
must fail the oversized event-loop cases; against the repaired module it must
report `all_passed: true` and exit zero.