diff options
| author | Gabriel Schneider <[email protected]> | 2026-07-02 21:41:13 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-01 15:02:07 -0300 |
| commit | 81992cc92743a98066ee1a9025bc74f91cf0e967 (patch) | |
| tree | 12c07f68f8ad030f6c6f8a2b3415998d0a87eee7 /tests.zig | |
| parent | bead9f0b78380d1428f40fdab339c1c8f88fa626 (diff) | |
| download | pardes-81992cc92743a98066ee1a9025bc74f91cf0e967.tar.gz pardes-81992cc92743a98066ee1a9025bc74f91cf0e967.zip | |
storage and replay
Diffstat (limited to 'tests.zig')
| -rw-r--r-- | tests.zig | 129 |
1 files changed, 127 insertions, 2 deletions
@@ -2,6 +2,7 @@ const std = @import("std"); const h = @import("e2e_harness.zig"); const Harness = h.Harness; const config = @import("config"); +const dump = @import("dump.zig"); // E2E tests for pardes. The harness forks the app in a pty and parses its // output with a ghostty terminal emulator, so all assertions read the 2D screen @@ -36,15 +37,70 @@ fn colOf(text: []const u8, needle: []const u8) ?usize { return null; } +fn writeReplayFixture(io: std.Io, gpa: std.mem.Allocator, path: []const u8) !void { + const file_content = + "const std = @import(\"std\");\n" ++ + "\n" ++ + "// utf8: \xe2\x80\x94\n"; + const content_b64 = try dump.encodeBytes(gpa, file_content); + defer gpa.free(content_b64); + const panes = [_]dump.Pane{.{ + .kind = .file, + .tag = "nm /tmp/replay_fixture.zig Save Del", + .body = "", + .cols = 78, + .rows = 22, + .file = .{ + .path = "/tmp/replay_fixture.zig", + .content = file_content, + .content_b64 = content_b64, + }, + }}; + const col_panes = [_]usize{0}; + const columns = [_]dump.Column{.{ .weight = 1, .panes = &col_panes }}; + try dump.writeFile(io, gpa, path, .{ + .screen = .{ .cols = 80, .rows = 24 }, + .topbar = "Kill Newcol Tutor Debug Colors NextColor Dump", + .theme = "dark", + .columns = &columns, + .panes = &panes, + }); +} + +fn writeReplayImageFixture(io: std.Io, gpa: std.mem.Allocator, path: []const u8) !void { + const png_b64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII="; + const panes = [_]dump.Pane{.{ + .kind = .image, + .tag = "img /tmp/replay.png Petscii Del", + .body = "", + .cols = 78, + .rows = 22, + .image = .{ + .path = "/tmp/replay.png", + .bytes_b64 = png_b64, + }, + }}; + const col_panes = [_]usize{0}; + const columns = [_]dump.Column{.{ .weight = 1, .panes = &col_panes }}; + try dump.writeFile(io, gpa, path, .{ + .screen = .{ .cols = 80, .rows = 24 }, + .topbar = "Kill Newcol Tutor Debug Colors NextColor Dump", + .theme = "dark", + .columns = &columns, + .panes = &panes, + }); +} + pub fn main(init: std.process.Init) !void { const gpa = init.gpa; const io = init.io; const args = try init.minimal.args.toSlice(init.arena.allocator()); - if (args.len != 2) { - std.debug.print("usage: {s} <pardes-exe>\n", .{args[0]}); + if (args.len != 3) { + std.debug.print("usage: {s} <pardes-exe> <pardes-replay-exe>\n", .{args[0]}); return error.BadArgs; } const exe = args[1]; + const replay_exe = args[2]; var hs = try Harness.init(gpa, exe.ptr, 24, 80); hs.trace = config.trace; @@ -61,6 +117,75 @@ pub fn main(init: std.process.Init) !void { // the resize counter to 1 and repaints, so "resizes=1" shows in the debug // overlay. Confirms the app is up and handled its first size. try hs.expectWaitFor("resizes=1", 6000, "app never handled its initial winsize (no 'resizes=1')"); + try hs.expectWaitFor("build.zig", 8000, "new terminals did not auto-ls their directory on open"); + + // 1b. Dump is a global-tagbar builtin. It writes a backend-independent ZON + // snapshot with compact pane ids, layout columns, rendered pane text, and + // pane backing contents. + std.Io.Dir.deleteFileAbsolute(io, dump.default_path) catch {}; + try hs.send("\x1b[<1;43;1M\x1b[<1;43;1m"); // middle-click "Dump" in the topbar + try hs.pump(800); + { + const dumped = dump.readFile(io, gpa, dump.default_path) catch { + hs.dump("Dump did not write a parseable /tmp/pardes-dump.zon"); + return error.ExpectFailed; + }; + defer dump.free(gpa, dumped); + if (dumped.panes.len == 0 or dumped.columns.len == 0 or dumped.active >= dumped.panes.len) { + hs.dump("Dump wrote an invalid empty layout"); + return error.ExpectFailed; + } + if (std.mem.indexOf(u8, dumped.topbar, "Dump") == null) { + hs.dump("Dump snapshot did not include the main tagline"); + return error.ExpectFailed; + } + if (!std.mem.eql(u8, dumped.theme, "dark")) { + hs.dump("Dump snapshot did not include the active theme"); + return error.ExpectFailed; + } + if (dumped.panes[0].kind != .terminal or dumped.panes[0].terminal == null) { + hs.dump("Dump snapshot did not capture the terminal pane stream"); + return error.ExpectFailed; + } + if (dumped.panes[0].terminal.?.stream_b64.len == 0) { + hs.dump("Dump snapshot did not include base64 terminal bytes"); + return error.ExpectFailed; + } + const tty_bytes = try dump.decodeBytes(gpa, dumped.panes[0].terminal.?.stream_b64); + defer gpa.free(tty_bytes); + if (std.mem.indexOf(u8, tty_bytes, "build.zig") == null) { + hs.dump("Dump terminal byte stream does not replay the visible ls output"); + return error.ExpectFailed; + } + } + { + var replay_h = try Harness.initArgs(gpa, replay_exe.ptr, 24, 80, dump.default_path); + replay_h.trace = config.trace; + defer replay_h.deinit(); + try replay_h.pump(1200); + try replay_h.expectContains("Kill Newcol", "replay did not render the dump topbar"); + try replay_h.expectContains("build.zig", "replay did not rebuild the terminal from the dumped byte stream"); + try replay_h.expectNotContains("\xc2\xaa", "replay rendered corrupt prefix bytes"); + } + { + const fixture_path = "/tmp/pardes-replay-fixture.zon"; + try writeReplayFixture(io, gpa, fixture_path); + var replay_file = try Harness.initArgs(gpa, replay_exe.ptr, 24, 80, fixture_path); + replay_file.trace = config.trace; + defer replay_file.deinit(); + try replay_file.pump(1200); + try replay_file.expectContains(" 1 const std", "replay file pane line numbers are broken"); + try replay_file.expectNotContains("\xc2\xaa", "replay file line numbers used invalid stack bytes"); + } + { + const fixture_path = "/tmp/pardes-replay-image.zon"; + try writeReplayImageFixture(io, gpa, fixture_path); + var replay_image = try Harness.initArgs(gpa, replay_exe.ptr, 24, 80, fixture_path); + replay_image.trace = config.trace; + defer replay_image.deinit(); + try replay_image.pump(1200); + try replay_image.expectContains("/tmp/replay.png", "replay image pane did not render from dumped bytes"); + } // 2. Regression: grow the pty -> SIGWINCH -> app re-resizes. Counter bumps // to 2 only if the resize handler is installed. |
