summaryrefslogtreecommitdiff
path: root/test/e2e_harness.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-28 13:21:42 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commit0b5ad7a30ba9e7c6eedcb426be294bd94219310f (patch)
tree1a991acfb2a14f25ece19e13e65915dbbb48c7fc /test/e2e_harness.zig
parent95202b9b22751c86f100aae9ff1c64e535dcbd70 (diff)
downloadpardes-0b5ad7a30ba9e7c6eedcb426be294bd94219310f.tar.gz
pardes-0b5ad7a30ba9e7c6eedcb426be294bd94219310f.zip
build.zig runners and testing improvements
Diffstat (limited to 'test/e2e_harness.zig')
-rw-r--r--test/e2e_harness.zig32
1 files changed, 31 insertions, 1 deletions
diff --git a/test/e2e_harness.zig b/test/e2e_harness.zig
index 9099f13e..eb4c7a2f 100644
--- a/test/e2e_harness.zig
+++ b/test/e2e_harness.zig
@@ -15,7 +15,9 @@ extern "c" fn forkpty(
extern "c" fn execvp(file: [*:0]const u8, argv: [*:null]const ?[*:0]const u8) c_int;
extern "c" fn _exit(status: c_int) noreturn;
-fn nowMs() i64 {
+/// Monotonic milliseconds; the harness's only clock (std.time lost
+/// milliTimestamp in 0.16).
+pub fn nowMs() i64 {
var ts: libc.timespec = undefined;
_ = libc.clock_gettime(.MONOTONIC, &ts);
return @as(i64, @intCast(ts.sec)) * 1000 + @divFloor(@as(i64, @intCast(ts.nsec)), 1_000_000);
@@ -133,6 +135,34 @@ pub const Harness = struct {
if (self.trace) self.traceScreen("pump");
}
+ /// Wait up to `ms` for the app to say something, feed whatever arrived to
+ /// the emulator, and report whether anything did. `ms` = 0 polls. This is
+ /// the event-driven half of `pump`: a caller that only cares about the
+ /// SETTLED grid sleeps in poll until the app actually writes, instead of
+ /// burning a fixed window per check (see snapshot.zig's waitStable).
+ pub fn pumpOnce(self: *Harness, ms: i64) !bool {
+ var fds = [_]posix.pollfd{.{ .fd = self.master, .events = posix.POLL.IN, .revents = 0 }};
+ const ready = posix.poll(&fds, @intCast(@max(0, ms))) catch 0;
+ if (ready == 0 or (fds[0].revents & posix.POLL.IN) == 0) return false;
+ var buf: [4096]u8 = undefined;
+ const n = posix.read(self.master, &buf) catch return false;
+ if (n == 0) return false;
+ self.raw.appendSlice(self.gpa, buf[0..n]) catch {};
+ self.stream.nextSlice(buf[0..n]);
+ return true;
+ }
+
+ /// Bytes sitting in the pty that nobody has picked up — keystrokes the app
+ /// has not read yet, or output we have not read yet (the master counts both
+ /// directions). Zero is the proof that the app has actually CONSUMED the
+ /// input just sent: without it, an app still asleep in its event loop looks
+ /// exactly like an app that has finished. See snapshot.zig's waitStable.
+ pub fn pending(self: *Harness) usize {
+ var n: c_int = 0;
+ if (posix.system.ioctl(self.master, posix.T.FIONREAD, @intFromPtr(&n)) != 0) return 0;
+ return if (n > 0) @intCast(n) else 0;
+ }
+
/// Resize both the pty (SIGWINCH to the app) and our terminal (grid reflows
/// to the new size). Harness terminal first so it's ready for the app's
/// re-render at the new size.