test harness: add -Dname to scope the corpus to specific examples

`zig build test -Dname=examples/0625-foo.sx[,examples/0626-bar.sx]` runs ONLY the
named example(s) — full repo-relative .sx paths, comma-separated (a leading `./`
is tolerated). Empty = run everything (unchanged default).

Why: a full `-Dupdate-goldens` re-runs and rewrites all ~690 snapshots, so one
flaky/host-divergent example (AOT links, cross-arch `target` examples) can clobber
a good snapshot. `-Dname` regenerates only the named example(s) and touches
nothing else. It also busts the cached test-run result — the corpus enumerates
.sx/expected files at runtime, so a bare snapshot edit alone is otherwise served
from cache.

- build.zig: new `name` option threaded onto corpus_paths.
- corpus_run.test.zig: `nameMatchesFilter` + a per-example skip in the run loop.
- CLAUDE.md: document the targeted-regen workflow under Snapshot integrity.
This commit is contained in:
agra
2026-06-17 14:55:06 +03:00
parent 0b4c50b187
commit 88c4cbcfa5
3 changed files with 54 additions and 0 deletions

View File

@@ -160,6 +160,21 @@ fn readOptional(io: std.Io, gpa: std.mem.Allocator, abs_path: []const u8) ?[]u8
return std.Io.Dir.readFileAlloc(.cwd(), io, abs_path, gpa, .limited(MAX_OUTPUT)) catch null;
}
/// True when `rel_path` (a repo-relative `.sx` path like `examples/0213-foo.sx`)
/// matches the `-Dname` filter — a comma-separated list of full `.sx` paths.
/// Each entry is whitespace-trimmed and a leading `./` is ignored, so both
/// `examples/0213-foo.sx` and `./examples/0213-foo.sx` match.
fn nameMatchesFilter(filter: []const u8, rel_path: []const u8) bool {
var it = std.mem.splitScalar(u8, filter, ',');
while (it.next()) |raw| {
var entry = std.mem.trim(u8, raw, " \t\r\n");
if (std.mem.startsWith(u8, entry, "./")) entry = entry[2..];
if (entry.len == 0) continue;
if (std.mem.eql(u8, entry, rel_path)) return true;
}
return false;
}
/// Per-example build/run directives, parsed from an optional `<name>.build`
/// JSON sidecar (replaces the old standalone `.aot` marker). Output snapshots
/// (.exit/.stdout/.stderr/.ir) stay separate — they are regenerated data, not
@@ -281,6 +296,14 @@ fn sweepRoot(
_ = work_state.reset(.retain_capacity);
const a = work_state.allocator();
// `-Dname=<paths>` filter: when set, run ONLY the named example(s) (full
// repo-relative `.sx` paths, comma-separated). A non-matching example is
// dropped silently — not counted as ran or skipped.
if (corpus_paths.name.len > 0) {
const this_rel = try std.fmt.allocPrint(a, "{s}/{s}.sx", .{ root_base, name });
if (!nameMatchesFilter(corpus_paths.name, this_rel)) continue;
}
const sx_abs = try std.fs.path.join(a, &.{ root_dir, try std.fmt.allocPrint(a, "{s}.sx", .{name}) });
std.Io.Dir.access(.cwd(), io, sx_abs, .{}) catch { // marker without source
skipped += 1;