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:
17
CLAUDE.md
17
CLAUDE.md
@@ -492,6 +492,23 @@ Safe workflow:
|
||||
2. Only run `zig build test -Dupdate-goldens` when you've intentionally changed output (new feature, new test, changed formatting).
|
||||
3. After regenerating, review the diff (`git diff examples/expected/ issues/expected/`) to confirm no error messages or empty output were captured.
|
||||
|
||||
**Scope a regen to specific examples with `-Dname`.** A *full* `-Dupdate-goldens`
|
||||
re-runs and rewrites all ~690 snapshots, so a single flaky/host-divergent example
|
||||
(AOT links, cross-arch `target` examples, anything that intermittently fails) can
|
||||
silently clobber a good snapshot. To capture just the example(s) you added, pass
|
||||
their full repo-relative `.sx` path(s), comma-separated — this rewrites ONLY those
|
||||
and touches nothing else:
|
||||
|
||||
```sh
|
||||
zig build test -Dname=examples/0625-comptime-weld-struct-field.sx -Dupdate-goldens
|
||||
zig build test -Dname=examples/0625-foo.sx,examples/0626-bar.sx # verify just these
|
||||
```
|
||||
|
||||
`-Dname` also busts the test-run cache (the corpus enumerates `.sx`/`expected/`
|
||||
files at RUNTIME, so editing a snapshot alone does NOT force a re-run — a plain
|
||||
`zig build test` may be served a cached result). Changing `-Dname` — or any
|
||||
compiler source — forces a fresh run.
|
||||
|
||||
### Adding a new language feature
|
||||
|
||||
There is no monolithic smoke file — each feature is its own focused example.
|
||||
|
||||
14
build.zig
14
build.zig
@@ -218,6 +218,20 @@ pub fn build(b: *std.Build) void {
|
||||
"Regenerate example/issue snapshots instead of verifying them (use with `zig build test`)",
|
||||
) orelse false;
|
||||
corpus_opts.addOption(bool, "update_goldens", update_goldens);
|
||||
// `zig build test -Dname=examples/0213-foo.sx[,examples/0214-bar.sx]` restricts
|
||||
// the corpus runner to ONLY the named example(s) — full repo-relative `.sx`
|
||||
// paths, comma-separated. Empty = run every example. Use it to verify or
|
||||
// regenerate (-Dupdate-goldens) a specific example without re-running (or
|
||||
// clobbering the snapshots of) the rest of the corpus. Because the value is
|
||||
// baked into the corpus options module, changing it also busts the cached
|
||||
// test-run result (the runner enumerates .sx/expected files at RUNTIME, so a
|
||||
// bare snapshot edit alone would otherwise be served from cache).
|
||||
const name_filter = b.option(
|
||||
[]const u8,
|
||||
"name",
|
||||
"Run only the named example(s): comma-separated repo-relative .sx paths (e.g. examples/0213-foo.sx)",
|
||||
) orelse "";
|
||||
corpus_opts.addOption([]const u8, "name", name_filter);
|
||||
mod.addOptions("corpus_paths", corpus_opts);
|
||||
|
||||
const mod_tests = b.addTest(.{
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user