diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-16 15:49:12 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-18 23:44:42 -0300 |
| commit | 1551e409c31992437cb2fa864f576d45c8433801 (patch) | |
| tree | e2fae8451f87b735a1360c7c2e383fdc40165789 /test/image_harness.zig | |
| parent | be2a9957708cbf0c478ca861c4a1f0f227bbfe10 (diff) | |
| download | pardes-1551e409c31992437cb2fa864f576d45c8433801.tar.gz pardes-1551e409c31992437cb2fa864f576d45c8433801.zip | |
big slow change: prebuilt shaders (SPIR-V/Metal), core gui reflow, docs, web + snapshot refresh
Diffstat (limited to 'test/image_harness.zig')
| -rw-r--r-- | test/image_harness.zig | 134 |
1 files changed, 99 insertions, 35 deletions
diff --git a/test/image_harness.zig b/test/image_harness.zig index 601bce49..21ec706e 100644 --- a/test/image_harness.zig +++ b/test/image_harness.zig @@ -27,6 +27,9 @@ const fixture_rgb = [3]u8{ 13, 77, 231 }; // filtered-tint results for the fixture's saturated red and green pages. const pdf_filtered_red = [3]u8{ 255, 77, 77 }; const pdf_filtered_green = [3]u8{ 0, 162, 0 }; +const pdf_filtered_blue = [3]u8{ 147, 147, 255 }; + +const PdfFirstPage = enum { red, blue }; pub fn main(init: std.process.Init) !void { var arena_state: std.heap.ArenaAllocator = .init(gpa); @@ -101,9 +104,10 @@ fn appendFmt(out: *BoundedPdf, comptime fmt: []const u8, args: anytype) !void { /// trailing pages make skipped native renders visible in Kitty's transmit /// headers. Building it here keeps the backend test independent of mutool, /// fonts, and checked-in binary fixtures while the xref offsets remain exact. -fn writePdf(arena: std.mem.Allocator, path: [:0]const u8) !void { +fn writePdf(arena: std.mem.Allocator, path: [:0]const u8, first_page: PdfFirstPage) !void { _ = arena; - const first_stream = + const first_stream = switch (first_page) { + .red => \\1 0 0 rg \\0 0 72 48 re \\f @@ -113,7 +117,19 @@ fn writePdf(arena: std.mem.Allocator, path: [:0]const u8) !void { \\4 20 Td \\(FIRST NEEDLE) Tj \\ET - ; + , + .blue => + \\0 0 1 rg + \\0 0 72 48 re + \\f + \\0 0 0 rg + \\BT + \\/F1 10 Tf + \\4 20 Td + \\(FIRST NEEDLE) Tj + \\ET + , + }; const second_stream = \\0 1 0 rg \\0 0 72 48 re @@ -257,10 +273,10 @@ fn runKittyPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) // Keep the generated location token short enough that the 38-column PDF // pane can visibly include each outline title after `path:page:ordinal`. const pdf_path = try std.fmt.allocPrintSentinel(arena, "/tmp/p{d}.pdf", .{libc.getpid()}, 0); - try writePdf(arena, pdf_path); + try writePdf(arena, pdf_path, .red); defer _ = libc.unlink(pdf_path); - var h = try eh.Harness.initArgs(gpa, exe, 16, 80, pdf_path); + var h = try eh.Harness.initArgs(gpa, exe, 16, 40, pdf_path); defer h.deinit(); try h.pump(150); try h.expectRawContains("\x1b_Gi=1,a=q\x1b\\", "app did not issue the Kitty graphics capability query"); @@ -334,9 +350,13 @@ fn runKittyPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) // A viewport step crosses the boundary continuously: page one leaves and // is deleted, page two remains cached, and newly intersecting page three - // is transmitted. Distinct fixture heights identify the latter on wire. + // is transmitted as the 64-row grain covering its visible head. The new + // post-step transmit identifies that page without requiring an offscreen + // whole-page raster during a viewport-sized fling. const page_step_start = h.raw.items.len; - const page_three_transmit = "\x1b_Gf=32,s=96,v=67,i="; + const all_transmits = "\x1b_Gf=32,s="; + const transmits_before_step = std.mem.count(u8, h.raw.items, all_transmits); + const page_three_transmit = "\x1b_Gf=32,s=96,v=64,i="; try h.send("\x06"); try h.expectWaitFor("pdf 2/4 width PdfFit", 5000, "continuous page-down did not enter PDF page two"); const page_step_deadline = eh.nowMs() + 5000; @@ -347,29 +367,31 @@ fn runKittyPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) return error.KittyPdfOffscreenPageNotReleased; if (std.mem.indexOf(u8, h.raw.items[page_step_start..], page_three_transmit) == null) return error.KittyPdfNewVisiblePageMissing; - if (std.mem.count(u8, h.raw.items, transmit) != initial_transmits) + if (std.mem.count(u8, h.raw.items, all_transmits) != transmits_before_step + 1) return error.KittyPdfRetainedNeighborRetransmitted; // Search still addresses semantic pages inside the strip. Returning one - // viewport, then revealing page two's hit, creates a decorated revision. + // viewport, then submitting the bare search selects and Looks its first + // hit through the shared walk, creating a decorated page-two revision. try h.send("\x02"); try h.expectWaitFor("pdf 1/4 width PdfFit", 5000, "continuous page-up did not return to PDF page one"); + const transmits_before_search = std.mem.count(u8, h.raw.items, all_transmits); try h.send("/SECOND\r"); const search_row = try std.fmt.allocPrint(arena, "{s}:2:1 SECOND", .{std.fs.path.basename(pdf_path)}); try h.expectWaitFor(search_row, 5000, "PDF search did not preserve the exact page-two query occurrence"); - const before_look = std.mem.count(u8, h.raw.items, transmit); - try h.send("n"); const look_deadline = eh.nowMs() + 5000; while (eh.nowMs() < look_deadline) { _ = try h.pumpOnce(50); - if (std.mem.count(u8, h.raw.items, transmit) > before_look and h.contains("pdf 2/4 width PdfFit")) break; + if (std.mem.count(u8, h.raw.items, all_transmits) > transmits_before_search and + h.contains("pdf 2/4 width PdfFit")) break; } - try h.expectContains("pdf 2/4 width PdfFit", "n did not reveal the PDF search result at page two"); - if (std.mem.count(u8, h.raw.items, transmit) <= before_look) return error.KittyPdfLookRetransmitMissing; + try h.expectContains("pdf 2/4 width PdfFit", "bare search did not reveal its first PDF hit at page two"); + if (std.mem.count(u8, h.raw.items, all_transmits) <= transmits_before_search) + return error.KittyPdfLookRetransmitMissing; // PdfFit changes only placement. The active decorated raster remains the // same terminal image while height fit supplies an explicit row extent. - const before_fit = std.mem.count(u8, h.raw.items, transmit); + const before_fit = std.mem.count(u8, h.raw.items, all_transmits); const fit_raw_start = h.raw.items.len; try h.send(" tz"); try h.expectWaitFor("pdf 2/4 height PdfFit", 5000, "PdfFit did not expose height fit"); @@ -382,12 +404,12 @@ fn runKittyPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) const height_crop_w = kittyParam(height_apc, 'w') orelse return error.KittyPdfHeightPlacementWrong; if (height_crop_w == 0 or height_crop_w > 96 or kittyParam(height_apc, 'h') != 64) return error.KittyPdfHeightPlacementWrong; - if (std.mem.count(u8, h.raw.items, transmit) != before_fit) + if (std.mem.count(u8, h.raw.items, all_transmits) != before_fit) return error.KittyPdfFitRetransmitted; // The generated PDF carries a real nested outline. PdfSections must show - // all three DFS rows in its output pane while leaving n/N ownership on - // the PDF. The first two destinations share page one but have distinct + // all three DFS rows in its output pane. n/N only select rows; Enter Looks + // the selected destination. The first two share page one but have distinct // page-space y coordinates; their placement APCs must differ while the // second reveal reuses exactly the rasters made visible by the first. try h.send(" ts"); @@ -396,8 +418,8 @@ fn runKittyPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) try h.expectContains("Page two", "PdfSections did not expose the other-page outline row"); const upper_start = h.raw.items.len; - try h.send("n"); - try h.expectWaitFor("pdf 1/4 height PdfFit", 5000, "first PdfSections n did not reveal page one"); + try h.send("n\r"); + try h.expectWaitFor("pdf 1/4 height PdfFit", 5000, "first PdfSections n+Look did not reveal page one"); const upper_deadline = eh.nowMs() + 3000; while (eh.nowMs() < upper_deadline and kittyPlacementApc(h.raw.items, upper_start) == null) _ = try h.pumpOnce(50); @@ -408,8 +430,8 @@ fn runKittyPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) const same_page_transmits = std.mem.count(u8, h.raw.items, "\x1b_Gf=32,"); const lower_start = h.raw.items.len; - try h.send("n"); - try h.expectWaitFor("pdf 1/4 height PdfFit", 5000, "nested PdfSections n left page one"); + try h.send("n\r"); + try h.expectWaitFor("pdf 1/4 height PdfFit", 5000, "nested PdfSections n+Look left page one"); const lower_deadline = eh.nowMs() + 3000; while (eh.nowMs() < lower_deadline and kittyPlacementApc(h.raw.items, lower_start) == null) _ = try h.pumpOnce(50); @@ -420,12 +442,32 @@ fn runKittyPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) if (std.mem.count(u8, h.raw.items, "\x1b_Gf=32,") != same_page_transmits) return error.KittyPdfSectionsSamePageRetransmitted; - try h.send("n"); - try h.expectWaitFor("pdf 2/4 height PdfFit", 5000, "third PdfSections n did not reveal page two"); - try h.send("N"); - try h.expectWaitFor("pdf 1/4 height PdfFit", 5000, "PdfSections N did not return to the nested page-one row"); + try h.send("n\r"); + try h.expectWaitFor("pdf 2/4 height PdfFit", 5000, "third PdfSections n+Look did not reveal page two"); + try h.send("N\r"); + try h.expectWaitFor("pdf 1/4 height PdfFit", 5000, "PdfSections N+Look did not return to the nested page-one row"); - std.debug.print("PDF harness ok: Kitty continuous cache + search/fit + nested PdfSections n/N\n", .{}); + // Editor-style save: build a same-sized blue first page on another inode, + // atomically rename it over the live pathname, and send NO input. The + // inotify wake alone must reopen MuPDF and transmit a fresh raster. + const replacement_path = try std.fmt.allocPrintSentinel(arena, "{s}.replacement", .{pdf_path}, 0); + defer _ = libc.unlink(replacement_path); + try writePdf(arena, replacement_path, .blue); + try h.pump(250); + const before_reload = std.mem.count(u8, h.raw.items, all_transmits); + try h.pump(250); + if (std.mem.count(u8, h.raw.items, all_transmits) != before_reload) + return error.KittyPdfNavigationDidNotSettle; + if (libc.rename(replacement_path, pdf_path) != 0) return error.PdfLiveRenameFailed; + const reload_deadline = eh.nowMs() + 5000; + while (eh.nowMs() < reload_deadline and + std.mem.count(u8, h.raw.items, all_transmits) <= before_reload) + _ = try h.pumpOnce(50); + if (std.mem.count(u8, h.raw.items, all_transmits) <= before_reload) + return error.KittyPdfLiveReloadMissing; + try h.expectWaitFor("reloaded", 5000, "PDF inotify reload did not report a committed transaction"); + + std.debug.print("PDF harness ok: Kitty continuous cache + search/fit + sections + idle live reload\n", .{}); } fn kittyPlacementApc(raw: []const u8, start: usize) ?[]const u8 { @@ -498,7 +540,7 @@ fn runGui(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) !void fn runGuiPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) !void { const pdf_path = try std.fmt.allocPrintSentinel(arena, "{s}/pages.pdf", .{base}, 0); const capture_path = try std.fmt.allocPrintSentinel(arena, "{s}/latest.ppm", .{base}, 0); - try writePdf(arena, pdf_path); + try writePdf(arena, pdf_path, .red); _ = libc.unlink(capture_path); _ = setenv("PARDES_TEST", "1", 1); _ = setenv("PARDES_TEST_CAPTURE_DIR", base, 1); @@ -532,7 +574,10 @@ fn runGuiPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) !v fit_width.width() - fit_height.width() else fit_height.width() - fit_width.width(); - if (fit_height.height() < fit_width.height() * 2 or width_delta > 8) { + // A positional PDF now owns the full single column, so width fit already + // occupies much of this short test window. Height fit must still extend + // the colored page by at least a third while preserving its clipped width. + if (fit_height.height() * 3 < fit_width.height() * 4 or width_delta > 8) { std.debug.print("SDL PDF fit extents did not change as expected: width={any}, height={any}\n", .{ fit_width, fit_height }); return error.SdlPdfFitGeometryWrong; } @@ -554,7 +599,8 @@ fn runGuiPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) !v const final_pixels = ppmPixels(final_capture) orelse return error.BadCapture; if (countColor(final_pixels, pdf_filtered_red) != 0) return error.SdlPdfOffscreenPageStillDrawn; - // Exercise the actual PdfSections output + Look stepping path in SDL too. + // Exercise the actual PdfSections output + n/N selection + Look path in + // SDL too. // The first two nested outline rows stay on red page one at different y // positions, the third moves to green page two, and N returns exactly to // the nested page-one destination. @@ -565,19 +611,37 @@ fn runGuiPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) !v try h.send(" ts"); _ = try waitForCaptureBounds(&h, capture_path, pdf_filtered_red, 500, 5000); _ = libc.unlink(capture_path); - try h.send("n"); + try h.send("n\r"); _ = try waitForCaptureBounds(&h, capture_path, pdf_filtered_red, 200, 5000); _ = libc.unlink(capture_path); - try h.send("n"); + try h.send("n\r"); _ = try waitForCaptureBounds(&h, capture_path, pdf_filtered_red, 100, 5000); _ = libc.unlink(capture_path); - try h.send("n"); + try h.send("n\r"); _ = try waitForCaptureBounds(&h, capture_path, pdf_filtered_green, 200, 5000); _ = libc.unlink(capture_path); - try h.send("N"); + try h.send("N\r"); _ = try waitForCaptureBounds(&h, capture_path, pdf_filtered_red, 100, 5000); - std.debug.print("PDF harness ok: SDL continuous strip + nested PdfSections red->green->red ({d}->{d} pixels)\n", .{ fit_width.count, green.count }); + // The GUI host receives the same parent-directory rename while idle. A + // newly captured blue first page proves the wake reached MuPDF, replaced + // the pane raster, and reached the actual GPU output without a key event. + const replacement_path = try std.fmt.allocPrintSentinel(arena, "{s}/pages.replacement.pdf", .{base}, 0); + defer _ = libc.unlink(replacement_path); + try writePdf(arena, replacement_path, .blue); + _ = libc.unlink(capture_path); + if (libc.rename(replacement_path, pdf_path) != 0) return error.PdfLiveRenameFailed; + const blue = try waitForCaptureBounds(&h, capture_path, pdf_filtered_blue, 100, 5000); + const reloaded_capture = try readFile(gpa, capture_path); + defer gpa.free(reloaded_capture); + const reloaded_pixels = ppmPixels(reloaded_capture) orelse return error.BadCapture; + if (countColor(reloaded_pixels, pdf_filtered_red) != 0) + return error.SdlPdfLiveReloadRetainedOldPixels; + + std.debug.print( + "PDF harness ok: SDL continuous strip + sections + idle red->blue reload ({d}->{d}->{d} pixels)\n", + .{ fit_width.count, green.count, blue.count }, + ); } const ColorBounds = struct { |
