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

@@ -470,6 +470,19 @@ build_one(const char *src, int entry_is_dir, const char *out,
return 1;
}
struct ImportSet visited = {0};
/* #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 filename-keyed expand path
* (the visited set dedupes if a fixture imports it explicitly).
* The wwstage twin (selfhost/cmd/ww/main.ww) mirrors this. */
if (is_test) {
char tpath[1024];
int tdir = 0;
if (locate_import(srcdir, "test", tpath, sizeof tpath, &tdir)) {
if (tdir) expand_dir(cf, tpath, &visited, srcdir);
else expand(cf, tpath, &visited, srcdir);
}
}
if (entry_is_dir) expand_dir(cf, srcd, &visited, srcdir);
else expand(cf, src, &visited, srcdir);
fclose(cf);
@@ -792,9 +805,16 @@ do_test(int argc, char **argv)
{
const char *src = NULL;
char incs[2048] = {0};
/* test builds to a temp and runs it: -l/-L/-o carry no meaning here, so
* reject them (and any unknown flag) rather than silently swallow —
* byte-identical to the wwstage twin (selfhost/cmd/ww/main.ww dotest). */
/* -c (compile-only, Go's `go test -c`) + -o <stem> build the test
* binary (and its lib/test-inclusive combined, via build_one's
* is_test auto-bundle + the T3 objstem redirect) WITHOUT running it —
* the byte-id gates feed <stem>.combined.ww to raw w6c -T / w6c_ww -T.
* -T stays internal to w6c; the driver never sees it. -l/-L carry no
* meaning for a test build, so they (and any unknown flag) are rejected
* rather than silently swallowed — byte-identical wording to the
* wwstage twin (selfhost/cmd/ww/main.ww dotest). */
int compileonly = 0;
char outstem[1024] = {0};
for (int i = 0; i < argc; i++) {
if (argv[i][0] == '-') {
if (argv[i][1] == 'I') {
@@ -812,6 +832,17 @@ do_test(int argc, char **argv)
size_t n = strlen(incs);
snprintf(incs + n, sizeof incs - n,
"%s%s", n ? ":" : "", dir);
} else if (strcmp(argv[i], "-c") == 0) {
compileonly = 1;
} else if (strcmp(argv[i], "-o") == 0) {
if (i + 1 >= argc) {
fprintf(stderr,
"ww test: -o needs an argument\n");
return 2;
}
snprintf(outstem, sizeof outstem, "%s", argv[++i]);
} else if (argv[i][1] == 'o' && argv[i][2]) {
snprintf(outstem, sizeof outstem, "%s", argv[i] + 2);
} else {
fprintf(stderr, "ww test: unknown flag\n");
return 2;
@@ -833,25 +864,37 @@ do_test(int argc, char **argv)
return 1;
}
char tmp[1024];
snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid());
if (build_one(resolved, is_dir, tmp, NULL, incs, "", "", 1) != 0) return 1;
int rc = run(tmp);
unlink(tmp);
const char *outp;
if (outstem[0]) outp = outstem;
else { snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid()); outp = tmp; }
if (build_one(resolved, is_dir, outp, outstem[0] ? outstem : NULL,
incs, "", "", 1) != 0) return 1;
if (compileonly) return 0;
int rc = run(outp);
if (!outstem[0]) unlink(outp);
return rc;
}
if (S_ISREG(st.st_mode)) {
/* single .ww file — build+run it. */
/* single .ww file — build, then run unless -c (compile-only). */
char tmp[1024];
snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid());
if (build_one(target, 0, tmp, NULL, incs, "", "", 1) != 0) return 1;
int rc = run(tmp);
unlink(tmp);
const char *outp;
if (outstem[0]) outp = outstem;
else { snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid()); outp = tmp; }
if (build_one(target, 0, outp, outstem[0] ? outstem : NULL,
incs, "", "", 1) != 0) return 1;
if (compileonly) return 0;
int rc = run(outp);
if (!outstem[0]) unlink(outp);
return rc;
}
if (!S_ISDIR(st.st_mode)) {
fprintf(stderr, "ww test: %s is neither file nor directory\n", target);
return 1;
}
if (compileonly || outstem[0]) {
fprintf(stderr, "ww test: -c/-o need a single test file\n");
return 2;
}
/* directory — run every *_test.ww inside. */
char **files = NULL;
int n = 0;