From 5b739ef6a734da72e336c5d5e248f27cb31a2fb4 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sun, 31 May 2026 19:01:42 +0200 Subject: [PATCH] run the PTY tests serially instead of retrying them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pty-tagged tests spawn real PTYs and flaked when dart test ran them in parallel (fd contention) — papered over with retry: 2. Run that pass with --concurrency=1 and drop the retries: serialization is the correct fix for resource-bound tests. Verified stable across repeated runs. T-193. Co-Authored-By: Claude --- ci/test.sh | 7 +++++-- test/panes/registry_test.dart | 2 +- test/pty/session_test.dart | 8 ++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ci/test.sh b/ci/test.sh index 15155001..3be44b2d 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -24,8 +24,11 @@ flutter analyze echo "==> dart format (whole tree)" dart format --set-exit-if-changed . -echo "==> dart test (pty — unreliable under the flutter test runner)" -dart test --tags pty test/pty/session_test.dart test/panes/registry_test.dart +echo "==> dart test (pty — unreliable under the flutter test runner; serial)" +# --concurrency=1: these spawn real PTYs and compete for fds when run in +# parallel, which flaked them (registry/session). Serialize — the proper fix +# for resource-bound tests, vs. the old per-test `retry:` band-aid. (T-193) +dart test --concurrency=1 --tags pty test/pty/session_test.dart test/panes/registry_test.dart if [[ "$coverage" == 1 ]]; then echo "==> flutter test --coverage (gate; unit + widget + golden + a11y)" diff --git a/test/panes/registry_test.dart b/test/panes/registry_test.dart index 994d4241..2a52b1bf 100644 --- a/test/panes/registry_test.dart +++ b/test/panes/registry_test.dart @@ -43,7 +43,7 @@ void main() { expect(evt.data['id'], pane.id); }); - test('output events base64-encode the child bytes', tags: ['pty'], retry: 2, () async { + test('output events base64-encode the child bytes', tags: ['pty'], () async { // Subscribe to the sink stream BEFORE spawn so we don't miss // any pane.output events that arrive between spawn and listen. final buf = StringBuffer(); diff --git a/test/pty/session_test.dart b/test/pty/session_test.dart index 22dbb2b5..c27f7664 100644 --- a/test/pty/session_test.dart +++ b/test/pty/session_test.dart @@ -26,7 +26,7 @@ void main() { if (!Platform.isLinux && !Platform.isMacOS) return; group('NativePty', () { - test('spawns shell -c echo and reads output', tags: ['pty'], retry: 2, () async { + test('spawns shell -c echo and reads output', tags: ['pty'], () async { final s = NativePty.start( executable: '/bin/sh', arguments: ['-c', 'echo hello-pty'], @@ -44,7 +44,7 @@ void main() { expect(got, contains('hello-pty')); }); - test('write sends keystrokes to child', tags: ['pty'], retry: 2, () async { + test('write sends keystrokes to child', tags: ['pty'], () async { final s = NativePty.start( executable: '/bin/sh', arguments: [], @@ -79,7 +79,7 @@ void main() { expect(result, contains('write-test-ok')); }); - test('close kills child and closes output', tags: ['pty'], retry: 2, () async { + test('close kills child and closes output', tags: ['pty'], () async { final s = NativePty.start( executable: '/bin/sh', arguments: [], @@ -103,7 +103,7 @@ void main() { expect(s.isClosed, isTrue); }); - test('bare command name resolves via the PATH env var', tags: ['pty'], retry: 2, () async { + test('bare command name resolves via the PATH env var', tags: ['pty'], () async { // 'sh' is a bare command; without resolution, execve would fail. final s = NativePty.start( executable: 'sh',