event-driven test waits, fail-loud on timeout (T-108)

Replaces the fixed Future.delayed sleeps the consultant flagged
with stream-based waits that complete when the awaited event
arrives. Timeout callbacks call fail() with a diagnostic instead
of `onTimeout: () {}` swallowing the signal — a never-producing
pty now reports "pty did not produce X within 5s" instead of an
unhelpful "Actual: ''".

session_test.dart:
  - _readUntil helper subscribes to s.output, completes when a
    marker substring appears (or onDone), fails on timeout.
  - _waitForBuffer polls a buffer the listener is already filling
    after a write; 25ms tick, 5s ceiling, fail-loud on miss.
  - Drops the 500ms settle + 50×100ms polling pattern in the write
    test; uses a "first-byte" completer for prompt-readiness.
  - retry: 2 restored on the four read-dependent forkpty tests
    (the underlying flutter-test-runner pty-output flake hasn't
    fully gone away; recovers cleanly on a fresh spawn).

watcher_test.dart:
  - "emits a created event" awaits stream.firstWhere instead of two
    fixed sleeps.
  - "filters ignored paths" uses pre + post sentinel markers to
    bracket the inotify-delivery window event-driven; the negative
    assertion only runs after the post marker is observed.

event_sink.dart:
  - RecordingEventSink gains a broadcast `stream` for the same
    event-await pattern. PaneRegistry's output test subscribes
    BEFORE spawn so first bytes aren't lost.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 22:05:49 +02:00
co-authored by Claude Opus 4.7
parent 7937da1734
commit b66e8f6cc0
7 changed files with 160 additions and 54 deletions
+13 -1
View File
@@ -7,6 +7,8 @@
/// (server depends on subsystems, not the other way round).
library;
import 'dart:async';
import '../ipc/envelope.dart';
abstract class DaemonEventSink {
@@ -16,10 +18,20 @@ abstract class DaemonEventSink {
/// In-memory recording sink for tests + for composing multi-sink
/// scenarios (e.g. tee to both the wire and an audit log).
class RecordingEventSink implements DaemonEventSink {
RecordingEventSink();
final List<IpcEvent> events = [];
final _controller = StreamController<IpcEvent>.broadcast();
@override
void emit(IpcEvent event) => events.add(event);
void emit(IpcEvent event) {
events.add(event);
_controller.add(event);
}
/// Live stream of every event emitted into this sink. Tests use
/// `stream.firstWhere(...)` for event-driven waits instead of
/// polling the [events] list with `Future.delayed`.
Stream<IpcEvent> get stream => _controller.stream;
/// Convenience: filter to a single subsystem (`pane`, `git`, …).
Iterable<IpcEvent> ofSubsystem(String subsystem) => events.where((e) => e.subsystem == subsystem);