From d1e93d4cdc2e2625d5a1724a7167a5c3e64fe6a2 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 19:49:19 -0300 Subject: Over a slow link the tty keeps one frame in flight: it asks the terminal to answer after each, and draws the latest state when it does --- test/e2e_harness.zig | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) (limited to 'test/e2e_harness.zig') diff --git a/test/e2e_harness.zig b/test/e2e_harness.zig index eaa19b43..9e67c49f 100644 --- a/test/e2e_harness.zig +++ b/test/e2e_harness.zig @@ -64,6 +64,8 @@ pub const Harness = struct { /// when true, print the captured screen state after each pump/waitFor and on /// every assertion, so live test runs can be inspected (zig build test -Dtrace). trace: bool = false, + /// How much of a `CSI 5 n` the output has ended on (`feed`). + status_request: u8 = 0, /// every raw byte the app has emitted (accumulated in pump). Lets tests assert /// on control sequences the emulator consumes and never renders (e.g. OSC 52 /// clipboard writes). Capture is bounded explicitly so a runaway child cannot @@ -147,6 +149,24 @@ pub const Harness = struct { /// Read pty output and feed it to our ghostty terminal for `ms` ms. After /// this, the grid reflects everything the app rendered so far. + /// The app's output into the emulator. The one query answered is the + /// device status request (`CSI 5 n` → `CSI 0 n`), as every terminal + /// does: the tty asks it after each frame and draws no other until the + /// answer (FrameAck in src/tty/tty.zig). + fn feed(self: *Harness, bytes: []const u8) void { + self.stream.nextSlice(bytes); + const request = "\x1b[5n"; + for (bytes) |b| { + if (b == request[self.status_request]) { + self.status_request += 1; + } else self.status_request = if (b == 0x1b) 1 else 0; + if (self.status_request == request.len) { + self.status_request = 0; + _ = libc.write(self.master, "\x1b[0n", 4); + } + } + } + pub fn pump(self: *Harness, ms: i64) !void { const deadline = nowMs() + ms; var buf: [4096]u8 = undefined; @@ -157,7 +177,7 @@ pub const Harness = struct { const n = posix.read(self.master, &buf) catch break; if (n == 0) break; self.recordRaw(buf[0..n]); - self.stream.nextSlice(buf[0..n]); + self.feed(buf[0..n]); } } if (self.trace) self.traceScreen("pump"); @@ -176,7 +196,7 @@ pub const Harness = struct { const n = posix.read(self.master, &buf) catch return false; if (n == 0) return false; self.recordRaw(buf[0..n]); - self.stream.nextSlice(buf[0..n]); + self.feed(buf[0..n]); return true; } @@ -243,7 +263,7 @@ pub const Harness = struct { const n = posix.read(self.master, &buf) catch break; if (n == 0) break; self.recordRaw(buf[0..n]); - self.stream.nextSlice(buf[0..n]); + self.feed(buf[0..n]); } const text = try self.screenText(); defer self.gpa.free(text); @@ -270,7 +290,7 @@ pub const Harness = struct { const n = posix.read(self.master, &buf) catch break; if (n == 0) break; self.recordRaw(buf[0..n]); - self.stream.nextSlice(buf[0..n]); + self.feed(buf[0..n]); if (std.mem.indexOf(u8, self.raw.items, needle) != null) return true; } return false; -- cgit v1.3