test: run example corpus in zig build test; sx ir → stdout

`zig build test` now runs the full examples/ + issues/ regression corpus
alongside the Zig unit tests, driven by a pure-Zig test
(src/corpus_run.test.zig) — no shell script in the build path. It spawns
the installed `sx` per example (subprocess-isolated, per-run timeout),
diffs stdout/stderr/exit and optional `sx ir` snapshots, and fails the
build on any mismatch. The file list is enumerated at runtime, so new
examples are covered with no test edit.

- `sx ir` / `ir-dump` now write to stdout (fd 1) instead of stderr, so
  the dumps can be piped/redirected.
- `zig build test -Dupdate-goldens` regenerates snapshots in-build,
  byte-identical to the legacy `run_examples.sh --update`; on mismatch
  the runner prints how to regenerate.
- run_examples.sh kept (still used by tools/verify-step.sh) and made
  portable to a bare macOS: timeout/gtimeout fallback, bash 3.2-safe
  empty-array handling.
- CLAUDE.md: document the new workflow.
This commit is contained in:
agra
2026-06-13 09:41:56 +03:00
parent 39488133c9
commit ab3c9202ff
7 changed files with 464 additions and 25 deletions

View File

@@ -193,28 +193,49 @@ pub fn build(b: *std.Build) void {
run_cmd.addArgs(args);
}
// Corpus paths for the LSP corpus-sweep test (src/lsp/corpus_sweep.test.zig).
// Inject absolute corpus dirs at configure time so the in-process analyzer
// sweep is CWD-independent; the test still ENUMERATES the directory
// contents at runtime (new examples are covered with no test edit).
// Corpus paths for the corpus tests (src/lsp/corpus_sweep.test.zig — the
// in-process analyzer sweep — and src/corpus_run.test.zig — the end-to-end
// example/issue runner). Inject absolute corpus dirs + the installed `sx`
// binary path at configure time so the tests are CWD-independent; the
// runner still ENUMERATES the directory contents at runtime, so new
// examples are covered with no test edit.
const corpus_opts = b.addOptions();
corpus_opts.addOption([]const u8, "examples_dir", b.path("examples").getPath(b));
corpus_opts.addOption([]const u8, "issues_dir", b.path("issues").getPath(b));
corpus_opts.addOption([]const u8, "library_dir", b.path("library").getPath(b));
// Absolute path to the installed `sx` binary the corpus runner spawns per
// example. The runner test depends on the install step (below) so this
// exists — and so the sibling library/ tree the binary loads is in place.
corpus_opts.addOption([]const u8, "sx_exe", b.getInstallPath(.bin, "sx"));
// `zig build test -Dupdate-goldens` flips src/corpus_run.test.zig from
// verify mode to regenerate mode: it overwrites each example's expected
// .exit/.stdout/.stderr (+ .ir where one exists) with freshly-normalized
// output instead of asserting against it. The in-build equivalent of the
// legacy `run_examples.sh --update`.
const update_goldens = b.option(
bool,
"update-goldens",
"Regenerate example/issue snapshots instead of verifying them (use with `zig build test`)",
) orelse false;
corpus_opts.addOption(bool, "update_goldens", update_goldens);
mod.addOptions("corpus_paths", corpus_opts);
const mod_tests = b.addTest(.{
.root_module = mod,
});
const run_mod_tests = b.addRunArtifact(mod_tests);
// src/corpus_run.test.zig spawns the installed `sx` binary per example, so
// the mod test binary must not run until `zig-out/bin/sx` + `zig-out/library`
// are installed. This is what folds the full example/issue regression suite
// into `zig build test` — no shell script, just a Zig test.
run_mod_tests.step.dependOn(b.getInstallStep());
const exe_tests = b.addTest(.{
.root_module = exe.root_module,
});
const run_exe_tests = b.addRunArtifact(exe_tests);
const test_step = b.step("test", "Run tests");
const test_step = b.step("test", "Run unit tests + the example/issue regression suite");
test_step.dependOn(&run_mod_tests.step);
test_step.dependOn(&run_exe_tests.step);
}