run pty-tagged core tests serially to fix isolate-starvation flake (T-292)

ci/test_core.sh ran 'dart test test/ipc test/pty ... test/pql' in the default
parallel pool, so the real-PTY tests in test/pty competed for fds + CPU with the
other suites and the reader isolate was starved — intermittently failing 'write
sends keystrokes to child' under load. Split into a serial --tags pty pass and a
parallel --exclude-tags pty pass, mirroring ci/test.sh. Same 571 tests; pty pass
verified stable across repeated runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 17:08:57 +02:00
co-authored by Claude Opus 4.8
parent fdccca1294
commit 0231cb41e0
3 changed files with 29 additions and 10 deletions
@@ -971,3 +971,4 @@ Acceptance: ''-'' is treated as a normal command-name character (suggestions kee
RESOLVED by the D-88 ClideTypeahead migration (T-286), verified 2026-06-09. Root cause of both symptoms was the OLD hand-rolled overlay, not the pure helpers (activeSlashQuery/filterSlashCommands always handled ''-''): (1) the old overlay didn''t narrow live as you typed, so a ''-'' looked like it emptied/broke the list; (2) the old focus model raced Tab. Post-migration the popover narrows live (ValueNotifier) and the field keeps focus (captureFocus:false), so ''-'' filters normally and Tab reliably completes. Confirmed bare ''-'' is NOT a keybinding (zoom is ctrl/meta+minus). Added regression tests: hyphen cases in slash_commands_test; composer widget test typing through a hyphen + Tab-accept. Commit d8b9a41.', NULL, '2026-06-09 15:03:23', '2026-06-09 15:03:23', '2026-06-09 15:03:23', NULL, '5f790b8fdba01f358c12abc80f916a7d', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-278', 'status', 'in_progress', 'done', NULL, '2026-06-09 15:03:23', '2026-06-09 15:03:23', '2026-06-09 15:03:23', NULL, '8561bd722293a0e24b1ed4a51d4d1885', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-292', 'status', 'backlog', 'in_progress', NULL, '2026-06-09 15:03:37', '2026-06-09 15:03:37', '2026-06-09 15:03:37', NULL, '95d2f6712b0fddd8c5d33e93b971dbfd', 1) ON CONFLICT(hash) DO NOTHING;
+5
View File
@@ -2447,3 +2447,8 @@ Repro: open the composer, type ''/'' then a command fragment; (a) include a ''-'
Acceptance: ''-'' is treated as a normal command-name character (suggestions keep filtering through hyphens); Tab reliably completes the highlighted suggestion every time (insert via completeSlash). Add/extend unit tests in slash_commands_test.dart for hyphenated queries and a composer widget test for Tab-accept.
RESOLVED by the D-88 ClideTypeahead migration (T-286), verified 2026-06-09. Root cause of both symptoms was the OLD hand-rolled overlay, not the pure helpers (activeSlashQuery/filterSlashCommands always handled ''-''): (1) the old overlay didn''t narrow live as you typed, so a ''-'' looked like it emptied/broke the list; (2) the old focus model raced Tab. Post-migration the popover narrows live (ValueNotifier) and the field keeps focus (captureFocus:false), so ''-'' filters normally and Tab reliably completes. Confirmed bare ''-'' is NOT a keybinding (zoom is ctrl/meta+minus). Added regression tests: hyphen cases in slash_commands_test; composer widget test typing through a hyphen + Tab-accept. Commit d8b9a41.', 'done', 'medium', NULL, NULL, NULL, '2026-06-08 09:20:00', '2026-06-09 15:03:23', NULL, 'e763ef44b7aee33629548cdb3529aa11', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-292', 'bug', NULL, 'Flaky: pty session_test ''write sends keystrokes to child'' starves under parallel/coverage load', 'test/pty/session_test.dart:47 ''write sends keystrokes to child'' (and its sibling pty integration tests) intermittently fail when run in a large parallel pool — observed twice during T-29: once in a bare ''flutter test --coverage'' (whole suite, max parallelism + coverage instrumentation) and once mid pre-push gate (''+570 -1''). Passes reliably in isolation and via the gate''s SERIAL test-core pass (''dart test test/pty'', +571 ok), so it is not a logic bug.
Root cause: these tests spawn a real /bin/sh via NativePty and wait on the reader ISOLATE to deliver bytes. Under heavy CPU contention the reader isolate is starved and the 20s ioTimeout (test/helpers/timeouts.dart) elapses before the shell''s first byte / echo response is delivered. Already hardened once (T-108 swapped wall-clock sleeps for event-driven waits; first-byte Completer + 25ms-poll _waitForBuffer), but the isolate-scheduling assumption still breaks under load.
These tests are tagged ''pty'' and are meant to run in the gate''s serial pass. Fix options: (a) ensure the parallel coverage pass EXCLUDES tag:pty (run pty only in the serial pass) so they never compete for isolate scheduling verify ci/test.sh / dart_test.yaml tag routing; (b) harden against isolate starvation (e.g. give the reader isolate priority, or assert on delivery via a more robust signal); (c) document that ''flutter test --coverage'' over the whole tree is not a supported invocation (use make test + the gate). Prefer (a). Refs: T-108, T-96 (reader-isolate hangs), T-192 (gate parallel/serial passes), D-23 (test pyramid).', 'in_progress', 'medium', NULL, NULL, 'D-23', '2026-06-09 14:57:32', '2026-06-09 15:03:37', NULL, '69ddf72fcc00b5ee9a5e46ceca4591d9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
+16 -3
View File
@@ -34,16 +34,29 @@ REPORTER="${TEST_REPORTER:-failures-only}"
# after SIGTERM if the test ignores it.
CORE_DIRS="test/ipc test/pty test/daemon test/git test/panes test/files test/editor test/pql"
echo "test-core: dart test ${CORE_DIRS} (timeout ${TIMEOUT_SECONDS}s)"
# Run a `dart test` pass under the hard timeout + process-group kill.
run_pass() {
if ! timeout --kill-after=5s "${TIMEOUT_SECONDS}s" \
setsid --wait dart test -r "$REPORTER" $CORE_DIRS ; then
setsid --wait dart test -r "$REPORTER" "$@" ; then
rc=$?
if [[ $rc -eq 124 ]]; then
echo "test-core: TIMEOUT — killing descendants" >&2
pkill -9 -f "dart test test/" 2>/dev/null || true
pkill -9 -f "dart test" 2>/dev/null || true
exit 1
fi
exit $rc
fi
}
# PTY-tagged tests spawn real PTYs (posix_openpt + posix_spawn) and rely on a
# reader isolate; run in the default parallel pool they contend for fds + CPU
# with the other suites and the isolate is starved, flaking 'write sends
# keystrokes to child' (T-292). Serialize them in their own pass — matching
# ci/test.sh — then run everything else in parallel.
echo "test-core: dart test (pty-tagged; --concurrency=1) (timeout ${TIMEOUT_SECONDS}s)"
run_pass --concurrency=1 --tags pty $CORE_DIRS
echo "test-core: dart test (rest; parallel, excludes pty) (timeout ${TIMEOUT_SECONDS}s)"
run_pass --exclude-tags pty $CORE_DIRS
echo "test-core: ok"