summaryrefslogtreecommitdiff
path: root/tests.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-02 21:41:13 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commit81992cc92743a98066ee1a9025bc74f91cf0e967 (patch)
tree12c07f68f8ad030f6c6f8a2b3415998d0a87eee7 /tests.zig
parentbead9f0b78380d1428f40fdab339c1c8f88fa626 (diff)
downloadpardes-81992cc92743a98066ee1a9025bc74f91cf0e967.tar.gz
pardes-81992cc92743a98066ee1a9025bc74f91cf0e967.zip
storage and replay
Diffstat (limited to 'tests.zig')
-rw-r--r--tests.zig129
1 files changed, 127 insertions, 2 deletions
diff --git a/tests.zig b/tests.zig
index 701b153a..0604fb98 100644
--- a/tests.zig
+++ b/tests.zig
@@ -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.