test: group examples into per-category folders
Move examples/*.sx and their expected/ snapshots into per-category subfolders (examples/<category>/...). Folder = leading filename token, with ffi-objc/ffi-jni kept whole; filenames are unchanged. The corpus runner and LSP sweep now discover each category's expected/ dir, while issues/ stays flat. Example 1058's repo-root-relative companion import is made file-relative. Path strings embedded in 164 snapshots were regenerated (path-only changes). Test-layout docs in CLAUDE.md updated.
This commit is contained in:
@@ -333,6 +333,15 @@ fn cleanupApk(a: std.mem.Allocator, io: std.Io, out_abs: []const u8, so_abs: []c
|
||||
/// Run every `<root>/expected/*.exit` test. Appends a formatted diagnostic to
|
||||
/// `failures` (owned by `fail_gpa`) for each mismatch. Returns the number of
|
||||
/// tests actually run (markers whose `.sx` is missing are skipped).
|
||||
/// Sweep a corpus root, discovering every `expected/` directory under it and
|
||||
/// running the markers in each. Two layouts are supported simultaneously:
|
||||
/// * flat: `<root>/expected/<name>.exit` with `<root>/<name>.sx`
|
||||
/// (used by `issues/`)
|
||||
/// * by-category: `<root>/<cat>/expected/<name>.exit` with
|
||||
/// `<root>/<cat>/<name>.sx` (used by `examples/`)
|
||||
/// `rel_prefix` (the source dir relative to repo root, e.g. `examples/basic`)
|
||||
/// is what gets handed to `sx` as the repo-relative `.sx` path, so diagnostics
|
||||
/// and snapshots stay normalized to the on-disk location.
|
||||
fn sweepRoot(
|
||||
fail_gpa: std.mem.Allocator,
|
||||
io: std.Io,
|
||||
@@ -345,11 +354,52 @@ fn sweepRoot(
|
||||
const repo_root = std.fs.path.dirname(root_dir) orelse ".";
|
||||
const root_base = std.fs.path.basename(root_dir); // "examples" | "issues"
|
||||
|
||||
var arena_state = std.heap.ArenaAllocator.init(fail_gpa);
|
||||
defer arena_state.deinit();
|
||||
const a = arena_state.allocator();
|
||||
|
||||
var total: usize = 0;
|
||||
|
||||
// A direct `<root>/expected/` (flat layout, e.g. issues/).
|
||||
if (std.Io.Dir.access(.cwd(), io, try std.fs.path.join(a, &.{ root_dir, "expected" }), .{})) |_| {
|
||||
total += try sweepExpectedDir(fail_gpa, io, repo_root, root_dir, root_base, failures);
|
||||
} else |_| {}
|
||||
|
||||
// Each immediate child dir holding an `expected/` (by-category layout,
|
||||
// e.g. examples/<cat>/). Collect child names first — spawning subprocesses
|
||||
// while iterating the dir handle is asking for trouble.
|
||||
var root = std.Io.Dir.openDirAbsolute(io, root_dir, .{ .iterate = true }) catch return total;
|
||||
defer root.close(io);
|
||||
var child_names: std.ArrayList([]const u8) = .empty;
|
||||
var rit = root.iterate();
|
||||
while (try rit.next(io)) |entry| {
|
||||
if (entry.kind != .directory) continue;
|
||||
if (std.mem.eql(u8, entry.name, "expected")) continue;
|
||||
try child_names.append(a, try a.dupe(u8, entry.name));
|
||||
}
|
||||
for (child_names.items) |child| {
|
||||
const child_dir = try std.fs.path.join(a, &.{ root_dir, child });
|
||||
const child_expected = try std.fs.path.join(a, &.{ child_dir, "expected" });
|
||||
std.Io.Dir.access(.cwd(), io, child_expected, .{}) catch continue;
|
||||
const rel_prefix = try std.fmt.allocPrint(a, "{s}/{s}", .{ root_base, child });
|
||||
total += try sweepExpectedDir(fail_gpa, io, repo_root, child_dir, rel_prefix, failures);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
fn sweepExpectedDir(
|
||||
fail_gpa: std.mem.Allocator,
|
||||
io: std.Io,
|
||||
repo_root: []const u8,
|
||||
source_dir: []const u8,
|
||||
rel_prefix: []const u8,
|
||||
failures: *std.ArrayList([]const u8),
|
||||
) !usize {
|
||||
var name_arena_state = std.heap.ArenaAllocator.init(fail_gpa);
|
||||
defer name_arena_state.deinit();
|
||||
const name_arena = name_arena_state.allocator();
|
||||
|
||||
const expected_dir_path = try std.fs.path.join(name_arena, &.{ root_dir, "expected" });
|
||||
const expected_dir_path = try std.fs.path.join(name_arena, &.{ source_dir, "expected" });
|
||||
var dir = std.Io.Dir.openDirAbsolute(io, expected_dir_path, .{ .iterate = true }) catch return 0;
|
||||
defer dir.close(io);
|
||||
|
||||
@@ -378,11 +428,11 @@ fn sweepRoot(
|
||||
// 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 });
|
||||
const this_rel = try std.fmt.allocPrint(a, "{s}/{s}.sx", .{ rel_prefix, 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}) });
|
||||
const sx_abs = try std.fs.path.join(a, &.{ source_dir, try std.fmt.allocPrint(a, "{s}.sx", .{name}) });
|
||||
std.Io.Dir.access(.cwd(), io, sx_abs, .{}) catch { // marker without source
|
||||
skipped += 1;
|
||||
std.debug.print("[corpus-run] skip {s} (no {s}.sx)\n", .{ name, name });
|
||||
@@ -390,7 +440,7 @@ fn sweepRoot(
|
||||
};
|
||||
ran += 1;
|
||||
|
||||
const rel_path = try std.fmt.allocPrint(a, "{s}/{s}.sx", .{ root_base, name });
|
||||
const rel_path = try std.fmt.allocPrint(a, "{s}/{s}.sx", .{ rel_prefix, name });
|
||||
const exp_dir = expected_dir_path;
|
||||
const exit_raw = readOptional(io, a, try std.fmt.allocPrint(a, "{s}/{s}.exit", .{ exp_dir, name })) orelse "";
|
||||
const out_raw = readOptional(io, a, try std.fmt.allocPrint(a, "{s}/{s}.stdout", .{ exp_dir, name })) orelse "";
|
||||
@@ -646,9 +696,9 @@ fn sweepRoot(
|
||||
try recordIfFailed(fail_gpa, failures, name, diag.items);
|
||||
}
|
||||
if (skipped > 0)
|
||||
std.debug.print("[corpus-run] {s}: {d} marker(s) skipped (no matching .sx)\n", .{ root_base, skipped });
|
||||
std.debug.print("[corpus-run] {s}: {d} marker(s) skipped (no matching .sx)\n", .{ rel_prefix, skipped });
|
||||
if (corpus_paths.update_goldens)
|
||||
std.debug.print("[corpus-run] {s}: {d} snapshot(s) regenerated\n", .{ root_base, updated });
|
||||
std.debug.print("[corpus-run] {s}: {d} snapshot(s) regenerated\n", .{ rel_prefix, updated });
|
||||
return ran;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,38 @@ fn sweepDirectory(alloc: std.mem.Allocator, io: std.Io, dir: []const u8) !usize
|
||||
var store = doc_mod.DocumentStore.init(alloc, io, &lib_paths);
|
||||
store.root_path = std.fs.path.dirname(corpus_paths.examples_dir) orelse "";
|
||||
|
||||
// `examples/` is organized into category subdirs (`examples/<cat>/*.sx`),
|
||||
// while `issues/` is flat (`issues/*.sx`). Sweep the files directly under
|
||||
// `dir` AND those one level down in each category subdir (skipping the
|
||||
// `expected/` snapshot dirs). Companion fixture dirs nested deeper
|
||||
// (`<cat>/<NNNN-...>/lib.sx`) are intentionally not swept — matching the
|
||||
// pre-reorg behavior where imported companions were never analyzed directly.
|
||||
var total = try sweepFilesIn(alloc, io, &store, dir, verbose);
|
||||
|
||||
var d = std.Io.Dir.openDirAbsolute(io, dir, .{ .iterate = true }) catch return total;
|
||||
defer d.close(io);
|
||||
var sub_names: std.ArrayList([]const u8) = .empty;
|
||||
var it = d.iterate();
|
||||
while (try it.next(io)) |entry| {
|
||||
if (entry.kind != .directory) continue;
|
||||
if (std.mem.eql(u8, entry.name, "expected")) continue;
|
||||
try sub_names.append(alloc, try alloc.dupe(u8, entry.name));
|
||||
}
|
||||
for (sub_names.items) |name| {
|
||||
const sub = try std.fs.path.join(alloc, &.{ dir, name });
|
||||
total += sweepFilesIn(alloc, io, &store, sub, verbose) catch 0;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/// Analyze every `.sx` directly under `dir` (non-recursive). Returns the count.
|
||||
fn sweepFilesIn(
|
||||
alloc: std.mem.Allocator,
|
||||
io: std.Io,
|
||||
store: *doc_mod.DocumentStore,
|
||||
dir: []const u8,
|
||||
verbose: bool,
|
||||
) !usize {
|
||||
const files = store.listDirectoryFiles(dir) orelse return error.CorpusDirNotFound;
|
||||
for (files) |path| {
|
||||
if (verbose) std.debug.print("[lsp-sweep] {s}\n", .{path});
|
||||
|
||||
Reference in New Issue
Block a user