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:
2026-06-11 00:08:39 +09:00
parent 08a76cf4c8
commit 16c83e70d3
15 changed files with 961 additions and 194 deletions

View File

@@ -3724,6 +3724,19 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, objstem: *u8, inc
c.out = cf;
c.dirs = searchpath.ptr;
c.visit = nil;
// #17 auto-bundle lib/test: the -T synth's main calls lib/test's
// run(), but @test files don't `import test;`. Pull it like an
// implicit import through the same locate+expand path (the visit
// set dedupes a fixture that imports it explicitly). cstage twin
// in cmd/ww/main.c buildone.
if (istest != 0) {
let td: i32 = 0;
let tp: *u8 = locateimport(searchpath.ptr, "test".ptr, "test".len: u64, &td);
if (tp != nil) {
if (td != 0) { expanddir(&c, tp); }
else { expand(&c, tp); };
};
};
if (entryisdir != 0) { expanddir(&c, srcd.ptr); }
else { expand(&c, src); };
};
@@ -4267,20 +4280,31 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
// directory: open the dir, getdents64, build+run each *_test.ww,
// report ok/FAIL per file, return 0 iff all pass.
fn runsingletest(selfdir: *u8, src: *u8, incs: *u8) i32 = {
fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *u8) i32 = {
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
tmp.len = os.PATH_MAX;
makeruntmp(tmp.ptr);
if (buildone(selfdir, src, 0, tmp.ptr, nil, incs, nil, 1i32) != 0) {
os.remove(pathstr(tmp.ptr));
// -o redirects the binary + its combined (objstem, T3) to <stem>; the
// default temp keeps the combined next to the source as before.
let outp: *u8 = nil;
let objstem: *u8 = nil;
if (outstem != nil) {
outp = outstem;
objstem = outstem;
} else {
makeruntmp(tmp.ptr);
outp = tmp.ptr;
};
if (buildone(selfdir, src, 0, outp, objstem, incs, nil, 1i32) != 0) {
if (outstem == nil) { os.remove(pathstr(outp)); };
return 1;
};
if (compileonly != 0) { return 0; };
let execargv: []*u8 = alloc([], 2u64)!;
execargv.len = 2;
execargv[0] = tmp.ptr;
execargv[0] = outp;
execargv[1] = nil;
let rc: i32 = procrun(tmp.ptr, execargv.ptr);
os.remove(pathstr(tmp.ptr));
let rc: i32 = procrun(outp, execargv.ptr);
if (outstem == nil) { os.remove(pathstr(outp)); };
return rc;
};
@@ -4371,6 +4395,11 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let incoff: u64 = 0u64;
cstrseal(incs.ptr, 0u64);
// -c (compile-only) + -o <stem>: build the test binary + its lib/test-
// inclusive combined WITHOUT running it, for the byte-id gates. cstage
// twin: cmd/ww/main.c do_test (error wording identical).
let compileonly: i32 = 0;
let outstem: *u8 = nil;
let i: i32 = start;
for (i < argc) {
let p: *u8 = argv[i];
@@ -4393,10 +4422,23 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
};
incoff = cstrinto(incs.ptr, incoff, dir);
cstrseal(incs.ptr, incoff);
} else { if (p[1u64] == 99u8 && p[2u64] == 0u8) { // "-c"
compileonly = 1;
} else { if (p[1u64] == 111u8) { // '-o'
if (p[2u64] != 0u8) {
outstem = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww test: -o needs an argument\n");
return 2;
};
i += 1;
outstem = argv[i];
};
} else {
cerr("ww test: unknown flag\n");
return 2;
};
}; }; };
} else {
if (target == nil) { target = p; };
};
@@ -4410,10 +4452,14 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
// single-file mode: literal *.ww that exists
if (cstrendswithlit(target, ".ww")) {
if (os.access(pathstr(target), 0i32) == 0) {
return runsingletest(selfdir, target, incs.ptr);
return runsingletest(selfdir, target, incs.ptr, compileonly, outstem);
};
};
if (compileonly != 0 || outstem != nil) {
cerr("ww test: -c/-o need a single test file\n");
return 2;
};
// otherwise treat target as a directory; enumerate *_test.ww
return rundirtests(selfdir, target);
};

View File

@@ -938,6 +938,19 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, objstem: *u8, inc
c.out = cf;
c.dirs = searchpath.ptr;
c.visit = nil;
// #17 auto-bundle lib/test: the -T synth's main calls lib/test's
// run(), but @test files don't `import test;`. Pull it like an
// implicit import through the same locate+expand path (the visit
// set dedupes a fixture that imports it explicitly). cstage twin
// in cmd/ww/main.c buildone.
if (istest != 0) {
let td: i32 = 0;
let tp: *u8 = locateimport(searchpath.ptr, "test".ptr, "test".len: u64, &td);
if (tp != nil) {
if (td != 0) { expanddir(&c, tp); }
else { expand(&c, tp); };
};
};
if (entryisdir != 0) { expanddir(&c, srcd.ptr); }
else { expand(&c, src); };
};
@@ -1481,20 +1494,31 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
// directory: open the dir, getdents64, build+run each *_test.ww,
// report ok/FAIL per file, return 0 iff all pass.
fn runsingletest(selfdir: *u8, src: *u8, incs: *u8) i32 = {
fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *u8) i32 = {
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
tmp.len = os.PATH_MAX;
makeruntmp(tmp.ptr);
if (buildone(selfdir, src, 0, tmp.ptr, nil, incs, nil, 1i32) != 0) {
os.remove(pathstr(tmp.ptr));
// -o redirects the binary + its combined (objstem, T3) to <stem>; the
// default temp keeps the combined next to the source as before.
let outp: *u8 = nil;
let objstem: *u8 = nil;
if (outstem != nil) {
outp = outstem;
objstem = outstem;
} else {
makeruntmp(tmp.ptr);
outp = tmp.ptr;
};
if (buildone(selfdir, src, 0, outp, objstem, incs, nil, 1i32) != 0) {
if (outstem == nil) { os.remove(pathstr(outp)); };
return 1;
};
if (compileonly != 0) { return 0; };
let execargv: []*u8 = alloc([], 2u64)!;
execargv.len = 2;
execargv[0] = tmp.ptr;
execargv[0] = outp;
execargv[1] = nil;
let rc: i32 = procrun(tmp.ptr, execargv.ptr);
os.remove(pathstr(tmp.ptr));
let rc: i32 = procrun(outp, execargv.ptr);
if (outstem == nil) { os.remove(pathstr(outp)); };
return rc;
};
@@ -1585,6 +1609,11 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let incoff: u64 = 0u64;
cstrseal(incs.ptr, 0u64);
// -c (compile-only) + -o <stem>: build the test binary + its lib/test-
// inclusive combined WITHOUT running it, for the byte-id gates. cstage
// twin: cmd/ww/main.c do_test (error wording identical).
let compileonly: i32 = 0;
let outstem: *u8 = nil;
let i: i32 = start;
for (i < argc) {
let p: *u8 = argv[i];
@@ -1607,10 +1636,23 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
};
incoff = cstrinto(incs.ptr, incoff, dir);
cstrseal(incs.ptr, incoff);
} else { if (p[1u64] == 99u8 && p[2u64] == 0u8) { // "-c"
compileonly = 1;
} else { if (p[1u64] == 111u8) { // '-o'
if (p[2u64] != 0u8) {
outstem = p + 2u64;
} else {
if (i + 1 >= argc) {
cerr("ww test: -o needs an argument\n");
return 2;
};
i += 1;
outstem = argv[i];
};
} else {
cerr("ww test: unknown flag\n");
return 2;
};
}; }; };
} else {
if (target == nil) { target = p; };
};
@@ -1624,10 +1666,14 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
// single-file mode: literal *.ww that exists
if (cstrendswithlit(target, ".ww")) {
if (os.access(pathstr(target), 0i32) == 0) {
return runsingletest(selfdir, target, incs.ptr);
return runsingletest(selfdir, target, incs.ptr, compileonly, outstem);
};
};
if (compileonly != 0 || outstem != nil) {
cerr("ww test: -c/-o need a single test file\n");
return 2;
};
// otherwise treat target as a directory; enumerate *_test.ww
return rundirtests(selfdir, target);
};