summaryrefslogtreecommitdiff
path: root/tests.zig
diff options
context:
space:
mode:
Diffstat (limited to 'tests.zig')
-rw-r--r--tests.zig123
1 files changed, 64 insertions, 59 deletions
diff --git a/tests.zig b/tests.zig
index 4cae1c26..51f9cefc 100644
--- a/tests.zig
+++ b/tests.zig
@@ -26,10 +26,45 @@ fn writeAll(fd: c_int, bytes: []const u8) !void {
}
}
-fn waitNoHang(pid: c_int) bool {
- var status: u32 = 0;
- const rc = linux.waitpid(pid, &status, linux.W.NOHANG);
- return posix.errno(rc) == .SUCCESS and rc == @as(usize, @intCast(pid));
+// TIOCSWINSZ on the pty master: changes the size and makes the kernel raise
+// SIGWINCH in the slave's foreground process group (i.e. the app under test).
+fn setWinsize(fd: c_int, rows: u16, cols: u16) !void {
+ const ws = posix.winsize{ .row = rows, .col = cols, .xpixel = 0, .ypixel = 0 };
+ const rc = linux.ioctl(fd, linux.T.IOCSWINSZ, @intFromPtr(&ws));
+ if (posix.errno(rc) != .SUCCESS) return error.IoctlFailed;
+}
+
+// Poll the pty for up to `iters` * 100ms, appending everything read into
+// `output`. Returns true as soon as `needle` is present in the accumulated
+// output (the app renders to the alt screen, so we just scan the byte stream).
+fn pollFor(
+ gpa: std.mem.Allocator,
+ master: c_int,
+ output: *std.ArrayList(u8),
+ needle: []const u8,
+ iters: usize,
+) !bool {
+ if (std.mem.indexOf(u8, output.items, needle) != null) return true;
+ var buf: [4096]u8 = undefined;
+ for (0..iters) |_| {
+ var fds = [_]posix.pollfd{.{ .fd = master, .events = posix.POLL.IN, .revents = 0 }};
+ _ = posix.poll(&fds, 100) catch {};
+ if ((fds[0].revents & (posix.POLL.IN | posix.POLL.HUP | posix.POLL.ERR)) != 0) {
+ const n = posix.read(master, &buf) catch break;
+ if (n == 0) break;
+ try output.appendSlice(gpa, buf[0..n]);
+ if (std.mem.indexOf(u8, output.items, needle) != null) return true;
+ }
+ }
+ return std.mem.indexOf(u8, output.items, needle) != null;
+}
+
+fn dump(items: []const u8, msg: []const u8) void {
+ std.debug.print("E2E FAIL: {s}\ncaptured {d} bytes:\n{s}\n", .{
+ msg,
+ items.len,
+ items[0..@min(items.len, 8192)],
+ });
}
pub fn main(init: std.process.Init) !void {
@@ -41,9 +76,8 @@ pub fn main(init: std.process.Init) !void {
}
const exe = args[1];
- const marker = "PARDES_E2E";
- const command = "echo " ++ marker ++ "\r";
+ // Boot the app in a small pty.
var master: c_int = undefined;
const ws = posix.winsize{ .row = 24, .col = 80, .xpixel = 0, .ypixel = 0 };
const pid = forkpty(&master, null, null, &ws);
@@ -58,63 +92,34 @@ pub fn main(init: std.process.Init) !void {
var output: std.ArrayList(u8) = .empty;
defer output.deinit(gpa);
- var buf: [4096]u8 = undefined;
- for (0..20) |_| {
- var fds = [_]posix.pollfd{.{
- .fd = master,
- .events = posix.POLL.IN,
- .revents = 0,
- }};
- _ = try posix.poll(&fds, 100);
- if ((fds[0].revents & posix.POLL.IN) != 0) {
- const n = posix.read(master, &buf) catch break;
- if (n == 0) break;
- try output.appendSlice(gpa, buf[0..n]);
- break;
- }
- }
-
- try writeAll(master, command);
-
- var saw_marker = false;
- for (0..80) |_| {
- var fds = [_]posix.pollfd{.{
- .fd = master,
- .events = posix.POLL.IN,
- .revents = 0,
- }};
- _ = try posix.poll(&fds, 100);
- if ((fds[0].revents & (posix.POLL.IN | posix.POLL.HUP | posix.POLL.ERR)) != 0) {
- const n = posix.read(master, &buf) catch break;
- if (n == 0) break;
- try output.appendSlice(gpa, buf[0..n]);
- if (std.mem.containsAtLeast(u8, output.items, 1, marker)) {
- saw_marker = true;
- break;
- }
- }
- if (waitNoHang(pid)) break;
+ // 1. The app posts an initial winsize on startup. Its winsize handler bumps
+ // the resize counter to 1 and calls vx.resize, which forces a full
+ // repaint, so "resizes 1" shows up in the rendered stream. This just
+ // confirms the app is up and handled its first size.
+ if (!try pollFor(gpa, master, &output, "resizes=1", 60)) {
+ dump(output.items, "app never handled its initial winsize (no 'resizes=1')");
+ return error.InitialResizeMissing;
}
- try writeAll(master, "\x03");
- for (0..20) |_| {
- if (waitNoHang(pid)) break;
- var fds = [_]posix.pollfd{.{
- .fd = master,
- .events = posix.POLL.IN,
- .revents = 0,
- }};
- _ = posix.poll(&fds, 50) catch {};
- if ((fds[0].revents & posix.POLL.IN) != 0) {
- _ = posix.read(master, &buf) catch 0;
- }
+ // 2. Regression check for "resizes aren't happening": grow the outer pty.
+ // This delivers SIGWINCH to the app. Only if the app installed its
+ // resize handler will it post a new winsize event, bump the counter to
+ // 2, and repaint -> "resizes 2". If the handler is missing the counter
+ // stays at 1 forever and this times out.
+ try setWinsize(master, 50, 200);
+ if (!try pollFor(gpa, master, &output, "resizes=2", 80)) {
+ dump(output.items, "SIGWINCH was not turned into a resize (counter stuck at 1)");
+ return error.ResizeNotDelivered;
}
- if (!saw_marker) {
- std.debug.print("app did not render marker; captured {d} bytes:\n{s}\n", .{
- output.items.len,
- output.items[0..@min(output.items.len, 4096)],
- });
+ // 3. Sanity: the app still forwards keystrokes to its embedded shell.
+ const marker = "PARDES_E2E";
+ try writeAll(master, "echo " ++ marker ++ "\r");
+ if (!try pollFor(gpa, master, &output, marker, 80)) {
+ dump(output.items, "app did not render echoed marker");
return error.MarkerNotRendered;
}
+
+ // Tell the app to quit (ctrl-c is the app's own exit key).
+ try writeAll(master, "\x03");
}