ww test: fork-isolated record-and-continue harness (lib/test, both stages)
lib/test/run.ww: fork+wait4 runner; each @test runs in its own child, abort/SEGV/FPE decoded from wait-status, failures recorded and the run continues; exit = fail count. Tests are hermetic: module globals do not persist test-to-test (fresh fork image; sanctioned divergence from harec's shared-process __test_main, no setjmp/signal layer needed). -T synth (both stages) emits a module-global (str,*fn() void) table + return run(table) instead of straight-line calls. Driver twins bundle lib/test under test mode and gain ww test -c/-o (go test -c) so the byte-id gates diff the same artifact the real path builds. Gates 989/910/997 rewired onto it; new 911 pins record-and-continue across all three fault classes; 949 +3 rows. (#17-team commit-2)
This commit is contained in:
@@ -15801,14 +15801,20 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
// untouched: the appended N_FNDECL rides cgfn, byte-id by
|
||||
// construction (rule 10; cstage twin at cmd/wcc/check.c).
|
||||
//
|
||||
// D1-D5 SANCTIONED PLAN-9-LEAN REDUCTION of hare-test (user-ratified,
|
||||
// reinstatable post-CSP; documented departure, not a hare cite):
|
||||
// D1 no __test_array section — straight-line calls; D2 call @test
|
||||
// fns by their real symbols (no testfunc.%d); D3 no per-test
|
||||
// setjmp/onabort isolation — run-to-completion = pass, abort/div0/
|
||||
// SIGSEGV/nonzero = fail and STOPS the run; D4 no sort, no fnmatch
|
||||
// filter — source/collection order; D5 no reflective file:line, no
|
||||
// per-test ok/FAIL line — failure is the nonzero process exit.
|
||||
// #17 RECORD-AND-CONTINUE (rob ruling 2026-06-10; drew-17-attest-spec
|
||||
// §a): instead of straight-line `foo(); bar();` calls (which abort the
|
||||
// whole run on the first failing @test — old D3), synthesize a value
|
||||
// table `[](str, *fn() void) = {("foo", &foo), ...}` + a single call to
|
||||
// the lib/test runner. The runner forks per test and reads the child's
|
||||
// wait-status, so abort/div0/SIGSEGV/nonzero each fail THAT test and the
|
||||
// run proceeds (lib/test/run.ww). Table = harec __test_array reduced to
|
||||
// a value table (D1 no section; D2 real symbols). RETAINED reductions
|
||||
// (user-ratified, reinstatable post-CSP): D4 no sort, no fnmatch filter
|
||||
// (source/collection order; fnmatch is #17 commit-3); D5 no reflective
|
||||
// file:line (the runner prints `name ... ok/FAIL` + a count summary).
|
||||
// Table rides cgen's #117 slice-of-tuple-global path; `run` resolves
|
||||
// bare against the auto-bundled lib/test (synth runs post-pass-1, so
|
||||
// run sits in the same flat "" bucket as the @test fns).
|
||||
if (c.istest != 0) {
|
||||
let pf: str = file.file;
|
||||
let pl: i32 = file.line;
|
||||
@@ -15824,9 +15830,11 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
// (c) collect @test fns in file.list order; build the body chain.
|
||||
let bhead: *node = nil;
|
||||
let btail: *node = nil;
|
||||
// (c) collect @test fns in file.list order; build one table row
|
||||
// `("<name>", &<name>)` per validated @test fn.
|
||||
let rhead: *node = nil;
|
||||
let rtail: *node = nil;
|
||||
let ntest: i32 = 0;
|
||||
let t: *node = file.list;
|
||||
for (t != nil) {
|
||||
if (t.kind == nkind.N_FNDECL) {
|
||||
@@ -15853,28 +15861,77 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
cerr("' must be fn() void\n");
|
||||
c.errs += 1;
|
||||
} else {
|
||||
let call: *node = newnode(nkind.N_CALL, pf, pl, pc);
|
||||
let cid: *node = newnode(nkind.N_IDENT, pf, pl, pc);
|
||||
cid.str = t.str;
|
||||
call.lhs = cid;
|
||||
let es: *node = newnode(nkind.N_EXPRSTMT, pf, pl, pc);
|
||||
es.lhs = call;
|
||||
if (bhead == nil) { bhead = es; }
|
||||
else { btail.next = es; };
|
||||
btail = es;
|
||||
let nm: *node = newnode(nkind.N_STRLIT, pf, pl, pc);
|
||||
nm.str = t.str;
|
||||
let id: *node = newnode(nkind.N_IDENT, pf, pl, pc);
|
||||
id.str = t.str;
|
||||
let amp: *node = newnode(nkind.N_UN, pf, pl, pc);
|
||||
amp.op = tkind.TK_AMP;
|
||||
amp.lhs = id;
|
||||
let row: *node = newnode(nkind.N_TUPLE, pf, pl, pc);
|
||||
row.list = nm;
|
||||
nm.next = amp;
|
||||
if (rhead == nil) { rhead = row; }
|
||||
else { rtail.next = row; };
|
||||
rtail = row;
|
||||
ntest += 1i32;
|
||||
};
|
||||
};
|
||||
};
|
||||
t = t.next;
|
||||
};
|
||||
let ret: *node = newnode(nkind.N_RETURN, pf, pl, pc);
|
||||
let zero: *node = newnode(nkind.N_INTLIT, pf, pl, pc);
|
||||
zero.uval = 0u64;
|
||||
ret.lhs = zero;
|
||||
if (bhead == nil) { bhead = ret; } else { btail.next = ret; };
|
||||
btail = ret;
|
||||
|
||||
let body: *node = newnode(nkind.N_BLOCK, pf, pl, pc);
|
||||
body.list = bhead;
|
||||
let tab: *node = nil;
|
||||
if (ntest == 0i32) {
|
||||
// no @test fns in this unit — exit 0, nothing to run.
|
||||
let ret: *node = newnode(nkind.N_RETURN, pf, pl, pc);
|
||||
let zero: *node = newnode(nkind.N_INTLIT, pf, pl, pc);
|
||||
zero.uval = 0u64;
|
||||
ret.lhs = zero;
|
||||
body.list = ret;
|
||||
} else {
|
||||
// const __wwtests: [](str, *fn() void) = [rows...];
|
||||
// wwstage tuple-type elements wrap in N_TPARAM (parse.ww:308).
|
||||
let e0: *node = newnode(nkind.N_TNAME, pf, pl, pc);
|
||||
e0.str = "str";
|
||||
let p0: *node = newnode(nkind.N_TPARAM, pf, pl, pc);
|
||||
p0.lhs = e0;
|
||||
let vret: *node = newnode(nkind.N_TNAME, pf, pl, pc);
|
||||
vret.str = "void";
|
||||
let vfn: *node = newnode(nkind.N_TFN, pf, pl, pc);
|
||||
vfn.lhs = vret;
|
||||
let e1: *node = newnode(nkind.N_TPTR, pf, pl, pc);
|
||||
e1.lhs = vfn;
|
||||
let p1: *node = newnode(nkind.N_TPARAM, pf, pl, pc);
|
||||
p1.lhs = e1;
|
||||
let tup: *node = newnode(nkind.N_TTUPLE, pf, pl, pc);
|
||||
tup.list = p0;
|
||||
p0.next = p1;
|
||||
let tsl: *node = newnode(nkind.N_TSLICE, pf, pl, pc);
|
||||
tsl.lhs = tup;
|
||||
let arr: *node = newnode(nkind.N_ARRLIT, pf, pl, pc);
|
||||
arr.list = rhead;
|
||||
tab = newnode(nkind.N_LET, pf, pl, pc);
|
||||
tab.op = tkind.TK_CONST;
|
||||
tab.str = "__wwtests";
|
||||
tab.lhs = tsl;
|
||||
tab.rhs = arr;
|
||||
// pass 1 already ran; install the table name now so main
|
||||
// resolves it (declmod returns "" for tab.nmod="").
|
||||
installdecl(c, file, tab);
|
||||
|
||||
let arg: *node = newnode(nkind.N_IDENT, pf, pl, pc);
|
||||
arg.str = "__wwtests";
|
||||
let call: *node = newnode(nkind.N_CALL, pf, pl, pc);
|
||||
let cid: *node = newnode(nkind.N_IDENT, pf, pl, pc);
|
||||
cid.str = "run";
|
||||
call.lhs = cid;
|
||||
call.list = arg;
|
||||
let ret: *node = newnode(nkind.N_RETURN, pf, pl, pc);
|
||||
ret.lhs = call;
|
||||
body.list = ret;
|
||||
};
|
||||
let m: *node = newnode(nkind.N_FNDECL, pf, pl, pc);
|
||||
m.str = "main";
|
||||
m.exported = 1i32;
|
||||
@@ -15882,16 +15939,18 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
rety.str = "i32";
|
||||
m.lhs = rety;
|
||||
m.body = body;
|
||||
// Append to file.list tail. No type_ pre-set: installdecl
|
||||
// never stamps a fn's type_ on the ww side either — cgfn reads
|
||||
// lhs/list on demand, like every parsed fn. Pass-2 below
|
||||
// resolve-walks the appended body.
|
||||
// Append the table const (if any) then main to file.list tail. No
|
||||
// type_ pre-set on main: installdecl never stamps a fn's type_ on
|
||||
// the ww side — cgfn reads lhs/list on demand. Pass-2 below
|
||||
// resolve-walks the appended bodies.
|
||||
let tl: *node = file.list;
|
||||
if (tl == nil) {
|
||||
file.list = m;
|
||||
if (tab != nil) { file.list = tab; tab.next = m; }
|
||||
else { file.list = m; };
|
||||
} else {
|
||||
for (tl.next != nil) { tl = tl.next; };
|
||||
tl.next = m;
|
||||
if (tab != nil) { tl.next = tab; tab.next = m; }
|
||||
else { tl.next = m; };
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user