a73x

a9c14dfd

Add test-render orchestrator

a73x   2026-04-17 15:55

Commit message
Add test-render orchestrator

Iterates tests/golden/scripts/*.vt, runs waystty --capture on each,
compares via imgdiff against reference PNGs. Continues on failure.
WAYSTTY_GOLDEN_UPDATE=1 copies output to reference instead of diffing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

build.zig
Old New
@@ -353,4 +353,21 @@ pub fn build(b: *std.Build) void {
353 imgdiff_test_mod.addImport("png", png_mod); 353 imgdiff_test_mod.addImport("png", png_mod);
354 const imgdiff_tests = b.addTest(.{ .root_module = imgdiff_test_mod }); 354 const imgdiff_tests = b.addTest(.{ .root_module = imgdiff_test_mod });
355 test_step.dependOn(&b.addRunArtifact(imgdiff_tests).step); 355 test_step.dependOn(&b.addRunArtifact(imgdiff_tests).step);
356
357 const test_render_mod = b.createModule(.{
358 .root_source_file = b.path("src/tools/test_render.zig"),
359 .target = target,
360 .optimize = optimize,
361 });
362 const test_render_exe = b.addExecutable(.{
363 .name = "test-render",
364 .root_module = test_render_mod,
365 });
366 b.installArtifact(test_render_exe);
367
368 const test_render_step = b.step("test-render", "Run all golden VT scripts and diff against references");
369 test_render_step.dependOn(b.getInstallStep());
370 const test_render_run = b.addRunArtifact(test_render_exe);
371 test_render_run.step.dependOn(b.getInstallStep());
372 test_render_step.dependOn(&test_render_run.step);
356 } 373 }
src/tools/test_render.zig
Old New
@@ -0,0 +1,75 @@
1 const std = @import("std");
2
3 pub fn main() !void {
4 var gpa: std.heap.DebugAllocator(.{}) = .init;
5 defer _ = gpa.deinit();
6 const alloc = gpa.allocator();
7
8 const mode_update = blk: {
9 const m = std.posix.getenv("WAYSTTY_GOLDEN_UPDATE") orelse break :blk false;
10 break :blk std.mem.eql(u8, m, "1");
11 };
12
13 try std.fs.cwd().makePath("tests/golden/output");
14
15 var scripts_dir = try std.fs.cwd().openDir("tests/golden/scripts", .{ .iterate = true });
16 defer scripts_dir.close();
17 var it = scripts_dir.iterate();
18
19 var passed: usize = 0;
20 var failed: usize = 0;
21
22 while (try it.next()) |entry| {
23 if (entry.kind != .file) continue;
24 if (!std.mem.endsWith(u8, entry.name, ".vt")) continue;
25
26 const base = entry.name[0 .. entry.name.len - 3];
27 const script_path = try std.fmt.allocPrint(alloc, "tests/golden/scripts/{s}.vt", .{base});
28 defer alloc.free(script_path);
29 const output_path = try std.fmt.allocPrint(alloc, "tests/golden/output/{s}.png", .{base});
30 defer alloc.free(output_path);
31 const reference_path = try std.fmt.allocPrint(alloc, "tests/golden/reference/{s}.png", .{base});
32 defer alloc.free(reference_path);
33 const diff_path = try std.fmt.allocPrint(alloc, "tests/golden/output/{s}.diff.png", .{base});
34 defer alloc.free(diff_path);
35
36 // Run waystty --capture
37 const cap = try std.process.Child.run(.{
38 .allocator = alloc,
39 .argv = &.{ "zig-out/bin/waystty", "--capture", script_path, output_path },
40 });
41 defer alloc.free(cap.stdout);
42 defer alloc.free(cap.stderr);
43 if (cap.term != .Exited or cap.term.Exited != 0) {
44 std.debug.print("FAIL: {s}: capture exited with {}\n stderr: {s}\n",
45 .{ base, cap.term, cap.stderr });
46 failed += 1;
47 continue;
48 }
49
50 if (mode_update) {
51 try std.fs.cwd().makePath("tests/golden/reference");
52 try std.fs.cwd().copyFile(output_path, std.fs.cwd(), reference_path, .{});
53 std.debug.print("UPDATED: {s}\n", .{base});
54 passed += 1;
55 continue;
56 }
57
58 // Run imgdiff
59 const dif = try std.process.Child.run(.{
60 .allocator = alloc,
61 .argv = &.{ "zig-out/bin/imgdiff", output_path, reference_path, diff_path },
62 });
63 defer alloc.free(dif.stdout);
64 defer alloc.free(dif.stderr);
65 std.debug.print("{s}", .{dif.stdout});
66 if (dif.term == .Exited and dif.term.Exited == 0) {
67 passed += 1;
68 } else {
69 failed += 1;
70 }
71 }
72
73 std.debug.print("\n=== test-render: {} passed, {} failed ===\n", .{ passed, failed });
74 if (failed > 0) std.process.exit(1);
75 }