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:
5
Makefile
5
Makefile
@@ -469,6 +469,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
|||||||
$(BIN)/test_selfhost $(BIN)/test_w6a_ww $(BIN)/test_w6l_ww \
|
$(BIN)/test_selfhost $(BIN)/test_w6a_ww $(BIN)/test_w6l_ww \
|
||||||
$(BIN)/test_w6c_ww $(BIN)/test_ww_ww $(BIN)/test_self_rebuild \
|
$(BIN)/test_w6c_ww $(BIN)/test_ww_ww $(BIN)/test_self_rebuild \
|
||||||
$(BIN)/test_dyn_ww $(BIN)/test_selfcheck $(BIN)/test_at_test_ww \
|
$(BIN)/test_dyn_ww $(BIN)/test_selfcheck $(BIN)/test_at_test_ww \
|
||||||
|
$(BIN)/test_attest_record \
|
||||||
$(BIN)/test_fmt_run $(BIN)/test_log_run $(BIN)/test_fnmatch_run \
|
$(BIN)/test_fmt_run $(BIN)/test_log_run $(BIN)/test_fnmatch_run \
|
||||||
$(BIN)/test_shlex_run $(BIN)/test_getenv_run $(BIN)/test_dirs_run \
|
$(BIN)/test_shlex_run $(BIN)/test_getenv_run $(BIN)/test_dirs_run \
|
||||||
$(BIN)/test_stat_run $(BIN)/test_time_run \
|
$(BIN)/test_stat_run $(BIN)/test_time_run \
|
||||||
@@ -2156,6 +2157,10 @@ $(BIN)/test_at_test_ww: test/wcc/997_at_test_ww.c $(BIN)/ww_ww $(BIN)/w6c_ww \
|
|||||||
$(BIN)/w6a_ww $(BIN)/w6l_ww $(BIN)/w6c $(LIB)/libwwrt.a | $(BIN)
|
$(BIN)/w6a_ww $(BIN)/w6l_ww $(BIN)/w6c $(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
$(BIN)/test_attest_record: test/wcc/911_attest_record.c $(BIN)/ww $(BIN)/w6c \
|
||||||
|
$(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
$(BIN)/test_fmt_run: test/wcc/970_fmt_run.c $(BIN)/ww $(BIN)/w6c \
|
$(BIN)/test_fmt_run: test/wcc/970_fmt_run.c $(BIN)/ww $(BIN)/w6c \
|
||||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|||||||
118
cmd/wcc/check.c
118
cmd/wcc/check.c
@@ -2974,14 +2974,22 @@ check_file(Checker *c, Node *file)
|
|||||||
* untouched: the appended N_FNDECL rides the existing cgfn path, so
|
* untouched: the appended N_FNDECL rides the existing cgfn path, so
|
||||||
* byte-id holds at the gated choke point by construction.
|
* byte-id holds at the gated choke point by construction.
|
||||||
*
|
*
|
||||||
* D1-D5 SANCTIONED PLAN-9-LEAN REDUCTION of hare-test (user-ratified,
|
* #17 RECORD-AND-CONTINUE (rob ruling 2026-06-10; drew-17-attest-spec
|
||||||
* reinstatable post-CSP; this is a documented departure, not a hare
|
* §a): instead of straight-line `foo(); bar();` calls (which abort the
|
||||||
* cite): D1 no __test_array linker section — straight-line calls;
|
* whole run on the first failing @test — the old D3), synthesize a value
|
||||||
* D2 call @test fns by their real symbols (no testfunc.%d rename);
|
* table `[](str, *fn() void) = {("foo", &foo), ...}` and a single call
|
||||||
* D3 no per-test setjmp/onabort isolation — run-to-completion = pass,
|
* to the lib/test runner. The runner forks per test and reads the
|
||||||
* abort/div0/SIGSEGV/nonzero = fail and STOPS the run; D4 no sort,
|
* child's wait-status, so abort/div0/SIGSEGV/nonzero each fail THAT test
|
||||||
* no fnmatch filter — source/collection order; D5 no reflective
|
* and the run proceeds (lib/test/run.ww). The table is the harec
|
||||||
* file:line, no per-test ok/FAIL line — failure is the nonzero exit.
|
* __test_array reduced to an in-source value table (D1: no linker
|
||||||
|
* section; D2: real symbols, no testfunc.%d rename). 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).
|
||||||
|
* The table rides cgen's #117 slice-of-tuple-global DATA path; the
|
||||||
|
* `run` callee resolves bare against the auto-bundled lib/test (the
|
||||||
|
* synth runs post-pass-1, so lib/test's `run` sits in the same flat ""
|
||||||
|
* bucket as the @test fns — bare, like the @test calls themselves).
|
||||||
*/
|
*/
|
||||||
if (c->is_test) {
|
if (c->is_test) {
|
||||||
Pos fp = file->pos;
|
Pos fp = file->pos;
|
||||||
@@ -2991,8 +2999,10 @@ check_file(Checker *c, Node *file)
|
|||||||
&& strcmp(d->str, "main") == 0 && d->body != NULL)
|
&& strcmp(d->str, "main") == 0 && d->body != NULL)
|
||||||
err(c, d->pos, "test mode: main is synthesized "
|
err(c, d->pos, "test mode: main is synthesized "
|
||||||
"by -T; remove the explicit main");
|
"by -T; remove the explicit main");
|
||||||
/* (c) collect @test fns in file->list order, build the body. */
|
/* (c) collect @test fns in file->list order; build one table row
|
||||||
Node *bhead = NULL, *btail = NULL;
|
* `("<name>", &<name>)` per validated @test fn. */
|
||||||
|
Node *rhead = NULL, *rtail = NULL;
|
||||||
|
int ntest = 0;
|
||||||
for (Node *d = file->list; d; d = d->next) {
|
for (Node *d = file->list; d; d = d->next) {
|
||||||
if (d->kind != N_FNDECL)
|
if (d->kind != N_FNDECL)
|
||||||
continue;
|
continue;
|
||||||
@@ -3015,22 +3025,72 @@ check_file(Checker *c, Node *file)
|
|||||||
d->str);
|
d->str);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
Node *nm = newnode(c->a, N_STRLIT, fp);
|
||||||
|
nm->str = d->str;
|
||||||
|
nm->strlen = strlen(d->str);
|
||||||
|
Node *id = newnode(c->a, N_IDENT, fp);
|
||||||
|
id->str = d->str;
|
||||||
|
Node *amp = newnode(c->a, N_UN, fp);
|
||||||
|
amp->op = TK_AMP;
|
||||||
|
amp->lhs = id;
|
||||||
|
Node *row = newnode(c->a, N_TUPLE, fp);
|
||||||
|
row->list = nm;
|
||||||
|
nm->next = amp;
|
||||||
|
if (rhead == NULL) rhead = row;
|
||||||
|
else rtail->next = row;
|
||||||
|
rtail = row;
|
||||||
|
ntest++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node *body = newnode(c->a, N_BLOCK, fp);
|
||||||
|
Node *tab = NULL;
|
||||||
|
if (ntest == 0) {
|
||||||
|
/* no @test fns in this unit — exit 0, nothing to run. */
|
||||||
|
Node *ret = newnode(c->a, N_RETURN, fp);
|
||||||
|
ret->lhs = newnode(c->a, N_INTLIT, fp);
|
||||||
|
ret->lhs->uval = 0;
|
||||||
|
body->list = ret;
|
||||||
|
} else {
|
||||||
|
/* const __wwtests: [](str, *fn() void) = [rows...];
|
||||||
|
* cstage tuple-type elements chain raw via ->next (no
|
||||||
|
* N_TPARAM wrap; parse.c:341). */
|
||||||
|
Node *e0 = newnode(c->a, N_TNAME, fp);
|
||||||
|
e0->str = "str"; e0->strlen = 3;
|
||||||
|
Node *vfn = newnode(c->a, N_TFN, fp);
|
||||||
|
vfn->lhs = newnode(c->a, N_TNAME, fp);
|
||||||
|
vfn->lhs->str = "void"; vfn->lhs->strlen = 4;
|
||||||
|
Node *e1 = newnode(c->a, N_TPTR, fp);
|
||||||
|
e1->lhs = vfn;
|
||||||
|
Node *tup = newnode(c->a, N_TTUPLE, fp);
|
||||||
|
tup->list = e0; e0->next = e1;
|
||||||
|
Node *tsl = newnode(c->a, N_TSLICE, fp);
|
||||||
|
tsl->lhs = tup;
|
||||||
|
Node *arr = newnode(c->a, N_ARRLIT, fp);
|
||||||
|
arr->list = rhead;
|
||||||
|
tab = newnode(c->a, N_LET, fp);
|
||||||
|
tab->op = TK_CONST;
|
||||||
|
tab->str = "__wwtests";
|
||||||
|
tab->lhs = tsl;
|
||||||
|
tab->rhs = arr;
|
||||||
|
/* pass 1 already ran, so install the table's name now —
|
||||||
|
* pass 2 (below) cexprs its rhs and main references it. */
|
||||||
|
Type *tt = resolve_type(c, tsl);
|
||||||
|
tab->type = tt;
|
||||||
|
scope_define_in_module(c->cur, tab->str, NULL, SK_VAR,
|
||||||
|
tt, tab);
|
||||||
|
|
||||||
|
/* return run(__wwtests); */
|
||||||
|
Node *arg = newnode(c->a, N_IDENT, fp);
|
||||||
|
arg->str = "__wwtests";
|
||||||
Node *call = newnode(c->a, N_CALL, fp);
|
Node *call = newnode(c->a, N_CALL, fp);
|
||||||
call->lhs = newnode(c->a, N_IDENT, fp);
|
call->lhs = newnode(c->a, N_IDENT, fp);
|
||||||
call->lhs->str = d->str;
|
call->lhs->str = "run";
|
||||||
Node *es = newnode(c->a, N_EXPRSTMT, fp);
|
call->list = arg;
|
||||||
es->lhs = call;
|
Node *ret = newnode(c->a, N_RETURN, fp);
|
||||||
if (bhead == NULL) bhead = es;
|
ret->lhs = call;
|
||||||
else btail->next = es;
|
body->list = ret;
|
||||||
btail = es;
|
|
||||||
}
|
}
|
||||||
Node *ret = newnode(c->a, N_RETURN, fp);
|
|
||||||
ret->lhs = newnode(c->a, N_INTLIT, fp);
|
|
||||||
ret->lhs->uval = 0;
|
|
||||||
if (bhead == NULL) bhead = ret;
|
|
||||||
else btail->next = ret;
|
|
||||||
Node *body = newnode(c->a, N_BLOCK, fp);
|
|
||||||
body->list = bhead;
|
|
||||||
Node *m = newnode(c->a, N_FNDECL, fp);
|
Node *m = newnode(c->a, N_FNDECL, fp);
|
||||||
m->str = "main";
|
m->str = "main";
|
||||||
m->export = 1;
|
m->export = 1;
|
||||||
@@ -3039,12 +3099,16 @@ check_file(Checker *c, Node *file)
|
|||||||
m->body = body;
|
m->body = body;
|
||||||
m->type = build_fn_type(c, m);
|
m->type = build_fn_type(c, m);
|
||||||
/* pass 1 already ran, so the install loop never stamped m's
|
/* pass 1 already ran, so the install loop never stamped m's
|
||||||
* type; set it explicitly (pass 2 below reads d->type). */
|
* type; set it explicitly (pass 2 below reads d->type).
|
||||||
|
* Append the table const (if any) then main to file->list. */
|
||||||
Node *tl = file->list;
|
Node *tl = file->list;
|
||||||
if (tl == NULL) file->list = m;
|
if (tl == NULL) {
|
||||||
else {
|
file->list = tab ? tab : m;
|
||||||
|
if (tab) tab->next = m;
|
||||||
|
} else {
|
||||||
while (tl->next) tl = tl->next;
|
while (tl->next) tl = tl->next;
|
||||||
tl->next = m;
|
if (tab) { tl->next = tab; tab->next = m; }
|
||||||
|
else tl->next = m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -470,6 +470,19 @@ build_one(const char *src, int entry_is_dir, const char *out,
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
struct ImportSet visited = {0};
|
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);
|
if (entry_is_dir) expand_dir(cf, srcd, &visited, srcdir);
|
||||||
else expand(cf, src, &visited, srcdir);
|
else expand(cf, src, &visited, srcdir);
|
||||||
fclose(cf);
|
fclose(cf);
|
||||||
@@ -792,9 +805,16 @@ do_test(int argc, char **argv)
|
|||||||
{
|
{
|
||||||
const char *src = NULL;
|
const char *src = NULL;
|
||||||
char incs[2048] = {0};
|
char incs[2048] = {0};
|
||||||
/* test builds to a temp and runs it: -l/-L/-o carry no meaning here, so
|
/* -c (compile-only, Go's `go test -c`) + -o <stem> build the test
|
||||||
* reject them (and any unknown flag) rather than silently swallow —
|
* binary (and its lib/test-inclusive combined, via build_one's
|
||||||
* byte-identical to the wwstage twin (selfhost/cmd/ww/main.ww dotest). */
|
* 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++) {
|
for (int i = 0; i < argc; i++) {
|
||||||
if (argv[i][0] == '-') {
|
if (argv[i][0] == '-') {
|
||||||
if (argv[i][1] == 'I') {
|
if (argv[i][1] == 'I') {
|
||||||
@@ -812,6 +832,17 @@ do_test(int argc, char **argv)
|
|||||||
size_t n = strlen(incs);
|
size_t n = strlen(incs);
|
||||||
snprintf(incs + n, sizeof incs - n,
|
snprintf(incs + n, sizeof incs - n,
|
||||||
"%s%s", n ? ":" : "", dir);
|
"%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 {
|
} else {
|
||||||
fprintf(stderr, "ww test: unknown flag\n");
|
fprintf(stderr, "ww test: unknown flag\n");
|
||||||
return 2;
|
return 2;
|
||||||
@@ -833,25 +864,37 @@ do_test(int argc, char **argv)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
char tmp[1024];
|
char tmp[1024];
|
||||||
snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid());
|
const char *outp;
|
||||||
if (build_one(resolved, is_dir, tmp, NULL, incs, "", "", 1) != 0) return 1;
|
if (outstem[0]) outp = outstem;
|
||||||
int rc = run(tmp);
|
else { snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid()); outp = tmp; }
|
||||||
unlink(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;
|
return rc;
|
||||||
}
|
}
|
||||||
if (S_ISREG(st.st_mode)) {
|
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];
|
char tmp[1024];
|
||||||
snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid());
|
const char *outp;
|
||||||
if (build_one(target, 0, tmp, NULL, incs, "", "", 1) != 0) return 1;
|
if (outstem[0]) outp = outstem;
|
||||||
int rc = run(tmp);
|
else { snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid()); outp = tmp; }
|
||||||
unlink(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;
|
return rc;
|
||||||
}
|
}
|
||||||
if (!S_ISDIR(st.st_mode)) {
|
if (!S_ISDIR(st.st_mode)) {
|
||||||
fprintf(stderr, "ww test: %s is neither file nor directory\n", target);
|
fprintf(stderr, "ww test: %s is neither file nor directory\n", target);
|
||||||
return 1;
|
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. */
|
/* directory — run every *_test.ww inside. */
|
||||||
char **files = NULL;
|
char **files = NULL;
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|||||||
108
lib/test/run.ww
Normal file
108
lib/test/run.ww
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package test;
|
||||||
|
|
||||||
|
// Plan-9-lean port of Hare's @test runner (ref/hare/test/+test.ha
|
||||||
|
// __test_main:97-115, run_test:284, do_test:250). MECHANISM DIVERGENCE
|
||||||
|
// (drew-17-attest-spec.md §a; rob ruling 2026-06-10, task #17): Hare
|
||||||
|
// isolates each test in ONE process via arch::setjmp + an rt::onabort
|
||||||
|
// hook + a SIGSEGV signal handler; ww has none of those primitives, so
|
||||||
|
// we fork per test and read the child's wait-status. A clean child
|
||||||
|
// exit(0) is a pass; abort/div0/SIGSEGV/nonzero-exit all surface as a
|
||||||
|
// failing child status, so the fork boundary catches EVERY fault class
|
||||||
|
// the setjmp+signal path catches, at a process boundary. The proven
|
||||||
|
// fork+wait4+WEXITSTATUS decode is procrun (selfhost/cmd/ww/main.ww:
|
||||||
|
// 154-177). CONSEQUENCE (sanctioned): module globals do NOT persist
|
||||||
|
// test-to-test — each test runs in a fresh fork snapshot. Hare's tests
|
||||||
|
// share one process, so its globals persist; ww's are hermetic.
|
||||||
|
//
|
||||||
|
// D1/D4/D5 reductions also retained from the -T synth era: no
|
||||||
|
// __test_array linker section (the synth hands us a value table), no
|
||||||
|
// sort, no file:line reflection. fnmatch name-filtering is task #17
|
||||||
|
// commit-3 (drew spec §d), not here.
|
||||||
|
|
||||||
|
import os;
|
||||||
|
|
||||||
|
// run — execute each test in `tests` in a forked subprocess, report a
|
||||||
|
// per-test status line, and return the failure count (the synthesized
|
||||||
|
// `export fn main()` returns this, so the process exits nonzero iff any
|
||||||
|
// test failed). The table is `[](str, *fn() void)` — Hare models a test
|
||||||
|
// as `struct { name: str, func: *fn() void }` (+test.ha:23-26); the -T
|
||||||
|
// synth emits the value-table tuple form (rob ruling), so the runner
|
||||||
|
// reads `.0`/`.1` off each row rather than named fields.
|
||||||
|
//
|
||||||
|
// The private helpers carry a `tst` prefix: until real packages (#8) give
|
||||||
|
// lib/test symbol isolation, every -T build flat-bundles this module into
|
||||||
|
// the SAME bare-leaf scope as the test's own modules, so an un-prefixed
|
||||||
|
// `puts`/`runone` collides with a same-named module-private fn (lib/dirs
|
||||||
|
// and lib/temp both ship a private `puts(off, s)`). `run` stays the bound
|
||||||
|
// runner name (the synth's callee). A user `fn run` (or `const __wwtests`)
|
||||||
|
// in a @test unit collides with the synth: cstage — the shipping `ww test`
|
||||||
|
// path — rejects it loudly ("duplicate fn run" / type mismatch, rc!=0).
|
||||||
|
// wwstage TOLERATES the duplicate (#23, pre-existing rule-10 gap: cstage
|
||||||
|
// rejects a duplicate fn, wwstage does not), so the collision is loud on
|
||||||
|
// the reference stage but silent on wwstage until #23 lands.
|
||||||
|
export fn run(tests: [](str, *fn() void)) i32 = {
|
||||||
|
let nfail: i32 = 0;
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < tests.len) {
|
||||||
|
let row: (str, *fn() void) = tests[i];
|
||||||
|
let name: str = row.0;
|
||||||
|
let f: *fn() void = row.1;
|
||||||
|
tstputs(name);
|
||||||
|
tstputs(" ... ");
|
||||||
|
let st: i32 = tstrunone(f);
|
||||||
|
if (st == 0) {
|
||||||
|
tstputs("ok\n");
|
||||||
|
} else {
|
||||||
|
tstputs("FAIL\n");
|
||||||
|
nfail += 1;
|
||||||
|
};
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
tstputuint(tests.len - nfail);
|
||||||
|
tstputs(" passed, ");
|
||||||
|
tstputuint(nfail);
|
||||||
|
tstputs(" failed\n");
|
||||||
|
return nfail;
|
||||||
|
};
|
||||||
|
|
||||||
|
// runone — fork, run `f` in the child, decode the child's wait-status.
|
||||||
|
// Byte-for-byte the procrun (main.ww:154-177) status decode: low 7 bits
|
||||||
|
// are the killing signal (abort=SIGABRT, div0/SIGSEGV), 0 if the child
|
||||||
|
// exited; next byte is the exit code.
|
||||||
|
fn tstrunone(f: *fn() void) i32 = {
|
||||||
|
let pid: i32 = os.fork();
|
||||||
|
if (pid < 0) { return -1; };
|
||||||
|
if (pid == 0) {
|
||||||
|
(*f)();
|
||||||
|
os.exit(0i32);
|
||||||
|
};
|
||||||
|
let status: i32 = 0;
|
||||||
|
let r: i32 = os.wait4(pid, &status, 0i32, nil: *void);
|
||||||
|
if (r < 0) { return -1; };
|
||||||
|
if ((status & 127i32) != 0) { return 1; };
|
||||||
|
return (status >> 8i32) & 255i32;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn tstputs(s: str) void = {
|
||||||
|
os.write(os.STDOUT_FILENO, s.ptr, s.len: u64);
|
||||||
|
};
|
||||||
|
|
||||||
|
// tstputuint — write a non-negative i32 in decimal. Lean substitute for
|
||||||
|
// fmt::printf (Hare's test uses fmt); keeping lib/test's bundle floor at
|
||||||
|
// os-only avoids pulling the io/strconv stack into every -T build.
|
||||||
|
fn tstputuint(n: i32) void = {
|
||||||
|
let buf: [16]u8;
|
||||||
|
let i: i32 = 16;
|
||||||
|
let v: i32 = n;
|
||||||
|
if (v == 0) {
|
||||||
|
i -= 1;
|
||||||
|
buf[i] = 48u8;
|
||||||
|
} else {
|
||||||
|
for (v > 0) {
|
||||||
|
i -= 1;
|
||||||
|
buf[i] = (48 + v % 10): u8;
|
||||||
|
v = v / 10;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
os.write(os.STDOUT_FILENO, &buf[i], (16 - i): u64);
|
||||||
|
};
|
||||||
@@ -15801,14 +15801,20 @@ export fn checkfile(c: *checker, file: *node) void = {
|
|||||||
// untouched: the appended N_FNDECL rides cgfn, byte-id by
|
// untouched: the appended N_FNDECL rides cgfn, byte-id by
|
||||||
// construction (rule 10; cstage twin at cmd/wcc/check.c).
|
// construction (rule 10; cstage twin at cmd/wcc/check.c).
|
||||||
//
|
//
|
||||||
// D1-D5 SANCTIONED PLAN-9-LEAN REDUCTION of hare-test (user-ratified,
|
// #17 RECORD-AND-CONTINUE (rob ruling 2026-06-10; drew-17-attest-spec
|
||||||
// reinstatable post-CSP; documented departure, not a hare cite):
|
// §a): instead of straight-line `foo(); bar();` calls (which abort the
|
||||||
// D1 no __test_array section — straight-line calls; D2 call @test
|
// whole run on the first failing @test — old D3), synthesize a value
|
||||||
// fns by their real symbols (no testfunc.%d); D3 no per-test
|
// table `[](str, *fn() void) = {("foo", &foo), ...}` + a single call to
|
||||||
// setjmp/onabort isolation — run-to-completion = pass, abort/div0/
|
// the lib/test runner. The runner forks per test and reads the child's
|
||||||
// SIGSEGV/nonzero = fail and STOPS the run; D4 no sort, no fnmatch
|
// wait-status, so abort/div0/SIGSEGV/nonzero each fail THAT test and the
|
||||||
// filter — source/collection order; D5 no reflective file:line, no
|
// run proceeds (lib/test/run.ww). Table = harec __test_array reduced to
|
||||||
// per-test ok/FAIL line — failure is the nonzero process exit.
|
// 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) {
|
if (c.istest != 0) {
|
||||||
let pf: str = file.file;
|
let pf: str = file.file;
|
||||||
let pl: i32 = file.line;
|
let pl: i32 = file.line;
|
||||||
@@ -15824,9 +15830,11 @@ export fn checkfile(c: *checker, file: *node) void = {
|
|||||||
};
|
};
|
||||||
u = u.next;
|
u = u.next;
|
||||||
};
|
};
|
||||||
// (c) collect @test fns in file.list order; build the body chain.
|
// (c) collect @test fns in file.list order; build one table row
|
||||||
let bhead: *node = nil;
|
// `("<name>", &<name>)` per validated @test fn.
|
||||||
let btail: *node = nil;
|
let rhead: *node = nil;
|
||||||
|
let rtail: *node = nil;
|
||||||
|
let ntest: i32 = 0;
|
||||||
let t: *node = file.list;
|
let t: *node = file.list;
|
||||||
for (t != nil) {
|
for (t != nil) {
|
||||||
if (t.kind == nkind.N_FNDECL) {
|
if (t.kind == nkind.N_FNDECL) {
|
||||||
@@ -15853,28 +15861,77 @@ export fn checkfile(c: *checker, file: *node) void = {
|
|||||||
cerr("' must be fn() void\n");
|
cerr("' must be fn() void\n");
|
||||||
c.errs += 1;
|
c.errs += 1;
|
||||||
} else {
|
} else {
|
||||||
let call: *node = newnode(nkind.N_CALL, pf, pl, pc);
|
let nm: *node = newnode(nkind.N_STRLIT, pf, pl, pc);
|
||||||
let cid: *node = newnode(nkind.N_IDENT, pf, pl, pc);
|
nm.str = t.str;
|
||||||
cid.str = t.str;
|
let id: *node = newnode(nkind.N_IDENT, pf, pl, pc);
|
||||||
call.lhs = cid;
|
id.str = t.str;
|
||||||
let es: *node = newnode(nkind.N_EXPRSTMT, pf, pl, pc);
|
let amp: *node = newnode(nkind.N_UN, pf, pl, pc);
|
||||||
es.lhs = call;
|
amp.op = tkind.TK_AMP;
|
||||||
if (bhead == nil) { bhead = es; }
|
amp.lhs = id;
|
||||||
else { btail.next = es; };
|
let row: *node = newnode(nkind.N_TUPLE, pf, pl, pc);
|
||||||
btail = es;
|
row.list = nm;
|
||||||
|
nm.next = amp;
|
||||||
|
if (rhead == nil) { rhead = row; }
|
||||||
|
else { rtail.next = row; };
|
||||||
|
rtail = row;
|
||||||
|
ntest += 1i32;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
t = t.next;
|
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);
|
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);
|
let m: *node = newnode(nkind.N_FNDECL, pf, pl, pc);
|
||||||
m.str = "main";
|
m.str = "main";
|
||||||
m.exported = 1i32;
|
m.exported = 1i32;
|
||||||
@@ -15882,16 +15939,18 @@ export fn checkfile(c: *checker, file: *node) void = {
|
|||||||
rety.str = "i32";
|
rety.str = "i32";
|
||||||
m.lhs = rety;
|
m.lhs = rety;
|
||||||
m.body = body;
|
m.body = body;
|
||||||
// Append to file.list tail. No type_ pre-set: installdecl
|
// Append the table const (if any) then main to file.list tail. No
|
||||||
// never stamps a fn's type_ on the ww side either — cgfn reads
|
// type_ pre-set on main: installdecl never stamps a fn's type_ on
|
||||||
// lhs/list on demand, like every parsed fn. Pass-2 below
|
// the ww side — cgfn reads lhs/list on demand. Pass-2 below
|
||||||
// resolve-walks the appended body.
|
// resolve-walks the appended bodies.
|
||||||
let tl: *node = file.list;
|
let tl: *node = file.list;
|
||||||
if (tl == nil) {
|
if (tl == nil) {
|
||||||
file.list = m;
|
if (tab != nil) { file.list = tab; tab.next = m; }
|
||||||
|
else { file.list = m; };
|
||||||
} else {
|
} else {
|
||||||
for (tl.next != nil) { tl = tl.next; };
|
for (tl.next != nil) { tl = tl.next; };
|
||||||
tl.next = m;
|
if (tab != nil) { tl.next = tab; tab.next = m; }
|
||||||
|
else { tl.next = m; };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5469,14 +5469,20 @@ export fn checkfile(c: *checker, file: *node) void = {
|
|||||||
// untouched: the appended N_FNDECL rides cgfn, byte-id by
|
// untouched: the appended N_FNDECL rides cgfn, byte-id by
|
||||||
// construction (rule 10; cstage twin at cmd/wcc/check.c).
|
// construction (rule 10; cstage twin at cmd/wcc/check.c).
|
||||||
//
|
//
|
||||||
// D1-D5 SANCTIONED PLAN-9-LEAN REDUCTION of hare-test (user-ratified,
|
// #17 RECORD-AND-CONTINUE (rob ruling 2026-06-10; drew-17-attest-spec
|
||||||
// reinstatable post-CSP; documented departure, not a hare cite):
|
// §a): instead of straight-line `foo(); bar();` calls (which abort the
|
||||||
// D1 no __test_array section — straight-line calls; D2 call @test
|
// whole run on the first failing @test — old D3), synthesize a value
|
||||||
// fns by their real symbols (no testfunc.%d); D3 no per-test
|
// table `[](str, *fn() void) = {("foo", &foo), ...}` + a single call to
|
||||||
// setjmp/onabort isolation — run-to-completion = pass, abort/div0/
|
// the lib/test runner. The runner forks per test and reads the child's
|
||||||
// SIGSEGV/nonzero = fail and STOPS the run; D4 no sort, no fnmatch
|
// wait-status, so abort/div0/SIGSEGV/nonzero each fail THAT test and the
|
||||||
// filter — source/collection order; D5 no reflective file:line, no
|
// run proceeds (lib/test/run.ww). Table = harec __test_array reduced to
|
||||||
// per-test ok/FAIL line — failure is the nonzero process exit.
|
// 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) {
|
if (c.istest != 0) {
|
||||||
let pf: str = file.file;
|
let pf: str = file.file;
|
||||||
let pl: i32 = file.line;
|
let pl: i32 = file.line;
|
||||||
@@ -5492,9 +5498,11 @@ export fn checkfile(c: *checker, file: *node) void = {
|
|||||||
};
|
};
|
||||||
u = u.next;
|
u = u.next;
|
||||||
};
|
};
|
||||||
// (c) collect @test fns in file.list order; build the body chain.
|
// (c) collect @test fns in file.list order; build one table row
|
||||||
let bhead: *node = nil;
|
// `("<name>", &<name>)` per validated @test fn.
|
||||||
let btail: *node = nil;
|
let rhead: *node = nil;
|
||||||
|
let rtail: *node = nil;
|
||||||
|
let ntest: i32 = 0;
|
||||||
let t: *node = file.list;
|
let t: *node = file.list;
|
||||||
for (t != nil) {
|
for (t != nil) {
|
||||||
if (t.kind == nkind.N_FNDECL) {
|
if (t.kind == nkind.N_FNDECL) {
|
||||||
@@ -5521,28 +5529,77 @@ export fn checkfile(c: *checker, file: *node) void = {
|
|||||||
cerr("' must be fn() void\n");
|
cerr("' must be fn() void\n");
|
||||||
c.errs += 1;
|
c.errs += 1;
|
||||||
} else {
|
} else {
|
||||||
let call: *node = newnode(nkind.N_CALL, pf, pl, pc);
|
let nm: *node = newnode(nkind.N_STRLIT, pf, pl, pc);
|
||||||
let cid: *node = newnode(nkind.N_IDENT, pf, pl, pc);
|
nm.str = t.str;
|
||||||
cid.str = t.str;
|
let id: *node = newnode(nkind.N_IDENT, pf, pl, pc);
|
||||||
call.lhs = cid;
|
id.str = t.str;
|
||||||
let es: *node = newnode(nkind.N_EXPRSTMT, pf, pl, pc);
|
let amp: *node = newnode(nkind.N_UN, pf, pl, pc);
|
||||||
es.lhs = call;
|
amp.op = tkind.TK_AMP;
|
||||||
if (bhead == nil) { bhead = es; }
|
amp.lhs = id;
|
||||||
else { btail.next = es; };
|
let row: *node = newnode(nkind.N_TUPLE, pf, pl, pc);
|
||||||
btail = es;
|
row.list = nm;
|
||||||
|
nm.next = amp;
|
||||||
|
if (rhead == nil) { rhead = row; }
|
||||||
|
else { rtail.next = row; };
|
||||||
|
rtail = row;
|
||||||
|
ntest += 1i32;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
t = t.next;
|
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);
|
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);
|
let m: *node = newnode(nkind.N_FNDECL, pf, pl, pc);
|
||||||
m.str = "main";
|
m.str = "main";
|
||||||
m.exported = 1i32;
|
m.exported = 1i32;
|
||||||
@@ -5550,16 +5607,18 @@ export fn checkfile(c: *checker, file: *node) void = {
|
|||||||
rety.str = "i32";
|
rety.str = "i32";
|
||||||
m.lhs = rety;
|
m.lhs = rety;
|
||||||
m.body = body;
|
m.body = body;
|
||||||
// Append to file.list tail. No type_ pre-set: installdecl
|
// Append the table const (if any) then main to file.list tail. No
|
||||||
// never stamps a fn's type_ on the ww side either — cgfn reads
|
// type_ pre-set on main: installdecl never stamps a fn's type_ on
|
||||||
// lhs/list on demand, like every parsed fn. Pass-2 below
|
// the ww side — cgfn reads lhs/list on demand. Pass-2 below
|
||||||
// resolve-walks the appended body.
|
// resolve-walks the appended bodies.
|
||||||
let tl: *node = file.list;
|
let tl: *node = file.list;
|
||||||
if (tl == nil) {
|
if (tl == nil) {
|
||||||
file.list = m;
|
if (tab != nil) { file.list = tab; tab.next = m; }
|
||||||
|
else { file.list = m; };
|
||||||
} else {
|
} else {
|
||||||
for (tl.next != nil) { tl = tl.next; };
|
for (tl.next != nil) { tl = tl.next; };
|
||||||
tl.next = m;
|
if (tab != nil) { tl.next = tab; tab.next = m; }
|
||||||
|
else { tl.next = m; };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3724,6 +3724,19 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, objstem: *u8, inc
|
|||||||
c.out = cf;
|
c.out = cf;
|
||||||
c.dirs = searchpath.ptr;
|
c.dirs = searchpath.ptr;
|
||||||
c.visit = nil;
|
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); }
|
if (entryisdir != 0) { expanddir(&c, srcd.ptr); }
|
||||||
else { expand(&c, src); };
|
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,
|
// directory: open the dir, getdents64, build+run each *_test.ww,
|
||||||
// report ok/FAIL per file, return 0 iff all pass.
|
// 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))!;
|
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
tmp.len = os.PATH_MAX;
|
tmp.len = os.PATH_MAX;
|
||||||
makeruntmp(tmp.ptr);
|
// -o redirects the binary + its combined (objstem, T3) to <stem>; the
|
||||||
if (buildone(selfdir, src, 0, tmp.ptr, nil, incs, nil, 1i32) != 0) {
|
// default temp keeps the combined next to the source as before.
|
||||||
os.remove(pathstr(tmp.ptr));
|
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;
|
return 1;
|
||||||
};
|
};
|
||||||
|
if (compileonly != 0) { return 0; };
|
||||||
let execargv: []*u8 = alloc([], 2u64)!;
|
let execargv: []*u8 = alloc([], 2u64)!;
|
||||||
execargv.len = 2;
|
execargv.len = 2;
|
||||||
execargv[0] = tmp.ptr;
|
execargv[0] = outp;
|
||||||
execargv[1] = nil;
|
execargv[1] = nil;
|
||||||
let rc: i32 = procrun(tmp.ptr, execargv.ptr);
|
let rc: i32 = procrun(outp, execargv.ptr);
|
||||||
os.remove(pathstr(tmp.ptr));
|
if (outstem == nil) { os.remove(pathstr(outp)); };
|
||||||
return rc;
|
return rc;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -4371,6 +4395,11 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
let incoff: u64 = 0u64;
|
let incoff: u64 = 0u64;
|
||||||
cstrseal(incs.ptr, 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;
|
let i: i32 = start;
|
||||||
for (i < argc) {
|
for (i < argc) {
|
||||||
let p: *u8 = argv[i];
|
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);
|
incoff = cstrinto(incs.ptr, incoff, dir);
|
||||||
cstrseal(incs.ptr, incoff);
|
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 {
|
} else {
|
||||||
cerr("ww test: unknown flag\n");
|
cerr("ww test: unknown flag\n");
|
||||||
return 2;
|
return 2;
|
||||||
};
|
}; }; };
|
||||||
} else {
|
} else {
|
||||||
if (target == nil) { target = p; };
|
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
|
// single-file mode: literal *.ww that exists
|
||||||
if (cstrendswithlit(target, ".ww")) {
|
if (cstrendswithlit(target, ".ww")) {
|
||||||
if (os.access(pathstr(target), 0i32) == 0) {
|
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
|
// otherwise treat target as a directory; enumerate *_test.ww
|
||||||
return rundirtests(selfdir, target);
|
return rundirtests(selfdir, target);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -938,6 +938,19 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, objstem: *u8, inc
|
|||||||
c.out = cf;
|
c.out = cf;
|
||||||
c.dirs = searchpath.ptr;
|
c.dirs = searchpath.ptr;
|
||||||
c.visit = nil;
|
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); }
|
if (entryisdir != 0) { expanddir(&c, srcd.ptr); }
|
||||||
else { expand(&c, src); };
|
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,
|
// directory: open the dir, getdents64, build+run each *_test.ww,
|
||||||
// report ok/FAIL per file, return 0 iff all pass.
|
// 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))!;
|
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
tmp.len = os.PATH_MAX;
|
tmp.len = os.PATH_MAX;
|
||||||
makeruntmp(tmp.ptr);
|
// -o redirects the binary + its combined (objstem, T3) to <stem>; the
|
||||||
if (buildone(selfdir, src, 0, tmp.ptr, nil, incs, nil, 1i32) != 0) {
|
// default temp keeps the combined next to the source as before.
|
||||||
os.remove(pathstr(tmp.ptr));
|
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;
|
return 1;
|
||||||
};
|
};
|
||||||
|
if (compileonly != 0) { return 0; };
|
||||||
let execargv: []*u8 = alloc([], 2u64)!;
|
let execargv: []*u8 = alloc([], 2u64)!;
|
||||||
execargv.len = 2;
|
execargv.len = 2;
|
||||||
execargv[0] = tmp.ptr;
|
execargv[0] = outp;
|
||||||
execargv[1] = nil;
|
execargv[1] = nil;
|
||||||
let rc: i32 = procrun(tmp.ptr, execargv.ptr);
|
let rc: i32 = procrun(outp, execargv.ptr);
|
||||||
os.remove(pathstr(tmp.ptr));
|
if (outstem == nil) { os.remove(pathstr(outp)); };
|
||||||
return rc;
|
return rc;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1585,6 +1609,11 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
let incoff: u64 = 0u64;
|
let incoff: u64 = 0u64;
|
||||||
cstrseal(incs.ptr, 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;
|
let i: i32 = start;
|
||||||
for (i < argc) {
|
for (i < argc) {
|
||||||
let p: *u8 = argv[i];
|
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);
|
incoff = cstrinto(incs.ptr, incoff, dir);
|
||||||
cstrseal(incs.ptr, incoff);
|
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 {
|
} else {
|
||||||
cerr("ww test: unknown flag\n");
|
cerr("ww test: unknown flag\n");
|
||||||
return 2;
|
return 2;
|
||||||
};
|
}; }; };
|
||||||
} else {
|
} else {
|
||||||
if (target == nil) { target = p; };
|
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
|
// single-file mode: literal *.ww that exists
|
||||||
if (cstrendswithlit(target, ".ww")) {
|
if (cstrendswithlit(target, ".ww")) {
|
||||||
if (os.access(pathstr(target), 0i32) == 0) {
|
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
|
// otherwise treat target as a directory; enumerate *_test.ww
|
||||||
return rundirtests(selfdir, target);
|
return rundirtests(selfdir, target);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15801,14 +15801,20 @@ export fn checkfile(c: *checker, file: *node) void = {
|
|||||||
// untouched: the appended N_FNDECL rides cgfn, byte-id by
|
// untouched: the appended N_FNDECL rides cgfn, byte-id by
|
||||||
// construction (rule 10; cstage twin at cmd/wcc/check.c).
|
// construction (rule 10; cstage twin at cmd/wcc/check.c).
|
||||||
//
|
//
|
||||||
// D1-D5 SANCTIONED PLAN-9-LEAN REDUCTION of hare-test (user-ratified,
|
// #17 RECORD-AND-CONTINUE (rob ruling 2026-06-10; drew-17-attest-spec
|
||||||
// reinstatable post-CSP; documented departure, not a hare cite):
|
// §a): instead of straight-line `foo(); bar();` calls (which abort the
|
||||||
// D1 no __test_array section — straight-line calls; D2 call @test
|
// whole run on the first failing @test — old D3), synthesize a value
|
||||||
// fns by their real symbols (no testfunc.%d); D3 no per-test
|
// table `[](str, *fn() void) = {("foo", &foo), ...}` + a single call to
|
||||||
// setjmp/onabort isolation — run-to-completion = pass, abort/div0/
|
// the lib/test runner. The runner forks per test and reads the child's
|
||||||
// SIGSEGV/nonzero = fail and STOPS the run; D4 no sort, no fnmatch
|
// wait-status, so abort/div0/SIGSEGV/nonzero each fail THAT test and the
|
||||||
// filter — source/collection order; D5 no reflective file:line, no
|
// run proceeds (lib/test/run.ww). Table = harec __test_array reduced to
|
||||||
// per-test ok/FAIL line — failure is the nonzero process exit.
|
// 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) {
|
if (c.istest != 0) {
|
||||||
let pf: str = file.file;
|
let pf: str = file.file;
|
||||||
let pl: i32 = file.line;
|
let pl: i32 = file.line;
|
||||||
@@ -15824,9 +15830,11 @@ export fn checkfile(c: *checker, file: *node) void = {
|
|||||||
};
|
};
|
||||||
u = u.next;
|
u = u.next;
|
||||||
};
|
};
|
||||||
// (c) collect @test fns in file.list order; build the body chain.
|
// (c) collect @test fns in file.list order; build one table row
|
||||||
let bhead: *node = nil;
|
// `("<name>", &<name>)` per validated @test fn.
|
||||||
let btail: *node = nil;
|
let rhead: *node = nil;
|
||||||
|
let rtail: *node = nil;
|
||||||
|
let ntest: i32 = 0;
|
||||||
let t: *node = file.list;
|
let t: *node = file.list;
|
||||||
for (t != nil) {
|
for (t != nil) {
|
||||||
if (t.kind == nkind.N_FNDECL) {
|
if (t.kind == nkind.N_FNDECL) {
|
||||||
@@ -15853,28 +15861,77 @@ export fn checkfile(c: *checker, file: *node) void = {
|
|||||||
cerr("' must be fn() void\n");
|
cerr("' must be fn() void\n");
|
||||||
c.errs += 1;
|
c.errs += 1;
|
||||||
} else {
|
} else {
|
||||||
let call: *node = newnode(nkind.N_CALL, pf, pl, pc);
|
let nm: *node = newnode(nkind.N_STRLIT, pf, pl, pc);
|
||||||
let cid: *node = newnode(nkind.N_IDENT, pf, pl, pc);
|
nm.str = t.str;
|
||||||
cid.str = t.str;
|
let id: *node = newnode(nkind.N_IDENT, pf, pl, pc);
|
||||||
call.lhs = cid;
|
id.str = t.str;
|
||||||
let es: *node = newnode(nkind.N_EXPRSTMT, pf, pl, pc);
|
let amp: *node = newnode(nkind.N_UN, pf, pl, pc);
|
||||||
es.lhs = call;
|
amp.op = tkind.TK_AMP;
|
||||||
if (bhead == nil) { bhead = es; }
|
amp.lhs = id;
|
||||||
else { btail.next = es; };
|
let row: *node = newnode(nkind.N_TUPLE, pf, pl, pc);
|
||||||
btail = es;
|
row.list = nm;
|
||||||
|
nm.next = amp;
|
||||||
|
if (rhead == nil) { rhead = row; }
|
||||||
|
else { rtail.next = row; };
|
||||||
|
rtail = row;
|
||||||
|
ntest += 1i32;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
t = t.next;
|
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);
|
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);
|
let m: *node = newnode(nkind.N_FNDECL, pf, pl, pc);
|
||||||
m.str = "main";
|
m.str = "main";
|
||||||
m.exported = 1i32;
|
m.exported = 1i32;
|
||||||
@@ -15882,16 +15939,18 @@ export fn checkfile(c: *checker, file: *node) void = {
|
|||||||
rety.str = "i32";
|
rety.str = "i32";
|
||||||
m.lhs = rety;
|
m.lhs = rety;
|
||||||
m.body = body;
|
m.body = body;
|
||||||
// Append to file.list tail. No type_ pre-set: installdecl
|
// Append the table const (if any) then main to file.list tail. No
|
||||||
// never stamps a fn's type_ on the ww side either — cgfn reads
|
// type_ pre-set on main: installdecl never stamps a fn's type_ on
|
||||||
// lhs/list on demand, like every parsed fn. Pass-2 below
|
// the ww side — cgfn reads lhs/list on demand. Pass-2 below
|
||||||
// resolve-walks the appended body.
|
// resolve-walks the appended bodies.
|
||||||
let tl: *node = file.list;
|
let tl: *node = file.list;
|
||||||
if (tl == nil) {
|
if (tl == nil) {
|
||||||
file.list = m;
|
if (tab != nil) { file.list = tab; tab.next = m; }
|
||||||
|
else { file.list = m; };
|
||||||
} else {
|
} else {
|
||||||
for (tl.next != nil) { tl = tl.next; };
|
for (tl.next != nil) { tl = tl.next; };
|
||||||
tl.next = m;
|
if (tab != nil) { tl.next = tab; tab.next = m; }
|
||||||
|
else { tl.next = m; };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -54,9 +54,10 @@ static int
|
|||||||
run_fixture(const char *bin, const char *comp, const char *drv)
|
run_fixture(const char *bin, const char *comp, const char *drv)
|
||||||
{
|
{
|
||||||
int pid = getpid();
|
int pid = getpid();
|
||||||
char src[256], comb[256], asmf[256], obj[256], exe[256];
|
char src[256], comb[256], binout[256], asmf[256], obj[256], exe[256];
|
||||||
char rt[1024], cmd[4096];
|
char rt[1024], cmd[4096];
|
||||||
snprintf(src, sizeof src, "/tmp/at910_%s_%d.ww", comp, pid);
|
snprintf(src, sizeof src, "/tmp/at910_%s_%d.ww", comp, pid);
|
||||||
|
snprintf(binout, sizeof binout, "/tmp/at910_%s_%d", comp, pid);
|
||||||
snprintf(comb, sizeof comb, "/tmp/at910_%s_%d.combined.ww", comp, pid);
|
snprintf(comb, sizeof comb, "/tmp/at910_%s_%d.combined.ww", comp, pid);
|
||||||
snprintf(asmf, sizeof asmf, "/tmp/at910_%s_%d.s", comp, pid);
|
snprintf(asmf, sizeof asmf, "/tmp/at910_%s_%d.s", comp, pid);
|
||||||
snprintf(obj, sizeof obj, "/tmp/at910_%s_%d.o", comp, pid);
|
snprintf(obj, sizeof obj, "/tmp/at910_%s_%d.o", comp, pid);
|
||||||
@@ -66,12 +67,12 @@ run_fixture(const char *bin, const char *comp, const char *drv)
|
|||||||
snprintf(cmd, sizeof cmd, "cp test/wcc/data/attest_pass.ww %s", src);
|
snprintf(cmd, sizeof cmd, "cp test/wcc/data/attest_pass.ww %s", src);
|
||||||
if (runwait(cmd) != 0) { fprintf(stderr, "910 FAIL: cp\n"); return 1; }
|
if (runwait(cmd) != 0) { fprintf(stderr, "910 FAIL: cp\n"); return 1; }
|
||||||
|
|
||||||
/* Driver resolves `import rt` into <src>.combined.ww. The build
|
/* `test -c -o <stem>` (compile-only) resolves imports AND auto-bundles
|
||||||
* itself exits nonzero (the @test-only fixture has no main — that
|
* lib/test (#17) into <stem>.combined.ww — the synth's run() callee
|
||||||
* is exactly what -T synthesizes), but the combined unit is written
|
* must link below — WITHOUT running the harness. We depend on the
|
||||||
* before any compile step, so we depend on the ARTIFACT, not the
|
* combined ARTIFACT; `-o` keeps the binary off the CWD. */
|
||||||
* exit code. */
|
snprintf(cmd, sizeof cmd, "%s/%s test -c -o %s %s > /dev/null 2>&1",
|
||||||
snprintf(cmd, sizeof cmd, "%s/%s build %s > /dev/null 2>&1", bin, drv, src);
|
bin, drv, binout, src);
|
||||||
runwait(cmd);
|
runwait(cmd);
|
||||||
if (access(comb, 0) != 0) {
|
if (access(comb, 0) != 0) {
|
||||||
fprintf(stderr, "910 FAIL: %s build produced no %s\n", drv, comb);
|
fprintf(stderr, "910 FAIL: %s build produced no %s\n", drv, comb);
|
||||||
@@ -94,6 +95,10 @@ run_fixture(const char *bin, const char *comp, const char *drv)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
unlink(src); unlink(comb); unlink(asmf); unlink(obj); unlink(exe);
|
unlink(src); unlink(comb); unlink(asmf); unlink(obj); unlink(exe);
|
||||||
|
unlink(binout);
|
||||||
|
char tmp[300];
|
||||||
|
snprintf(tmp, sizeof tmp, "%s.s", binout); unlink(tmp);
|
||||||
|
snprintf(tmp, sizeof tmp, "%s.o", binout); unlink(tmp);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,9 +166,18 @@ nondrop(const char *bin, const char *comp)
|
|||||||
fprintf(stderr, "910 FAIL: %s non-T nondrop compile\n", comp);
|
fprintf(stderr, "910 FAIL: %s non-T nondrop compile\n", comp);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
/* The -T compile synthesizes a main that calls lib/test's run(), so it
|
||||||
|
* needs the lib/test-inclusive combined `ww test -c` writes (the plain
|
||||||
|
* compile above has no synth, so it stays on the raw fixture). */
|
||||||
|
char stem[256], comb[300];
|
||||||
|
snprintf(stem, sizeof stem, "/tmp/at910nd_%s_c_%d", comp, pid);
|
||||||
|
snprintf(comb, sizeof comb, "%s.combined.ww", stem);
|
||||||
snprintf(cmd, sizeof cmd,
|
snprintf(cmd, sizeof cmd,
|
||||||
"%s/%s -T test/wcc/data/attest_nondrop.ww -o %s 2>/dev/null",
|
"%s/ww test -c -o %s test/wcc/data/attest_nondrop.ww > /dev/null 2>&1",
|
||||||
bin, comp, tee);
|
bin, stem);
|
||||||
|
runwait(cmd);
|
||||||
|
snprintf(cmd, sizeof cmd,
|
||||||
|
"%s/%s -T %s -o %s 2>/dev/null", bin, comp, comb, tee);
|
||||||
if (runwait(cmd) != 0) {
|
if (runwait(cmd) != 0) {
|
||||||
fprintf(stderr, "910 FAIL: %s -T nondrop compile\n", comp);
|
fprintf(stderr, "910 FAIL: %s -T nondrop compile\n", comp);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -184,7 +198,11 @@ nondrop(const char *bin, const char *comp)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unlink(plain); unlink(tee);
|
unlink(plain); unlink(tee); unlink(comb);
|
||||||
|
char tmp[300];
|
||||||
|
snprintf(tmp, sizeof tmp, "%s.s", stem); unlink(tmp);
|
||||||
|
snprintf(tmp, sizeof tmp, "%s.o", stem); unlink(tmp);
|
||||||
|
unlink(stem);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
173
test/wcc/911_attest_record.c
Normal file
173
test/wcc/911_attest_record.c
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
/*
|
||||||
|
* 911_attest_record — #17 record-and-continue harness (lib/test + the
|
||||||
|
* table-synth -T entry). Sibling of 910_at_test (which pins the synth
|
||||||
|
* itself + the #6 non-T drop); this one pins the RUNTIME behaviour the
|
||||||
|
* fork+wait4 runner adds:
|
||||||
|
*
|
||||||
|
* 1. RECORD-AND-CONTINUE — `ww test` the mixed fixture (one pass, then
|
||||||
|
* assert-fail/SIGSEGV/SIGFPE, then a final pass). The runner must
|
||||||
|
* proceed past every failure, emit the right per-test `name ... ok/
|
||||||
|
* FAIL` line (table-driven below), print the right `P passed, F
|
||||||
|
* failed` summary, and exit with the failure count (nonzero).
|
||||||
|
* 2. ALL-PASS — `ww test` the all-pass fixture exits 0 (no false
|
||||||
|
* failure from the new runner).
|
||||||
|
* 3. BYTE-ID (rule 10) — `ww test -c` the mixed fixture into a
|
||||||
|
* lib/test-inclusive combined, then `w6c -T` and `w6c_ww -T` must
|
||||||
|
* emit byte-identical asm (the table + fork loop, both stages).
|
||||||
|
*
|
||||||
|
* The fork changes failure OUTPUT, not pass behaviour: a passing fixture
|
||||||
|
* still exits 0, so the 35 converted lib tests stay green (904/967/...).
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
static int
|
||||||
|
runwait(const char *cmd)
|
||||||
|
{
|
||||||
|
int rc = system(cmd);
|
||||||
|
if (rc == -1) return -1;
|
||||||
|
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
absbin(void)
|
||||||
|
{
|
||||||
|
const char *b = getenv("BIN");
|
||||||
|
if (!b) b = "out/bin";
|
||||||
|
if (b[0] == '/') return b;
|
||||||
|
static char buf[2048];
|
||||||
|
char cwd[1024];
|
||||||
|
if (getcwd(cwd, sizeof cwd) == NULL) return NULL;
|
||||||
|
snprintf(buf, sizeof buf, "%s/%s", cwd, b);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
slurp(const char *path, char *out, size_t outsz)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) return -1;
|
||||||
|
size_t n = fread(out, 1, outsz - 1, f);
|
||||||
|
out[n] = '\0';
|
||||||
|
fclose(f);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Each expected per-test verdict line, in source/collection order. */
|
||||||
|
static const char *expect_lines[] = {
|
||||||
|
"rec_pass_a ... ok",
|
||||||
|
"rec_assert ... FAIL",
|
||||||
|
"rec_segv ... FAIL",
|
||||||
|
"rec_div0 ... FAIL",
|
||||||
|
"rec_pass_b ... ok",
|
||||||
|
"2 passed, 3 failed",
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
record_continue(const char *bin)
|
||||||
|
{
|
||||||
|
int pid = getpid();
|
||||||
|
char out[256], cmd[4096], buf[8192];
|
||||||
|
snprintf(out, sizeof out, "/tmp/at911_%d.out", pid);
|
||||||
|
|
||||||
|
/* `ww test` builds the fixture (auto-bundling lib/test) and runs it;
|
||||||
|
* the exit code is the runner's failure count. */
|
||||||
|
snprintf(cmd, sizeof cmd,
|
||||||
|
"%s/ww test test/wcc/data/attest_record.ww > %s 2>/dev/null",
|
||||||
|
bin, out);
|
||||||
|
int rc = runwait(cmd);
|
||||||
|
if (rc != 3) {
|
||||||
|
fprintf(stderr, "911 FAIL: record-and-continue exit %d "
|
||||||
|
"(expected 3 failures)\n", rc);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (slurp(out, buf, sizeof buf) != 0) {
|
||||||
|
fprintf(stderr, "911 FAIL: cannot read runner output\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < sizeof expect_lines / sizeof expect_lines[0]; i++) {
|
||||||
|
if (strstr(buf, expect_lines[i]) == NULL) {
|
||||||
|
fprintf(stderr, "911 FAIL: missing runner line '%s'\n"
|
||||||
|
"--- got ---\n%s\n", expect_lines[i], buf);
|
||||||
|
unlink(out);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unlink(out);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
all_pass(const char *bin)
|
||||||
|
{
|
||||||
|
char cmd[4096];
|
||||||
|
snprintf(cmd, sizeof cmd,
|
||||||
|
"%s/ww test test/wcc/data/attest_pass.ww > /dev/null 2>&1", bin);
|
||||||
|
int rc = runwait(cmd);
|
||||||
|
if (rc != 0) {
|
||||||
|
fprintf(stderr, "911 FAIL: all-pass fixture exit %d "
|
||||||
|
"(expected 0)\n", rc);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
byteid(const char *bin)
|
||||||
|
{
|
||||||
|
int pid = getpid();
|
||||||
|
char stem[256], comb[256], cs[256], ws[256], cmd[4096];
|
||||||
|
snprintf(stem, sizeof stem, "/tmp/at911_bid_%d", pid);
|
||||||
|
snprintf(comb, sizeof comb, "%s.combined.ww", stem);
|
||||||
|
snprintf(cs, sizeof cs, "/tmp/at911_bid_c_%d.s", pid);
|
||||||
|
snprintf(ws, sizeof ws, "/tmp/at911_bid_w_%d.s", pid);
|
||||||
|
|
||||||
|
/* `ww test -c -o <stem>` (compile-only) writes a lib/test-inclusive
|
||||||
|
* combined beside -o without running the harness. */
|
||||||
|
snprintf(cmd, sizeof cmd,
|
||||||
|
"%s/ww test -c -o %s test/wcc/data/attest_record.ww > /dev/null 2>&1",
|
||||||
|
bin, stem);
|
||||||
|
runwait(cmd);
|
||||||
|
if (access(comb, 0) != 0) {
|
||||||
|
fprintf(stderr, "911 FAIL: ww test -c produced no %s\n", comb);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
snprintf(cmd, sizeof cmd, "%s/w6c -T %s -o %s 2>/dev/null", bin, comb, cs);
|
||||||
|
if (runwait(cmd) != 0) { fprintf(stderr, "911 FAIL: w6c -T (byteid)\n"); return 1; }
|
||||||
|
snprintf(cmd, sizeof cmd, "%s/w6c_ww -T %s -o %s 2>/dev/null", bin, comb, ws);
|
||||||
|
if (runwait(cmd) != 0) { fprintf(stderr, "911 FAIL: w6c_ww -T (byteid)\n"); return 1; }
|
||||||
|
|
||||||
|
char bc[262144], bw[262144];
|
||||||
|
int rc = 0;
|
||||||
|
if (slurp(cs, bc, sizeof bc) < 0 || slurp(ws, bw, sizeof bw) < 0) {
|
||||||
|
fprintf(stderr, "911 FAIL: slurp byteid asm\n");
|
||||||
|
rc = 1;
|
||||||
|
} else if (strcmp(bc, bw) != 0) {
|
||||||
|
fprintf(stderr, "911 FAIL: -T asm differs between stages\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
unlink(comb); unlink(cs); unlink(ws);
|
||||||
|
snprintf(cmd, sizeof cmd, "%s.s", stem); unlink(cmd);
|
||||||
|
snprintf(cmd, sizeof cmd, "%s.o", stem); unlink(cmd);
|
||||||
|
unlink(stem);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(void)
|
||||||
|
{
|
||||||
|
const char *bin = absbin();
|
||||||
|
if (!bin) { fprintf(stderr, "911 FAIL: getcwd\n"); return 1; }
|
||||||
|
|
||||||
|
if (record_continue(bin) != 0) return 1;
|
||||||
|
if (all_pass(bin) != 0) return 1;
|
||||||
|
if (byteid(bin) != 0) return 1;
|
||||||
|
|
||||||
|
printf("@test record-and-continue: 3 fault classes recorded + run "
|
||||||
|
"proceeds + counts + all-pass exit 0 + cs/ww byte-id (#17)\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -5,9 +5,11 @@
|
|||||||
* build/run — a lone -I/-L/-l/-o (flag with no following argument) is a
|
* build/run — a lone -I/-L/-l/-o (flag with no following argument) is a
|
||||||
* hard error "ww <cmd>: -X needs an argument" (rc 2), not a
|
* hard error "ww <cmd>: -X needs an argument" (rc 2), not a
|
||||||
* silently-swallowed positional.
|
* silently-swallowed positional.
|
||||||
* test — only -I carries meaning; -l/-L/-o and any unknown flag are
|
* test — -I/-c/-o carry meaning; -l/-L and any unknown flag are
|
||||||
* rejected with "ww test: unknown flag" (rc 2); a lone -I is
|
* rejected with "ww test: unknown flag" (rc 2); a lone -I/-o
|
||||||
* "ww test: -I needs an argument" (rc 2).
|
* is "ww test: -X needs an argument" (rc 2); -c/-o without a
|
||||||
|
* single test file is "ww test: -c/-o need a single test file"
|
||||||
|
* (rc 2, #17).
|
||||||
* Each row asserts the expected rc + stderr substring AND that the two
|
* Each row asserts the expected rc + stderr substring AND that the two
|
||||||
* drivers are byte-identical (rule 10): the cstage parse_build_flags /
|
* drivers are byte-identical (rule 10): the cstage parse_build_flags /
|
||||||
* do_test must match the wwstage main.ww dobuild/dorun/dotest verbatim.
|
* do_test must match the wwstage main.ww dobuild/dorun/dotest verbatim.
|
||||||
@@ -36,9 +38,14 @@ static const struct row rows[] = {
|
|||||||
{ "run -o", 2, "ww run: -o needs an argument" },
|
{ "run -o", 2, "ww run: -o needs an argument" },
|
||||||
{ "run -l", 2, "ww run: -l needs an argument" },
|
{ "run -l", 2, "ww run: -l needs an argument" },
|
||||||
{ "test -l", 2, "ww test: unknown flag" },
|
{ "test -l", 2, "ww test: unknown flag" },
|
||||||
{ "test -o x", 2, "ww test: unknown flag" },
|
|
||||||
{ "test -zz", 2, "ww test: unknown flag" },
|
{ "test -zz", 2, "ww test: unknown flag" },
|
||||||
{ "test -I", 2, "ww test: -I needs an argument" },
|
{ "test -I", 2, "ww test: -I needs an argument" },
|
||||||
|
/* #17: -c (compile-only) + -o are now meaningful for `test`; a lone -o
|
||||||
|
* needs an arg, and -c/-o require a single test file (not the default
|
||||||
|
* "." directory enumeration). */
|
||||||
|
{ "test -o", 2, "ww test: -o needs an argument" },
|
||||||
|
{ "test -o x", 2, "ww test: -c/-o need a single test file" },
|
||||||
|
{ "test -c", 2, "ww test: -c/-o need a single test file" },
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Run "<bin>/<drv> <args>" capturing rc + stderr text into err (NUL-
|
/* Run "<bin>/<drv> <args>" capturing rc + stderr text into err (NUL-
|
||||||
|
|||||||
@@ -254,18 +254,29 @@ check_one(const char *bin, const char *cwd, const struct ent *e, int idx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* exit deliberately ignored: the combined unit is written before
|
/* exit deliberately ignored: the combined unit is written before
|
||||||
* codegen (cf 990 resolveunit) and only it matters here. */
|
* codegen (cf 990 resolveunit) and only it matters here. A @test
|
||||||
snprintf(cmd, sizeof cmd,
|
* fixture builds via `ww test -c -o <stem>` (compile-only) so the
|
||||||
"cd %s && timeout 180 %s/ww build %s %s >/dev/null 2>&1",
|
* resolved unit AUTO-BUNDLES lib/test — the #17 synth's `run()` callee
|
||||||
td, bin, incs, base);
|
* must resolve when w6c/w6c_ww -T compile the combined below. Both the
|
||||||
|
* `-o <stem>` (test) and the next-to-source (build) paths land the
|
||||||
|
* combined at <stem>.combined.ww. Import-probes carry their own main
|
||||||
|
* and stay a plain non-test build. */
|
||||||
|
char stem[512];
|
||||||
|
snprintf(stem, sizeof stem, "%s", base);
|
||||||
|
char *sd = strrchr(stem, '.');
|
||||||
|
if (sd) *sd = '\0';
|
||||||
|
if (e->fixture)
|
||||||
|
snprintf(cmd, sizeof cmd,
|
||||||
|
"cd %s && timeout 180 %s/ww test -c -o %s %s %s >/dev/null 2>&1",
|
||||||
|
td, bin, stem, incs, base);
|
||||||
|
else
|
||||||
|
snprintf(cmd, sizeof cmd,
|
||||||
|
"cd %s && timeout 180 %s/ww build %s %s >/dev/null 2>&1",
|
||||||
|
td, bin, incs, base);
|
||||||
runwait(cmd);
|
runwait(cmd);
|
||||||
|
|
||||||
char comb[512];
|
char comb[512];
|
||||||
snprintf(comb, sizeof comb, "%s/%s", td, base);
|
snprintf(comb, sizeof comb, "%s/%s.combined.ww", td, stem);
|
||||||
char *dot = strrchr(comb, '.');
|
|
||||||
if (dot) *dot = '\0';
|
|
||||||
size_t cn = strlen(comb);
|
|
||||||
snprintf(comb + cn, sizeof comb - cn, ".combined.ww");
|
|
||||||
if (access(comb, 0) != 0) {
|
if (access(comb, 0) != 0) {
|
||||||
fprintf(stderr, "lib_byteid FAIL: %s — no resolved unit\n", label);
|
fprintf(stderr, "lib_byteid FAIL: %s — no resolved unit\n", label);
|
||||||
goto out;
|
goto out;
|
||||||
@@ -357,6 +368,10 @@ static const char *covered[] = {
|
|||||||
"lib/io", "lib/math", "lib/rt", "lib/types", "lib/ww/parse",
|
"lib/io", "lib/math", "lib/rt", "lib/types", "lib/ww/parse",
|
||||||
/* module body dragged into the lib/strconv/test fixtures */
|
/* module body dragged into the lib/strconv/test fixtures */
|
||||||
"lib/strconv",
|
"lib/strconv",
|
||||||
|
/* #17: the @test runner is AUTO-BUNDLED into every -T combined, so
|
||||||
|
* every @test fixture above byte-ids it cs/ww; 911_attest_record also
|
||||||
|
* compares it directly. It has no _test.ww of its own. */
|
||||||
|
"lib/test",
|
||||||
NULL,
|
NULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -66,9 +66,10 @@ static int
|
|||||||
run_fixture(const char *bin)
|
run_fixture(const char *bin)
|
||||||
{
|
{
|
||||||
int pid = getpid();
|
int pid = getpid();
|
||||||
char src[256], comb[256], asmf[256], obj[256], exe[256];
|
char src[256], comb[256], binout[256], asmf[256], obj[256], exe[256];
|
||||||
char rt[1024], cmd[4096];
|
char rt[1024], cmd[4096];
|
||||||
snprintf(src, sizeof src, "/tmp/at997_%d.ww", pid);
|
snprintf(src, sizeof src, "/tmp/at997_%d.ww", pid);
|
||||||
|
snprintf(binout, sizeof binout, "/tmp/at997_%d", pid);
|
||||||
snprintf(comb, sizeof comb, "/tmp/at997_%d.combined.ww", pid);
|
snprintf(comb, sizeof comb, "/tmp/at997_%d.combined.ww", pid);
|
||||||
snprintf(asmf, sizeof asmf, "/tmp/at997_%d.s", pid);
|
snprintf(asmf, sizeof asmf, "/tmp/at997_%d.s", pid);
|
||||||
snprintf(obj, sizeof obj, "/tmp/at997_%d.o", pid);
|
snprintf(obj, sizeof obj, "/tmp/at997_%d.o", pid);
|
||||||
@@ -78,10 +79,12 @@ run_fixture(const char *bin)
|
|||||||
snprintf(cmd, sizeof cmd, "cp test/wcc/data/attest_pass.ww %s", src);
|
snprintf(cmd, sizeof cmd, "cp test/wcc/data/attest_pass.ww %s", src);
|
||||||
if (runwait(cmd) != 0) { fprintf(stderr, "997 FAIL: cp\n"); return 1; }
|
if (runwait(cmd) != 0) { fprintf(stderr, "997 FAIL: cp\n"); return 1; }
|
||||||
|
|
||||||
/* ww_ww build resolves `import rt` into the combined unit; it exits
|
/* `ww_ww test -c -o <stem>` (compile-only) resolves imports AND auto-
|
||||||
* nonzero (no main — that is what -T synthesizes), so we gate on the
|
* bundles lib/test (#17) into <stem>.combined.ww — the synth's run()
|
||||||
* combined ARTIFACT existing, not the exit code. */
|
* callee must link below — WITHOUT running the harness. We gate on the
|
||||||
snprintf(cmd, sizeof cmd, "%s/ww_ww build %s > /dev/null 2>&1", bin, src);
|
* combined ARTIFACT existing; `-o` keeps the binary off the CWD. */
|
||||||
|
snprintf(cmd, sizeof cmd, "%s/ww_ww test -c -o %s %s > /dev/null 2>&1",
|
||||||
|
bin, binout, src);
|
||||||
runwait(cmd);
|
runwait(cmd);
|
||||||
if (access(comb, 0) != 0) {
|
if (access(comb, 0) != 0) {
|
||||||
fprintf(stderr, "997 FAIL: ww_ww build produced no %s\n", comb);
|
fprintf(stderr, "997 FAIL: ww_ww build produced no %s\n", comb);
|
||||||
@@ -101,23 +104,38 @@ run_fixture(const char *bin)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
unlink(src); unlink(comb); unlink(asmf); unlink(obj); unlink(exe);
|
unlink(src); unlink(comb); unlink(asmf); unlink(obj); unlink(exe);
|
||||||
|
unlink(binout);
|
||||||
|
char tmp[300];
|
||||||
|
snprintf(tmp, sizeof tmp, "%s.s", binout); unlink(tmp);
|
||||||
|
snprintf(tmp, sizeof tmp, "%s.o", binout); unlink(tmp);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* byteid — `w6c -T` and `w6c_ww -T` on the same source must agree. */
|
/* byteid — `w6c -T` and `w6c_ww -T` on the same unit must agree. The #17
|
||||||
|
* synth's run() callee only resolves against the auto-bundled lib/test, so
|
||||||
|
* we byte-compare the lib/test-INCLUSIVE combined (raw -T of the fixture
|
||||||
|
* would loud-reject the undefined run); `ww test -c` writes that combined. */
|
||||||
static int
|
static int
|
||||||
byteid(const char *bin)
|
byteid(const char *bin)
|
||||||
{
|
{
|
||||||
int pid = getpid();
|
int pid = getpid();
|
||||||
char cs[256], ws[256], cmd[4096];
|
char stem[256], comb[256], cs[256], ws[256], cmd[4096];
|
||||||
|
snprintf(stem, sizeof stem, "/tmp/at997_bid_%d", pid);
|
||||||
|
snprintf(comb, sizeof comb, "%s.combined.ww", stem);
|
||||||
snprintf(cs, sizeof cs, "/tmp/at997_bid_c_%d.s", pid);
|
snprintf(cs, sizeof cs, "/tmp/at997_bid_c_%d.s", pid);
|
||||||
snprintf(ws, sizeof ws, "/tmp/at997_bid_w_%d.s", pid);
|
snprintf(ws, sizeof ws, "/tmp/at997_bid_w_%d.s", pid);
|
||||||
|
|
||||||
snprintf(cmd, sizeof cmd, "%s/w6c -T test/wcc/data/attest_pass.ww -o %s 2>/dev/null",
|
snprintf(cmd, sizeof cmd,
|
||||||
bin, cs);
|
"%s/ww test -c -o %s test/wcc/data/attest_pass.ww > /dev/null 2>&1",
|
||||||
|
bin, stem);
|
||||||
|
runwait(cmd);
|
||||||
|
if (access(comb, 0) != 0) {
|
||||||
|
fprintf(stderr, "997 FAIL: ww test -c produced no %s\n", comb);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
snprintf(cmd, sizeof cmd, "%s/w6c -T %s -o %s 2>/dev/null", bin, comb, cs);
|
||||||
if (runwait(cmd) != 0) { fprintf(stderr, "997 FAIL: w6c -T (byteid)\n"); return 1; }
|
if (runwait(cmd) != 0) { fprintf(stderr, "997 FAIL: w6c -T (byteid)\n"); return 1; }
|
||||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -T test/wcc/data/attest_pass.ww -o %s 2>/dev/null",
|
snprintf(cmd, sizeof cmd, "%s/w6c_ww -T %s -o %s 2>/dev/null", bin, comb, ws);
|
||||||
bin, ws);
|
|
||||||
if (runwait(cmd) != 0) { fprintf(stderr, "997 FAIL: w6c_ww -T (byteid)\n"); return 1; }
|
if (runwait(cmd) != 0) { fprintf(stderr, "997 FAIL: w6c_ww -T (byteid)\n"); return 1; }
|
||||||
|
|
||||||
char *bc = NULL, *bw = NULL;
|
char *bc = NULL, *bw = NULL;
|
||||||
@@ -131,7 +149,11 @@ byteid(const char *bin)
|
|||||||
rc = 1;
|
rc = 1;
|
||||||
}
|
}
|
||||||
free(bc); free(bw);
|
free(bc); free(bw);
|
||||||
unlink(cs); unlink(ws);
|
unlink(comb); unlink(cs); unlink(ws);
|
||||||
|
char tmp[300];
|
||||||
|
snprintf(tmp, sizeof tmp, "%s.s", stem); unlink(tmp);
|
||||||
|
snprintf(tmp, sizeof tmp, "%s.o", stem); unlink(tmp);
|
||||||
|
unlink(stem);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,10 +202,12 @@ static int
|
|||||||
nondrop(const char *bin)
|
nondrop(const char *bin)
|
||||||
{
|
{
|
||||||
int pid = getpid();
|
int pid = getpid();
|
||||||
char wp[256], wt[256], cp[256], cmd[4096];
|
char wp[256], wt[256], cp[256], stem[256], comb[300], cmd[4096];
|
||||||
snprintf(wp, sizeof wp, "/tmp/at997nd_wp_%d.s", pid);
|
snprintf(wp, sizeof wp, "/tmp/at997nd_wp_%d.s", pid);
|
||||||
snprintf(wt, sizeof wt, "/tmp/at997nd_wt_%d.s", pid);
|
snprintf(wt, sizeof wt, "/tmp/at997nd_wt_%d.s", pid);
|
||||||
snprintf(cp, sizeof cp, "/tmp/at997nd_cp_%d.s", pid);
|
snprintf(cp, sizeof cp, "/tmp/at997nd_cp_%d.s", pid);
|
||||||
|
snprintf(stem, sizeof stem, "/tmp/at997nd_c_%d", pid);
|
||||||
|
snprintf(comb, sizeof comb, "%s.combined.ww", stem);
|
||||||
|
|
||||||
snprintf(cmd, sizeof cmd,
|
snprintf(cmd, sizeof cmd,
|
||||||
"%s/w6c_ww test/wcc/data/attest_nondrop.ww -o %s 2>/dev/null", bin, wp);
|
"%s/w6c_ww test/wcc/data/attest_nondrop.ww -o %s 2>/dev/null", bin, wp);
|
||||||
@@ -191,8 +215,14 @@ nondrop(const char *bin)
|
|||||||
fprintf(stderr, "997 FAIL: w6c_ww non-T nondrop compile\n");
|
fprintf(stderr, "997 FAIL: w6c_ww non-T nondrop compile\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
/* the -T compile synthesizes a main calling lib/test's run(), so it
|
||||||
|
* needs the lib/test-inclusive combined `ww test -c` writes. */
|
||||||
snprintf(cmd, sizeof cmd,
|
snprintf(cmd, sizeof cmd,
|
||||||
"%s/w6c_ww -T test/wcc/data/attest_nondrop.ww -o %s 2>/dev/null", bin, wt);
|
"%s/ww test -c -o %s test/wcc/data/attest_nondrop.ww > /dev/null 2>&1",
|
||||||
|
bin, stem);
|
||||||
|
runwait(cmd);
|
||||||
|
snprintf(cmd, sizeof cmd,
|
||||||
|
"%s/w6c_ww -T %s -o %s 2>/dev/null", bin, comb, wt);
|
||||||
if (runwait(cmd) != 0) {
|
if (runwait(cmd) != 0) {
|
||||||
fprintf(stderr, "997 FAIL: w6c_ww -T nondrop compile\n");
|
fprintf(stderr, "997 FAIL: w6c_ww -T nondrop compile\n");
|
||||||
return 1;
|
return 1;
|
||||||
@@ -232,7 +262,11 @@ nondrop(const char *bin)
|
|||||||
}
|
}
|
||||||
free(bc); free(bw);
|
free(bc); free(bw);
|
||||||
}
|
}
|
||||||
unlink(wp); unlink(wt); unlink(cp);
|
unlink(wp); unlink(wt); unlink(cp); unlink(comb);
|
||||||
|
char tmp[300];
|
||||||
|
snprintf(tmp, sizeof tmp, "%s.s", stem); unlink(tmp);
|
||||||
|
snprintf(tmp, sizeof tmp, "%s.o", stem); unlink(tmp);
|
||||||
|
unlink(stem);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
31
test/wcc/data/attest_record.ww
Normal file
31
test/wcc/data/attest_record.ww
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// @test fixture for #17 record-and-continue: a mix of one passing test,
|
||||||
|
// then the three runtime fault classes the lib/test fork+wait4 runner
|
||||||
|
// must each catch and PROCEED past (failed assert → SIGABRT, nil deref →
|
||||||
|
// SIGSEGV, divide-by-zero → SIGFPE), then a final passing test to prove
|
||||||
|
// the run reaches the tail after every failure. Driver:
|
||||||
|
// test/wcc/911_attest_record.c. The runner reports in source order, so
|
||||||
|
// the expected per-test verdicts are pinned positionally there.
|
||||||
|
|
||||||
|
package data;
|
||||||
|
|
||||||
|
@test fn rec_pass_a() void = {
|
||||||
|
assert(1 + 1 == 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn rec_assert() void = {
|
||||||
|
assert(1 == 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn rec_segv() void = {
|
||||||
|
let p: *i32 = nil: *i32;
|
||||||
|
*p = 7i32;
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn rec_div0() void = {
|
||||||
|
let z: i32 = 0;
|
||||||
|
let _: i32 = 1 / z;
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn rec_pass_b() void = {
|
||||||
|
assert(true);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user