From e5f9e172330bc4500995ddd5954ed94f94e07af6 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 25 Aug 2026 11:05:21 -0300 Subject: tests: a capture is a delta and a click names its word, so a tagline edit stops rewriting the suite --- build.zig | 24 +- docs/design.typ | 29 +- src/CHANGELOG.md | 20 ++ test/macos_e2e.swift | 11 +- test/snapshot.zig | 283 +++++++++++---- test/snapshots/acmefs-event.golden | 57 +-- test/snapshots/acmefs.golden | 57 +-- test/snapshots/argv.golden | 91 ----- test/snapshots/badutf.golden | 48 --- test/snapshots/boot.golden | 28 +- test/snapshots/builtins.golden | 528 ++++++++-------------------- test/snapshots/chordcut.golden | 377 ++------------------ test/snapshots/cmdword.golden | 220 +----------- test/snapshots/cornerdrag-left.golden | 220 +++++------- test/snapshots/cornerdrag.golden | 413 +++++++++++----------- test/snapshots/cursorline.golden | 73 +--- test/snapshots/doccol.golden | 157 +-------- test/snapshots/dump.snap | 4 +- test/snapshots/edit.golden | 13 +- test/snapshots/exec-bigdrag.golden | 1 - test/snapshots/exec.golden | 114 +----- test/snapshots/exec.snap | 7 +- test/snapshots/fileedit.golden | 81 +---- test/snapshots/filewatch.golden | 135 +++----- test/snapshots/find.golden | 509 ++------------------------- test/snapshots/find.snap | 6 +- test/snapshots/fsearch.golden | 239 +------------ test/snapshots/ghostty-mid.golden | 11 - test/snapshots/grep.golden | 237 +------------ test/snapshots/grep.snap | 10 +- test/snapshots/hscroll.golden | 136 +------- test/snapshots/images.golden | 86 +---- test/snapshots/jumps.golden | 594 ++++++++----------------------- test/snapshots/layout-close.golden | 195 +---------- test/snapshots/layout-open.golden | 144 +------- test/snapshots/leader.golden | 615 ++++++++------------------------- test/snapshots/load.golden | 13 +- test/snapshots/load.snap | 4 +- test/snapshots/logquiet.golden | 25 -- test/snapshots/look-center.golden | 83 +---- test/snapshots/look-dir.golden | 11 - test/snapshots/look-file-1col.golden | 8 - test/snapshots/look-file.golden | 13 +- test/snapshots/lookloaded.golden | 75 +--- test/snapshots/lookpanes.golden | 252 +------------- test/snapshots/lookrange.golden | 435 +++-------------------- test/snapshots/lsp-rename.golden | 59 +--- test/snapshots/lsp.golden | 275 +-------------- test/snapshots/lspcomplete.golden | 309 +---------------- test/snapshots/lspcompletemoved.golden | 5 - test/snapshots/lspdebug.golden | 233 ++----------- test/snapshots/lsprelpath.golden | 120 +------ test/snapshots/markup.golden | 31 +- test/snapshots/modal.golden | 315 +---------------- test/snapshots/msg.golden | 165 +-------- test/snapshots/multicursor.golden | 165 +-------- test/snapshots/nested-optout.golden | 82 +---- test/snapshots/nested.golden | 58 ---- test/snapshots/pipe.golden | 28 +- test/snapshots/psearch.golden | 247 +------------ test/snapshots/reflow.golden | 4 - test/snapshots/respawn.golden | 23 +- test/snapshots/respawn.snap | 8 +- test/snapshots/restore.golden | 59 +--- test/snapshots/restore.snap | 8 +- test/snapshots/rsearch.golden | 276 +-------------- test/snapshots/savepane.golden | 15 + test/snapshots/savepane.snap | 33 ++ test/snapshots/scroll.golden | 189 +++------- test/snapshots/scrolloff.golden | 111 +++--- test/snapshots/selarg.golden | 75 +--- test/snapshots/selregex.golden | 225 +----------- test/snapshots/shellset.golden | 60 +--- test/snapshots/shrinktag.golden | 150 +------- test/snapshots/splitparent.golden | 71 +--- test/snapshots/stepgrain.golden | 249 +------------ test/snapshots/syntax.golden | 71 +--- test/snapshots/tag.golden | 140 +------- test/snapshots/tag.snap | 25 +- test/snapshots/tagalign.golden | 83 +---- test/snapshots/tagalign.snap | 8 +- test/snapshots/tagalign2col.golden | 117 ++----- test/snapshots/tagbottom.golden | 553 +++++++++-------------------- test/snapshots/tagbottom.snap | 4 +- test/snapshots/tagbottomimage.golden | 140 +++----- test/snapshots/tagbottomimage.snap | 22 +- test/snapshots/tagcmd.golden | 144 +------- test/snapshots/tagnav.golden | 607 +------------------------------- test/snapshots/tagpath.golden | 237 +------------ test/snapshots/theme.golden | 171 +-------- test/snapshots/theme.snap | 12 +- test/snapshots/themesel.golden | 297 +++------------- test/snapshots/tinywin.golden | 61 ---- test/snapshots/topbarexec.golden | 66 ++-- test/snapshots/topbarexec.snap | 12 +- test/snapshots/tty.golden | 176 +--------- test/snapshots/ttyclick.golden | 110 +----- test/snapshots/ttyclick.snap | 4 +- test/snapshots/ttyedit.golden | 148 +------- test/snapshots/ttyfollow.golden | 92 ++--- test/snapshots/ttyhelp.golden | 114 ++---- test/snapshots/ttyhelp.snap | 4 +- test/snapshots/ttylook.golden | 232 +------------ test/snapshots/ttyonly.golden | 27 +- test/snapshots/ttypaste.golden | 41 --- test/snapshots/ttyprompt.golden | 77 +---- test/snapshots/ttytaken.golden | 152 ++------ test/snapshots/tutor.golden | 32 +- test/snapshots/tutor.snap | 4 +- test/snapshots/wheeldrift.golden | 106 +++--- test/snapshots/winch.golden | 26 -- test/snapshots/windownav.golden | 358 +------------------ test/snapshots/windowops.golden | 443 +++++++----------------- test/snapshots/windowops.snap | 8 +- test/snapshots/wrap.golden | 139 +------- test/snapshots/yankpaste.golden | 123 +------ test/web_snapshot.mjs | 4 +- 117 files changed, 2460 insertions(+), 13070 deletions(-) create mode 100644 test/snapshots/savepane.golden create mode 100644 test/snapshots/savepane.snap diff --git a/build.zig b/build.zig index 7d380eb8..061d1f2c 100644 --- a/build.zig +++ b/build.zig @@ -76,6 +76,12 @@ pub fn build(b: *std.Build) void { const requested_optimize = b.standardOptimizeOption(.{}); const static = b.option(bool, "static", "statically link") orelse false; const dump_path = b.option([]const u8, "dump", "dump .zon embedded into the web shell (-Dplatform=web)"); + // one substring, because that is what a dev loop needs: see the unit-test + // step for why the whole-binary run is worth narrowing + const test_filters: []const []const u8 = if (b.option([]const u8, "test-filter", "run only tests whose name contains this")) |f| + &.{f} + else + &.{}; const is_web = platform == .web; const enable_mupdf = b.option(bool, "mupdf", "native PDF rendering with MuPDF (AGPL/commercial; native default on, web off; -Dmupdf=false disables)") orelse !is_web; // JPEG 2000, and with it scanned PDFs: a scan is one /JPXDecode image per @@ -243,7 +249,12 @@ pub fn build(b: *std.Build) void { .name = "pardes-gen-themes", .root_module = b.createModule(.{ .target = b.graph.host, - .optimize = .ReleaseSafe, + // Debug on purpose: it runs for ~40ms, and every core module + // imports what it generates, so its compile is the first link of + // every cold build. ReleaseSafe cost 16s of compile to save 47ms of + // run time, and the 226 files it writes are byte-identical either + // way. + .optimize = .Debug, .root_source_file = b.path("tools/gen_themes.zig"), }), }); @@ -1025,7 +1036,7 @@ pub fn build(b: *std.Build) void { // configuration theirs is not declared. macos.zig imports the core, so // this one aggregate reaches both the C-ABI guard and the core tests. const unit_step = b.step("unit-test", "run the C-ABI guard and core unit tests"); - unit_step.dependOn(&b.addRunArtifact(b.addTest(.{ .root_module = root_mod })).step); + unit_step.dependOn(&b.addRunArtifact(b.addTest(.{ .root_module = root_mod, .filters = test_filters })).step); web_step.dependOn(&b.addFail("web needs -Dplatform=web -Dtarget=wasm32-freestanding -Ddump=").step); } else { // binary name: the native-shaped linux-x86_64 tty build stays `pardes` @@ -1295,8 +1306,15 @@ pub fn build(b: *std.Build) void { // runtime switch, which test analysis never enters. main.zig's test // block names the selected shell, user config, and platform-only // helpers; its ordinary core import reaches pardes.zig and modal.zig. + // + // `-Dtest-filter=` narrows it. The whole binary is 14s and + // two tests are 8.6s of that, so a one-line change to a module with a + // three-millisecond test used to cost the full run: `zig build + // unit-test -Dtest-filter="an untouched tagline"` is ~1s. The test + // runner's own `--test-filter` never worked here - nothing forwarded + // `--` args to the run step, so it was parsed as a script name. if (platform == .tty or platform == .gui) { - const shell_test = b.addTest(.{ .root_module = root_mod }); + const shell_test = b.addTest(.{ .root_module = root_mod, .filters = test_filters }); unit_step.dependOn(&b.addRunArtifact(shell_test).step); } } diff --git a/docs/design.typ b/docs/design.typ index 0b545bae..8b93614e 100644 --- a/docs/design.typ +++ b/docs/design.typ @@ -285,11 +285,38 @@ captures the rendered grid — text, cursor, and per-cell style runs — through its own ghostty terminal. Goldens are generated from the old binary (`zig build snap -- --update`); the new binary must reproduce them byte for byte (`pardes-snap pardes/zig-out/bin/pardes`). Eighteen scripts covered the -checklist in Appendix A at the rewrite; ninety cover it and everything +checklist in Appendix A at the rewrite; ninety-five cover it and everything since. All pass. Determinism pins: fixed workdir paths (they appear in tags), a controlled `$HOME` with `PS1='$ '`, `LC_ALL=C`, and a grid-stability sync primitive instead of timing guesses. +Two of those pins were doing damage rather than work. A capture used to restate +the whole screen, so 82% of golden lines were a copy of the line above, every +golden carried the topbar and a tag row, and adding one builtin word to a +tagline rewrote 78 of them — 3,758 lines of diff for a change no test was +about. A capture is now a *delta* against the previous capture of the same kind +in the same script: the first is the whole screen, the rest are only the rows +that changed. The corpus went from 16,700 lines to 5,722 and the same one-word +edit now moves 337. A capture whose only change is the cursor is the empty +delta its script always meant. + +And a click used to name a screen column, which is a coordinate into that same +chrome. When `Newtty`, `Joincol` and `Changelog` were added, seven scripts began +clicking the word next door — `tutor.snap` clicked `Grep`, `exec.snap` clicked +`Newtty` where it meant `Del`, `tagbottomimage.snap` clicked blank space 176 +columns from the `Del` whose effect it asserted — and `--update` blessed the +result, leaving 256 golden lines green while asserting the opposite of their own +first line. A click may now name the word (`press middle @Del 2`, `@Del#2` for +the second pane on a row, `@Save-2` for a column beside one), so the word is +either there to be clicked or the script fails. + +Regeneration was the other half of that failure: `--update` captured once, +serially, and wrote whatever it saw, which is how six wrong clicks became +goldens. It now captures in parallel at the widest probe settings the harness +has and then runs the ordinary verify pass over what it wrote, so a capture that +does not reproduce is reported instead of committed (77s to 41s, and the retry +machinery that serial update never had). + Three deliberate deviations surfaced by the oracle, kept after review: the greeting `ls` waits for the exact OSC 133 B input mark after the real resize (the prototype raced bash's startup and won only by allocator luck); shells diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md index 11df45ab..0b7bb12e 100644 --- a/src/CHANGELOG.md +++ b/src/CHANGELOG.md @@ -2,6 +2,26 @@ ## 0.0.1 +- Snapshot captures are deltas against the previous capture in the same script, + and a scripted click may name a word (`press middle @Del 2`, `@Del#2` for the + second pane on a row, `@Save-2` for a column beside one) instead of a screen + column. Adding one builtin word to a tagline used to rewrite 78 goldens + (3,758 lines) and silently move seven scripts onto the wrong word: `tutor.snap` + had been clicking `Grep` and asserting a tutor pane, `tagbottomimage.snap` + clicked 176 columns from the `Del` it claimed to execute. Seven tests regained + their subject, the corpus dropped from 16,700 lines to 5,722, and the same + edit now moves 337 lines. +- `zig build snap -- --update` regenerates in parallel at the widest probe + settings and then verifies every golden it just wrote, instead of capturing + once, serially, and blessing whatever it saw: 77s to 41s, and a capture that + does not reproduce is reported rather than committed. +- `zig build unit-test -Dtest-filter=` runs one test. The whole + binary is 14s, so a one-line change used to cost the full run; the test + runner's own `--test-filter` was silently dropped by the build. +- A snapshot script covers the `Save` prompt for the first time: armed on a + `+New` scratch it draws on the message row, prefilled with the pane's own + directory, and submitting it turns the scratch into the file it names. + - `pardes --fs` serves acme's control filesystem over Linux FUSE: a directory per pane holding `addr`, `body`, `ctl`, `data`, `errors`, `event`, `rdsel`, `tag`, `wrsel`, `xdata`, plus `index`, `cons` and `new/` at the root. A diff --git a/test/macos_e2e.swift b/test/macos_e2e.swift index f1bad492..c076aebb 100644 --- a/test/macos_e2e.swift +++ b/test/macos_e2e.swift @@ -13,10 +13,13 @@ // entry points the NSEvent overrides call. What a script exercises is what a // hand exercises. // -// The output format is test/snapshot.zig's, deliberately byte-identical in -// shape (`== snap