test(asm): Phase 0.1 — corpus ir-only branch for cross-target examples
When a `.build` target doesn't match the host, the runner can't execute the example here, so it verifies via `sx ir --target` only: asserts exit + the `.ir` snapshot (stdout) + diagnostics (stderr), never `.stdout`. An `.ir` snapshot is REQUIRED in ir-only mode — its absence is a loud failure, never a silent pass. - corpus_run.test.zig: ir_only flag (target set & !hostMatchesTarget); first dispatch arm runs `sx ir`, sets act_exit/act_err/act_ir; skip stdout in both update and verify modes; require ir_raw. - lock fixture 1639-platform-target-cross (asm-free main, target x86_64-linux, checked-in .ir). Verified: corrupt .ir => IR mismatch; delete .ir => require failure. Test-infra only; no compiler code. zig build test green (647 corpus, 444 unit).
This commit is contained in:
@@ -314,20 +314,36 @@ fn sweepRoot(
|
||||
const is_aot = cfg.aot;
|
||||
|
||||
// An example pinned to a non-host target cannot execute here; it routes
|
||||
// to ir-only mode (Phase 0.1). Until that lands, a mismatch must fail
|
||||
// loudly — never silently pass.
|
||||
if (cfg.target) |t| {
|
||||
if (!hostMatchesTarget(t)) {
|
||||
try failures.append(fail_gpa, try std.fmt.allocPrint(fail_gpa, "{s}: cross-target ir-only mode not yet implemented (target={s}, host={s}-{s})", .{ name, t, @tagName(builtin.cpu.arch), @tagName(builtin.os.tag) }));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// to ir-only mode (verify via `sx ir` only — see the first arm below).
|
||||
const ir_only = if (cfg.target) |t| !hostMatchesTarget(t) else false;
|
||||
|
||||
var act_exit: u32 = undefined;
|
||||
var act_out: []const u8 = undefined;
|
||||
var act_err: []const u8 = undefined;
|
||||
var act_ir: ?[]const u8 = null;
|
||||
|
||||
if (is_aot) {
|
||||
if (ir_only) {
|
||||
// Cross-target: cannot run on this host. Verify via `sx ir` only —
|
||||
// exit code, the IR snapshot (stdout), and diagnostics (stderr). An
|
||||
// .ir snapshot is REQUIRED: without it an arch-pinned example would
|
||||
// assert nothing. Its absence is a loud failure, never a silent pass.
|
||||
if (ir_raw == null) {
|
||||
try failures.append(fail_gpa, try std.fmt.allocPrint(fail_gpa, "{s}: cross-target example (target={s}) needs an .ir snapshot for ir-only mode", .{ name, cfg.target.? }));
|
||||
continue;
|
||||
}
|
||||
const ir_res = std.process.run(a, io, .{
|
||||
.argv = try withTarget(a, &.{ corpus_paths.sx_exe, "ir", rel_path }, cfg.target),
|
||||
.cwd = .{ .path = repo_root },
|
||||
.timeout = deadline(io),
|
||||
}) catch |err| {
|
||||
try failures.append(fail_gpa, try std.fmt.allocPrint(fail_gpa, "{s}: `sx ir` {s}{s}", .{ name, @errorName(err), if (err == error.Timeout) " (>10s)" else "" }));
|
||||
continue;
|
||||
};
|
||||
act_exit = termCode(ir_res.term);
|
||||
act_out = ""; // stdout carries IR (asserted via .ir), not a separate stream
|
||||
act_err = trimNl(try normalizeStd(a, ir_res.stderr));
|
||||
act_ir = trimNl(try normalizeIr(a, ir_res.stdout));
|
||||
} else if (is_aot) {
|
||||
// Build a native executable, then run it. The build's own stderr
|
||||
// ("compiled: <path>") is intentionally discarded — only the built
|
||||
// program's streams are snapshotted. A build failure (e.g. an
|
||||
@@ -383,10 +399,10 @@ fn sweepRoot(
|
||||
act_err = trimNl(try normalizeStd(a, run_res.stderr));
|
||||
}
|
||||
|
||||
// --- sx ir (only when a snapshot already exists; mirrors the shell's
|
||||
// `$has_ir` gate — update mode never CREATES new .ir files) ---
|
||||
var act_ir: ?[]const u8 = null;
|
||||
if (ir_raw != null) {
|
||||
// --- sx ir (execute-mode only; ir-only produced act_ir above). Runs
|
||||
// when a snapshot already exists; mirrors the shell's `$has_ir` gate —
|
||||
// update mode never CREATES new .ir files. ---
|
||||
if (!ir_only and ir_raw != null) {
|
||||
const ir_res = std.process.run(a, io, .{
|
||||
.argv = try withTarget(a, &.{ corpus_paths.sx_exe, "ir", rel_path }, cfg.target),
|
||||
.cwd = .{ .path = repo_root },
|
||||
@@ -404,7 +420,7 @@ fn sweepRoot(
|
||||
// --- update mode: overwrite snapshots with freshly-normalized output ---
|
||||
if (corpus_paths.update_goldens) {
|
||||
try writeGolden(io, a, exp_dir, name, "exit", try std.fmt.allocPrint(a, "{d}", .{act_exit}));
|
||||
try writeGolden(io, a, exp_dir, name, "stdout", act_out);
|
||||
if (!ir_only) try writeGolden(io, a, exp_dir, name, "stdout", act_out);
|
||||
try writeGolden(io, a, exp_dir, name, "stderr", act_err);
|
||||
if (act_ir) |ir| try writeGolden(io, a, exp_dir, name, "ir", ir);
|
||||
updated += 1;
|
||||
@@ -422,7 +438,7 @@ fn sweepRoot(
|
||||
var diag: std.ArrayList(u8) = .empty;
|
||||
if (act_exit != exp_exit)
|
||||
try diag.appendSlice(a, try std.fmt.allocPrint(a, " exit: expected={d} actual={d}\n", .{ exp_exit, act_exit }));
|
||||
if (!std.mem.eql(u8, act_out, exp_out))
|
||||
if (!ir_only and !std.mem.eql(u8, act_out, exp_out))
|
||||
try appendDiff(a, &diag, "stdout", exp_out, act_out);
|
||||
if (!std.mem.eql(u8, act_err, exp_err))
|
||||
try appendDiff(a, &diag, "stderr", exp_err, act_err);
|
||||
|
||||
Reference in New Issue
Block a user