mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-10-21 04:32:05 +00:00
Remove runtime-compiled Cocoa IME regression tests
This commit is contained in:
parent
4ef2df194a
commit
7cbea2f539
3 changed files with 0 additions and 260 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue