wcc,ww: @test under separate compilation (M4 E1, #79)
Make `ww test --sep` work the Hare +test way: the -T synth test-main emits a qualified test.run, and the test package is injected as an ordinary separately-compiled dependency instead of splicing lib/test source into a flat unit. Additive — combined stays the default and 910/997 are untouched (their migration is M4 E2). - compiler synth (both stages): the -T main emits N_DOT test.run plus a synthetic N_USE "test"; cmd/wcc/check.c + selfhost/cmd/wcc/check.ww. - driver (both stages): build_one_sep gains is_test, injects the test package as a root dep, and passes -T to the root; do_test --sep routes a single-file test through the sep producer; cmd/ww/main.c + selfhost/cmd/ww/main.ww. - 989_septest_run gate: ww test --sep on both stages, run-exit + cs==ww byte-id of the sep .s, non-vacuous. The synth's test.run is left ty_err by the checker in both regimes (lib/test's run is scope-keyed under "" not "test"; cgen emits the correct CALL via run's //ww:module test directive) — wwstage tolerates it like cstage (rule-10). The genuine fix, module-keying run under sep so the call type-resolves, is #80. w6c_ww/wwdump_ww/ww_ww move (their embedded source changed); w6a_ww/w6l_ww and the combined codegen output are unchanged.
This commit is contained in:
12
Makefile
12
Makefile
@@ -569,6 +569,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_separchive_run \
|
||||
$(BIN)/test_pkgcache_run \
|
||||
$(BIN)/test_c6soak_run \
|
||||
$(BIN)/test_septest_run \
|
||||
$(BIN)/test_floatlit_run \
|
||||
$(BIN)/test_checked_run \
|
||||
$(BIN)/test_floatarr_run \
|
||||
@@ -3219,6 +3220,17 @@ $(BIN)/test_c6soak_run: test/wcc/989_c6soak_run.c $(BIN)/ww $(BIN)/ww_ww \
|
||||
$(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# 989_septest_run — M4 E1 (#79-B) gate: `ww test --sep`. Drives BOTH driver
|
||||
# stages on a passing + a failing inline @test fixture (COLD per-(case,stage)
|
||||
# WW_PKGCACHE), asserting run-exit (pass=0, fail=1 = the non-vacuity teeth),
|
||||
# cs==ww per-pkg .s/.wwi (rule 10), and the synth `CALL test.run` in __root.s
|
||||
# (the -T synth fired under sep). Needs both driver + both compiler + both
|
||||
# linker stages + libwwrt.a for the test-binary link.
|
||||
$(BIN)/test_septest_run: test/wcc/989_septest_run.c $(BIN)/ww $(BIN)/ww_ww \
|
||||
$(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \
|
||||
$(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_floatlit_run: test/wcc/989_floatlit_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
@@ -3175,16 +3175,40 @@ check_file(Checker *c, Node *file)
|
||||
scope_define_in_module(c->cur, tab->str, NULL, SK_VAR,
|
||||
tt, tab);
|
||||
|
||||
/* return run(__wwtests); */
|
||||
/* return test.run(__wwtests);
|
||||
* QUALIFIED, not bare `run`: under the sep producer
|
||||
* lib/test is a real imported package, so the call must
|
||||
* carry the `test` module qualifier (N_DOT base ident
|
||||
* `test`, member `run`). The combined path tags the
|
||||
* auto-bundled lib/test `//ww:module test` too, so the
|
||||
* qualified form resolves+mangles identically there
|
||||
* (test.run either way) — byte-neutral. The base ident
|
||||
* resolves through a synthetic N_USE injected below. */
|
||||
Node *arg = newnode(c->a, N_IDENT, fp);
|
||||
arg->str = "__wwtests";
|
||||
Node *call = newnode(c->a, N_CALL, fp);
|
||||
call->lhs = newnode(c->a, N_IDENT, fp);
|
||||
call->lhs->str = "run";
|
||||
Node *dot = newnode(c->a, N_DOT, fp);
|
||||
dot->lhs = newnode(c->a, N_IDENT, fp);
|
||||
dot->lhs->str = "test";
|
||||
dot->str = "run";
|
||||
call->lhs = dot;
|
||||
call->list = arg;
|
||||
Node *ret = newnode(c->a, N_RETURN, fp);
|
||||
ret->lhs = call;
|
||||
body->list = ret;
|
||||
/* synth `use test;` so the N_DOT base ident binds as a
|
||||
* module qualifier (SK_USE) and use_path maps `test` to
|
||||
* its import path. Mirror the typedecl-pass N_USE install
|
||||
* (check.c:2892). Appended to file->list below; emits no
|
||||
* asm (cgen keys off module-tagged decls). */
|
||||
Node *usenode = newnode(c->a, N_USE, fp);
|
||||
usenode->str = "test";
|
||||
usenode->strlen = 4;
|
||||
usenode->usepath = "test";
|
||||
scope_define(c->cur, "test", SK_USE, NULL, usenode);
|
||||
Node *ul = file->list;
|
||||
if (ul == NULL) file->list = usenode;
|
||||
else { while (ul->next) ul = ul->next; ul->next = usenode; }
|
||||
}
|
||||
|
||||
Node *m = newnode(c->a, N_FNDECL, fp);
|
||||
|
||||
@@ -1132,7 +1132,7 @@ cache_store(struct sepgraph *g, int pi, const char *manifest,
|
||||
static int
|
||||
build_one_sep(const char *src, int entry_is_dir, const char *out,
|
||||
const char *objstem, const char *extra_includes, const char *extra_libs,
|
||||
const char *extra_libdirs)
|
||||
const char *extra_libdirs, int is_test)
|
||||
{
|
||||
const char *c6 = toolpath("WW_W6C", "w6c");
|
||||
const char *a6 = toolpath("WW_W6A", "w6a");
|
||||
@@ -1197,7 +1197,27 @@ build_one_sep(const char *src, int entry_is_dir, const char *out,
|
||||
struct sepgraph *g = calloc(1, sizeof *g);
|
||||
if (g == NULL) return 1;
|
||||
int root = sep_find_or_add(g, "", src, entry_is_dir);
|
||||
if (root < 0 || sep_scan_pkg(g, root, srcdir) < 0) { free(g); return 1; }
|
||||
if (root < 0) { free(g); return 1; }
|
||||
/* #79 (-T): lib/test is the synth main's `test.run` callee but @test
|
||||
* files never `import test;`. Inject it as a direct dep of the root so
|
||||
* sep_scan_pkg pulls test + its transitive deps; the producer adds -T
|
||||
* to the root and `test.run` links against test's `.a`. Mirrors the
|
||||
* combined path's auto-bundle (build_one is_test) and the sep_scan_file
|
||||
* dedup-guarded dep append. */
|
||||
if (is_test) {
|
||||
char tpath[1024];
|
||||
int tdir = 0;
|
||||
if (locate_import(srcdir, "test", tpath, sizeof tpath, &tdir)) {
|
||||
int ti = sep_find_or_add(g, "test", tpath, tdir);
|
||||
if (ti < 0) { free(g); return 1; }
|
||||
int seen = 0;
|
||||
for (int k = 0; k < g->pkg[root].ndeps; k++)
|
||||
if (g->pkg[root].deps[k] == ti) { seen = 1; break; }
|
||||
if (!seen && g->pkg[root].ndeps < SEP_MAXPKG)
|
||||
g->pkg[root].deps[g->pkg[root].ndeps++] = ti;
|
||||
}
|
||||
}
|
||||
if (sep_scan_pkg(g, root, srcdir) < 0) { free(g); return 1; }
|
||||
for (int i = 0; i < g->n; i++) g->pkg[i].color = 0;
|
||||
int *order = calloc((size_t)g->n, sizeof *order);
|
||||
int *stack = calloc((size_t)g->n, sizeof *stack);
|
||||
@@ -1233,8 +1253,11 @@ build_one_sep(const char *src, int entry_is_dir, const char *out,
|
||||
* export-check rejects. Skip -I for the root; its `.wwi`
|
||||
* is never consumed. */
|
||||
if (pi == root)
|
||||
snprintf(cmd, sizeof cmd, "%s -c -o %s %s",
|
||||
c6, asmf, unitf);
|
||||
/* #79: the root carries -T under `ww test --sep`
|
||||
* so w6c synthesizes the test main. Deps never
|
||||
* get -T. */
|
||||
snprintf(cmd, sizeof cmd, "%s %s-c -o %s %s",
|
||||
c6, is_test ? "-T " : "", asmf, unitf);
|
||||
else
|
||||
snprintf(cmd, sizeof cmd, "%s -c -I %s -o %s %s",
|
||||
c6, wwi, asmf, unitf);
|
||||
@@ -1542,7 +1565,7 @@ do_build(int argc, char **argv)
|
||||
}
|
||||
if (want_sep)
|
||||
return build_one_sep(resolved, is_dir, out, objstem, incs,
|
||||
libs, libdirs);
|
||||
libs, libdirs, 0);
|
||||
return build_one(resolved, is_dir, out, objstem, incs, libs, libdirs, 0);
|
||||
}
|
||||
|
||||
@@ -1606,12 +1629,21 @@ do_test(int argc, char **argv)
|
||||
* wwstage twin (selfhost/cmd/ww/main.ww dotest). */
|
||||
int compileonly = 0;
|
||||
char outstem[1024] = {0};
|
||||
/* #79 E1: --sep routes a single-file/module test through the
|
||||
* separate-compilation producer (build_one_sep, is_test=1) instead of
|
||||
* the amalgamator. Additive (combined stays default); dir-mode --sep is
|
||||
* deferred to E2. */
|
||||
int use_sep = 0;
|
||||
/* #17: an optional second positional after the target is a fnmatch
|
||||
* name-filter pattern, forwarded to the test binary as argv[1]. Only
|
||||
* meaningful for a single test file/module — rejected in dir mode. */
|
||||
const char *pattern = NULL;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
if (argv[i][0] == '-') {
|
||||
if (strcmp(argv[i], "--sep") == 0) {
|
||||
use_sep = 1;
|
||||
continue;
|
||||
}
|
||||
if (argv[i][1] == 'I') {
|
||||
const char *dir;
|
||||
if (argv[i][2]) {
|
||||
@@ -1664,8 +1696,12 @@ do_test(int argc, char **argv)
|
||||
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;
|
||||
int br = use_sep
|
||||
? build_one_sep(resolved, is_dir, outp,
|
||||
outstem[0] ? outstem : NULL, incs, "", "", 1)
|
||||
: build_one(resolved, is_dir, outp,
|
||||
outstem[0] ? outstem : NULL, incs, "", "", 1);
|
||||
if (br != 0) return 1;
|
||||
if (compileonly) return 0;
|
||||
int rc = run_test_bin(outp, pattern);
|
||||
if (!outstem[0]) unlink(outp);
|
||||
@@ -1677,8 +1713,12 @@ do_test(int argc, char **argv)
|
||||
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;
|
||||
int br = use_sep
|
||||
? build_one_sep(target, 0, outp, outstem[0] ? outstem : NULL,
|
||||
incs, "", "", 1)
|
||||
: build_one(target, 0, outp, outstem[0] ? outstem : NULL,
|
||||
incs, "", "", 1);
|
||||
if (br != 0) return 1;
|
||||
if (compileonly) return 0;
|
||||
int rc = run_test_bin(outp, pattern);
|
||||
if (!outstem[0]) unlink(outp);
|
||||
@@ -1692,6 +1732,13 @@ do_test(int argc, char **argv)
|
||||
fprintf(stderr, "ww test: -c/-o need a single test file\n");
|
||||
return 2;
|
||||
}
|
||||
/* #79 E1: dir-mode --sep is deferred to E2 — single-file/module is
|
||||
* enough to prove the sep test path. Loud-reject rather than silently
|
||||
* fall back to the combined per-file build. */
|
||||
if (use_sep) {
|
||||
fprintf(stderr, "ww test: --sep needs a single test file\n");
|
||||
return 2;
|
||||
}
|
||||
/* #17: a name-filter pattern is per-binary; directory mode builds one
|
||||
* binary per *_test.ww, so a single pattern can't sensibly route. */
|
||||
if (pattern) {
|
||||
|
||||
@@ -14229,6 +14229,22 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
// (ref/harec/src/check.c:1566-1581).
|
||||
if (ms != nil && (ms.skind == syntax.skind.SK_USE || ms.use_alias != 0i32)) {
|
||||
s = syntax.scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), nm);
|
||||
// Module-qualified callee whose leaf isn't scope-keyed
|
||||
// under its module: align to cstage, which stamps ty_err
|
||||
// here and lets cgen emit the call (cmd/wcc/check.c:1834-
|
||||
// 1843; rule-10). The -T synth's `test.run` hits this in
|
||||
// BOTH combined and sep (Q2-confirmed): lib/test's `run` is
|
||||
// scope-keyed under "" not "test" — the directive-for-cgen
|
||||
// vs declmod-for-scope keying split, the module-qualified
|
||||
// arm of #27. cgen still emits the correct CALL test.run
|
||||
// from run's `//ww:module test` directive. Load-bearing
|
||||
// until #80 module-keys run under sep so the synth call
|
||||
// type-resolves; NOT an M4-transient.
|
||||
if (s == nil) {
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
callee.type_ = c.tc.tyerr: *void; // N_DOT node itself (asserttyped checks it)
|
||||
return nil;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (s != nil) { if (s.skind == syntax.skind.SK_FN) { if (s.decl != nil) {
|
||||
@@ -17275,13 +17291,34 @@ export fn checkfile(c: *checker, file: *syntax.node) void = {
|
||||
let arg: *syntax.node = syntax.newnode(syntax.nkind.N_IDENT, pf, pl, pc);
|
||||
arg.str = "__wwtests";
|
||||
let call: *syntax.node = syntax.newnode(syntax.nkind.N_CALL, pf, pl, pc);
|
||||
let cid: *syntax.node = syntax.newnode(syntax.nkind.N_IDENT, pf, pl, pc);
|
||||
cid.str = "run";
|
||||
call.lhs = cid;
|
||||
// QUALIFIED test.run (N_DOT base ident `test`, member `run`):
|
||||
// the sep producer sees lib/test as a real imported package,
|
||||
// so the call must carry the module qualifier; the combined
|
||||
// path tags auto-bundled lib/test `//ww:module test` too, so
|
||||
// it resolves+mangles identically (test.run). Cstage twin:
|
||||
// cmd/wcc/check.c synth. The base ident binds through the
|
||||
// synth N_USE below.
|
||||
let dot: *syntax.node = syntax.newnode(syntax.nkind.N_DOT, pf, pl, pc);
|
||||
let did: *syntax.node = syntax.newnode(syntax.nkind.N_IDENT, pf, pl, pc);
|
||||
did.str = "test";
|
||||
dot.lhs = did;
|
||||
dot.str = "run";
|
||||
call.lhs = dot;
|
||||
call.list = arg;
|
||||
let ret: *syntax.node = syntax.newnode(syntax.nkind.N_RETURN, pf, pl, pc);
|
||||
ret.lhs = call;
|
||||
body.list = ret;
|
||||
// synth `use test;` so the N_DOT base binds as a module
|
||||
// qualifier (SK_USE) and usepathfor maps `test` to its path.
|
||||
// Mirror installdecl's N_USE SK_USE install. Appended to
|
||||
// file.list below; emits no asm.
|
||||
let usenode: *syntax.node = syntax.newnode(syntax.nkind.N_USE, pf, pl, pc);
|
||||
usenode.str = "test";
|
||||
usenode.usepath = "test";
|
||||
syntax.scopedefine(c.top, "test", syntax.skind.SK_USE, nil, usenode);
|
||||
let ul: *syntax.node = file.list;
|
||||
if (ul == nil) { file.list = usenode; }
|
||||
else { for (ul.next != nil) { ul = ul.next; }; ul.next = usenode; };
|
||||
};
|
||||
let m: *syntax.node = syntax.newnode(syntax.nkind.N_FNDECL, pf, pl, pc);
|
||||
m.str = "main";
|
||||
|
||||
@@ -3486,6 +3486,22 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
// (ref/harec/src/check.c:1566-1581).
|
||||
if (ms != nil && (ms.skind == syntax.skind.SK_USE || ms.use_alias != 0i32)) {
|
||||
s = syntax.scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), nm);
|
||||
// Module-qualified callee whose leaf isn't scope-keyed
|
||||
// under its module: align to cstage, which stamps ty_err
|
||||
// here and lets cgen emit the call (cmd/wcc/check.c:1834-
|
||||
// 1843; rule-10). The -T synth's `test.run` hits this in
|
||||
// BOTH combined and sep (Q2-confirmed): lib/test's `run` is
|
||||
// scope-keyed under "" not "test" — the directive-for-cgen
|
||||
// vs declmod-for-scope keying split, the module-qualified
|
||||
// arm of #27. cgen still emits the correct CALL test.run
|
||||
// from run's `//ww:module test` directive. Load-bearing
|
||||
// until #80 module-keys run under sep so the synth call
|
||||
// type-resolves; NOT an M4-transient.
|
||||
if (s == nil) {
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
callee.type_ = c.tc.tyerr: *void; // N_DOT node itself (asserttyped checks it)
|
||||
return nil;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (s != nil) { if (s.skind == syntax.skind.SK_FN) { if (s.decl != nil) {
|
||||
@@ -6532,13 +6548,34 @@ export fn checkfile(c: *checker, file: *syntax.node) void = {
|
||||
let arg: *syntax.node = syntax.newnode(syntax.nkind.N_IDENT, pf, pl, pc);
|
||||
arg.str = "__wwtests";
|
||||
let call: *syntax.node = syntax.newnode(syntax.nkind.N_CALL, pf, pl, pc);
|
||||
let cid: *syntax.node = syntax.newnode(syntax.nkind.N_IDENT, pf, pl, pc);
|
||||
cid.str = "run";
|
||||
call.lhs = cid;
|
||||
// QUALIFIED test.run (N_DOT base ident `test`, member `run`):
|
||||
// the sep producer sees lib/test as a real imported package,
|
||||
// so the call must carry the module qualifier; the combined
|
||||
// path tags auto-bundled lib/test `//ww:module test` too, so
|
||||
// it resolves+mangles identically (test.run). Cstage twin:
|
||||
// cmd/wcc/check.c synth. The base ident binds through the
|
||||
// synth N_USE below.
|
||||
let dot: *syntax.node = syntax.newnode(syntax.nkind.N_DOT, pf, pl, pc);
|
||||
let did: *syntax.node = syntax.newnode(syntax.nkind.N_IDENT, pf, pl, pc);
|
||||
did.str = "test";
|
||||
dot.lhs = did;
|
||||
dot.str = "run";
|
||||
call.lhs = dot;
|
||||
call.list = arg;
|
||||
let ret: *syntax.node = syntax.newnode(syntax.nkind.N_RETURN, pf, pl, pc);
|
||||
ret.lhs = call;
|
||||
body.list = ret;
|
||||
// synth `use test;` so the N_DOT base binds as a module
|
||||
// qualifier (SK_USE) and usepathfor maps `test` to its path.
|
||||
// Mirror installdecl's N_USE SK_USE install. Appended to
|
||||
// file.list below; emits no asm.
|
||||
let usenode: *syntax.node = syntax.newnode(syntax.nkind.N_USE, pf, pl, pc);
|
||||
usenode.str = "test";
|
||||
usenode.usepath = "test";
|
||||
syntax.scopedefine(c.top, "test", syntax.skind.SK_USE, nil, usenode);
|
||||
let ul: *syntax.node = file.list;
|
||||
if (ul == nil) { file.list = usenode; }
|
||||
else { for (ul.next != nil) { ul = ul.next; }; ul.next = usenode; };
|
||||
};
|
||||
let m: *syntax.node = syntax.newnode(syntax.nkind.N_FNDECL, pf, pl, pc);
|
||||
m.str = "main";
|
||||
|
||||
@@ -4374,7 +4374,7 @@ fn cachestore(cacheroot: *u8, g: *sepgraph, pi: i32, manifest: *u8,
|
||||
// reverse-topo `w6l` of the `.a` set + libwwrt.a. Side files land in a
|
||||
// cold `<stem>.sepwork` scratch dir. Twin of cstage build_one_sep.
|
||||
fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
||||
objstem: *u8, incs: *u8, lf: *lflags) i32 = {
|
||||
objstem: *u8, incs: *u8, lf: *lflags, istest: i32) i32 = {
|
||||
let c6: *u8 = joinpathlit(selfdir, "w6c_ww");
|
||||
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
|
||||
let l6: *u8 = joinpathlit(selfdir, "w6l_ww");
|
||||
@@ -4470,6 +4470,31 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
||||
let g: *sepgraph = alloc(sepgraph{pkg = pkgslot, n = 0})!;
|
||||
let root: i32 = sepfindoradd(g, "\0".ptr, src, entryisdir);
|
||||
if (root < 0) { return 1; };
|
||||
// #79 (-T): lib/test is the synth main's `test.run` callee but @test
|
||||
// files never `import test;`. Inject it as a direct dep of the root so
|
||||
// sepscanpkg pulls test + its transitive deps; the producer adds -T to
|
||||
// the root and `test.run` links against test's `.a`. Mirrors the
|
||||
// combined path's auto-bundle (buildone istest) and sepscanfile dedup.
|
||||
if (istest != 0) {
|
||||
let td: i32 = 0;
|
||||
let tp: *u8 = locateimport(searchpath.ptr, "test".ptr, "test".len: u64, &td);
|
||||
if (tp != nil) {
|
||||
let ti: i32 = sepfindoradd(g, "test\0".ptr, tp, td);
|
||||
if (ti < 0) { return 1; };
|
||||
let seen: bool = false;
|
||||
let m: i32 = 0;
|
||||
for (m < g.pkg[root].ndeps) {
|
||||
if (g.pkg[root].deps[m] == ti) { seen = true; };
|
||||
m += 1;
|
||||
};
|
||||
if (!seen) {
|
||||
if (g.pkg[root].ndeps < SEP_MAXPKG) {
|
||||
g.pkg[root].deps[g.pkg[root].ndeps] = ti;
|
||||
g.pkg[root].ndeps += 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (sepscanpkg(g, root, searchpath.ptr) < 0) { return 1; };
|
||||
|
||||
// Reset colors, reverse-topo.
|
||||
@@ -4517,13 +4542,17 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
||||
// LOCAL type (the root is never imported), which the
|
||||
// export-check rejects. Build a shorter root argv
|
||||
// without the -I/wwi pair; root's `.wwi` is unconsumed.
|
||||
// #79: the root carries -T under `ww test --sep` so w6c
|
||||
// synthesizes the test main; deps never get -T.
|
||||
let roott: bool = (pi == root) && (istest != 0);
|
||||
let alen: u64 = 8u64;
|
||||
if (pi == root) { alen = 6u64; };
|
||||
if (pi == root) { alen = 6u64; if (roott) { alen = 7u64; }; };
|
||||
let argv: []*u8 = alloc([], alen)!;
|
||||
argv.len = (alen: i32);
|
||||
argv[0] = "w6c\0".ptr;
|
||||
argv[1] = "-c\0".ptr;
|
||||
let k: u64 = 2u64;
|
||||
let k: u64 = 1u64;
|
||||
if (roott) { argv[k] = "-T\0".ptr; k += 1u64; };
|
||||
argv[k] = "-c\0".ptr; k += 1u64;
|
||||
if (pi != root) {
|
||||
argv[k] = "-I\0".ptr; k += 1u64;
|
||||
argv[k] = wwi; k += 1u64;
|
||||
@@ -5128,7 +5157,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
lf.libs = libs.ptr;
|
||||
lf.nlibs = nlibs;
|
||||
if (wantsep != 0) {
|
||||
return buildonesep(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf);
|
||||
return buildonesep(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf, 0i32);
|
||||
};
|
||||
return buildone(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf, 0i32);
|
||||
};
|
||||
@@ -5319,7 +5348,7 @@ 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, compileonly: i32, outstem: *u8, pattern: *u8) i32 = {
|
||||
fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *u8, pattern: *u8, usesep: i32) i32 = {
|
||||
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
tmp.len = os.PATH_MAX;
|
||||
// -o redirects the binary + its combined (objstem, T3) to <stem>; the
|
||||
@@ -5333,7 +5362,20 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *
|
||||
makeruntmp(tmp.ptr);
|
||||
outp = tmp.ptr;
|
||||
};
|
||||
if (buildone(selfdir, src, 0, outp, objstem, incs, nil, 1i32) != 0) {
|
||||
// #79 E1: --sep routes the test build through the separate-compilation
|
||||
// producer (buildonesep, istest=1) instead of the amalgamator.
|
||||
let bres: i32 = 0;
|
||||
if (usesep != 0) {
|
||||
let lf: lflags;
|
||||
lf.libdirs = nil;
|
||||
lf.nlibdirs = 0;
|
||||
lf.libs = nil;
|
||||
lf.nlibs = 0;
|
||||
bres = buildonesep(selfdir, src, 0, outp, objstem, incs, &lf, 1i32);
|
||||
} else {
|
||||
bres = buildone(selfdir, src, 0, outp, objstem, incs, nil, 1i32);
|
||||
};
|
||||
if (bres != 0) {
|
||||
if (outstem == nil) { os.remove(pathstr(outp)); };
|
||||
return 1;
|
||||
};
|
||||
@@ -5463,11 +5505,17 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
// twin: cmd/ww/main.c do_test (error wording identical).
|
||||
let compileonly: i32 = 0;
|
||||
let outstem: *u8 = nil;
|
||||
// #79 E1: --sep routes a single-file test through the separate-
|
||||
// compilation producer (buildonesep, istest=1). Dir-mode --sep is
|
||||
// deferred to E2. cstage twin: do_test use_sep.
|
||||
let usesep: i32 = 0;
|
||||
let i: i32 = start;
|
||||
for (i < argc) {
|
||||
let p: *u8 = argv[i];
|
||||
if (p[0u64] == 45u8) { // '-'
|
||||
if (p[1u64] == 73u8) { // '-I'
|
||||
if (cstreqlit(p, "--sep")) { // #79: separate-compile test
|
||||
usesep = 1;
|
||||
} else { if (p[1u64] == 73u8) { // '-I'
|
||||
let dir: *u8 = nil;
|
||||
if (p[2u64] != 0u8) {
|
||||
dir = p + 2u64;
|
||||
@@ -5501,7 +5549,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
} else {
|
||||
cerr("ww test: unknown flag\n");
|
||||
return 2;
|
||||
}; }; };
|
||||
}; }; }; }; // #79: extra close for the --sep else
|
||||
} else {
|
||||
if (target == nil) { target = p; }
|
||||
else { if (patarg == nil) { patarg = p; }; };
|
||||
@@ -5516,7 +5564,7 @@ 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, compileonly, outstem, patarg);
|
||||
return runsingletest(selfdir, target, incs.ptr, compileonly, outstem, patarg, usesep);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5524,6 +5572,11 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
cerr("ww test: -c/-o need a single test file\n");
|
||||
return 2;
|
||||
};
|
||||
// #79 E1: dir-mode --sep deferred to E2; single-file proves the path.
|
||||
if (usesep != 0) {
|
||||
cerr("ww test: --sep needs a single test file\n");
|
||||
return 2;
|
||||
};
|
||||
// #17: a name-filter pattern is per-binary; dir mode builds one binary
|
||||
// per *_test.ww, so a single pattern can't route. cstage twin parity.
|
||||
if (patarg != nil) {
|
||||
|
||||
@@ -1500,7 +1500,7 @@ fn cachestore(cacheroot: *u8, g: *sepgraph, pi: i32, manifest: *u8,
|
||||
// reverse-topo `w6l` of the `.a` set + libwwrt.a. Side files land in a
|
||||
// cold `<stem>.sepwork` scratch dir. Twin of cstage build_one_sep.
|
||||
fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
||||
objstem: *u8, incs: *u8, lf: *lflags) i32 = {
|
||||
objstem: *u8, incs: *u8, lf: *lflags, istest: i32) i32 = {
|
||||
let c6: *u8 = joinpathlit(selfdir, "w6c_ww");
|
||||
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
|
||||
let l6: *u8 = joinpathlit(selfdir, "w6l_ww");
|
||||
@@ -1596,6 +1596,31 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
||||
let g: *sepgraph = alloc(sepgraph{pkg = pkgslot, n = 0})!;
|
||||
let root: i32 = sepfindoradd(g, "\0".ptr, src, entryisdir);
|
||||
if (root < 0) { return 1; };
|
||||
// #79 (-T): lib/test is the synth main's `test.run` callee but @test
|
||||
// files never `import test;`. Inject it as a direct dep of the root so
|
||||
// sepscanpkg pulls test + its transitive deps; the producer adds -T to
|
||||
// the root and `test.run` links against test's `.a`. Mirrors the
|
||||
// combined path's auto-bundle (buildone istest) and sepscanfile dedup.
|
||||
if (istest != 0) {
|
||||
let td: i32 = 0;
|
||||
let tp: *u8 = locateimport(searchpath.ptr, "test".ptr, "test".len: u64, &td);
|
||||
if (tp != nil) {
|
||||
let ti: i32 = sepfindoradd(g, "test\0".ptr, tp, td);
|
||||
if (ti < 0) { return 1; };
|
||||
let seen: bool = false;
|
||||
let m: i32 = 0;
|
||||
for (m < g.pkg[root].ndeps) {
|
||||
if (g.pkg[root].deps[m] == ti) { seen = true; };
|
||||
m += 1;
|
||||
};
|
||||
if (!seen) {
|
||||
if (g.pkg[root].ndeps < SEP_MAXPKG) {
|
||||
g.pkg[root].deps[g.pkg[root].ndeps] = ti;
|
||||
g.pkg[root].ndeps += 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (sepscanpkg(g, root, searchpath.ptr) < 0) { return 1; };
|
||||
|
||||
// Reset colors, reverse-topo.
|
||||
@@ -1643,13 +1668,17 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
||||
// LOCAL type (the root is never imported), which the
|
||||
// export-check rejects. Build a shorter root argv
|
||||
// without the -I/wwi pair; root's `.wwi` is unconsumed.
|
||||
// #79: the root carries -T under `ww test --sep` so w6c
|
||||
// synthesizes the test main; deps never get -T.
|
||||
let roott: bool = (pi == root) && (istest != 0);
|
||||
let alen: u64 = 8u64;
|
||||
if (pi == root) { alen = 6u64; };
|
||||
if (pi == root) { alen = 6u64; if (roott) { alen = 7u64; }; };
|
||||
let argv: []*u8 = alloc([], alen)!;
|
||||
argv.len = (alen: i32);
|
||||
argv[0] = "w6c\0".ptr;
|
||||
argv[1] = "-c\0".ptr;
|
||||
let k: u64 = 2u64;
|
||||
let k: u64 = 1u64;
|
||||
if (roott) { argv[k] = "-T\0".ptr; k += 1u64; };
|
||||
argv[k] = "-c\0".ptr; k += 1u64;
|
||||
if (pi != root) {
|
||||
argv[k] = "-I\0".ptr; k += 1u64;
|
||||
argv[k] = wwi; k += 1u64;
|
||||
@@ -2254,7 +2283,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
lf.libs = libs.ptr;
|
||||
lf.nlibs = nlibs;
|
||||
if (wantsep != 0) {
|
||||
return buildonesep(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf);
|
||||
return buildonesep(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf, 0i32);
|
||||
};
|
||||
return buildone(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf, 0i32);
|
||||
};
|
||||
@@ -2445,7 +2474,7 @@ 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, compileonly: i32, outstem: *u8, pattern: *u8) i32 = {
|
||||
fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *u8, pattern: *u8, usesep: i32) i32 = {
|
||||
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
tmp.len = os.PATH_MAX;
|
||||
// -o redirects the binary + its combined (objstem, T3) to <stem>; the
|
||||
@@ -2459,7 +2488,20 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *
|
||||
makeruntmp(tmp.ptr);
|
||||
outp = tmp.ptr;
|
||||
};
|
||||
if (buildone(selfdir, src, 0, outp, objstem, incs, nil, 1i32) != 0) {
|
||||
// #79 E1: --sep routes the test build through the separate-compilation
|
||||
// producer (buildonesep, istest=1) instead of the amalgamator.
|
||||
let bres: i32 = 0;
|
||||
if (usesep != 0) {
|
||||
let lf: lflags;
|
||||
lf.libdirs = nil;
|
||||
lf.nlibdirs = 0;
|
||||
lf.libs = nil;
|
||||
lf.nlibs = 0;
|
||||
bres = buildonesep(selfdir, src, 0, outp, objstem, incs, &lf, 1i32);
|
||||
} else {
|
||||
bres = buildone(selfdir, src, 0, outp, objstem, incs, nil, 1i32);
|
||||
};
|
||||
if (bres != 0) {
|
||||
if (outstem == nil) { os.remove(pathstr(outp)); };
|
||||
return 1;
|
||||
};
|
||||
@@ -2589,11 +2631,17 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
// twin: cmd/ww/main.c do_test (error wording identical).
|
||||
let compileonly: i32 = 0;
|
||||
let outstem: *u8 = nil;
|
||||
// #79 E1: --sep routes a single-file test through the separate-
|
||||
// compilation producer (buildonesep, istest=1). Dir-mode --sep is
|
||||
// deferred to E2. cstage twin: do_test use_sep.
|
||||
let usesep: i32 = 0;
|
||||
let i: i32 = start;
|
||||
for (i < argc) {
|
||||
let p: *u8 = argv[i];
|
||||
if (p[0u64] == 45u8) { // '-'
|
||||
if (p[1u64] == 73u8) { // '-I'
|
||||
if (cstreqlit(p, "--sep")) { // #79: separate-compile test
|
||||
usesep = 1;
|
||||
} else { if (p[1u64] == 73u8) { // '-I'
|
||||
let dir: *u8 = nil;
|
||||
if (p[2u64] != 0u8) {
|
||||
dir = p + 2u64;
|
||||
@@ -2627,7 +2675,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
} else {
|
||||
cerr("ww test: unknown flag\n");
|
||||
return 2;
|
||||
}; }; };
|
||||
}; }; }; }; // #79: extra close for the --sep else
|
||||
} else {
|
||||
if (target == nil) { target = p; }
|
||||
else { if (patarg == nil) { patarg = p; }; };
|
||||
@@ -2642,7 +2690,7 @@ 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, compileonly, outstem, patarg);
|
||||
return runsingletest(selfdir, target, incs.ptr, compileonly, outstem, patarg, usesep);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2650,6 +2698,11 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
cerr("ww test: -c/-o need a single test file\n");
|
||||
return 2;
|
||||
};
|
||||
// #79 E1: dir-mode --sep deferred to E2; single-file proves the path.
|
||||
if (usesep != 0) {
|
||||
cerr("ww test: --sep needs a single test file\n");
|
||||
return 2;
|
||||
};
|
||||
// #17: a name-filter pattern is per-binary; dir mode builds one binary
|
||||
// per *_test.ww, so a single pattern can't route. cstage twin parity.
|
||||
if (patarg != nil) {
|
||||
|
||||
@@ -14229,6 +14229,22 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
// (ref/harec/src/check.c:1566-1581).
|
||||
if (ms != nil && (ms.skind == syntax.skind.SK_USE || ms.use_alias != 0i32)) {
|
||||
s = syntax.scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), nm);
|
||||
// Module-qualified callee whose leaf isn't scope-keyed
|
||||
// under its module: align to cstage, which stamps ty_err
|
||||
// here and lets cgen emit the call (cmd/wcc/check.c:1834-
|
||||
// 1843; rule-10). The -T synth's `test.run` hits this in
|
||||
// BOTH combined and sep (Q2-confirmed): lib/test's `run` is
|
||||
// scope-keyed under "" not "test" — the directive-for-cgen
|
||||
// vs declmod-for-scope keying split, the module-qualified
|
||||
// arm of #27. cgen still emits the correct CALL test.run
|
||||
// from run's `//ww:module test` directive. Load-bearing
|
||||
// until #80 module-keys run under sep so the synth call
|
||||
// type-resolves; NOT an M4-transient.
|
||||
if (s == nil) {
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
callee.type_ = c.tc.tyerr: *void; // N_DOT node itself (asserttyped checks it)
|
||||
return nil;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (s != nil) { if (s.skind == syntax.skind.SK_FN) { if (s.decl != nil) {
|
||||
@@ -17275,13 +17291,34 @@ export fn checkfile(c: *checker, file: *syntax.node) void = {
|
||||
let arg: *syntax.node = syntax.newnode(syntax.nkind.N_IDENT, pf, pl, pc);
|
||||
arg.str = "__wwtests";
|
||||
let call: *syntax.node = syntax.newnode(syntax.nkind.N_CALL, pf, pl, pc);
|
||||
let cid: *syntax.node = syntax.newnode(syntax.nkind.N_IDENT, pf, pl, pc);
|
||||
cid.str = "run";
|
||||
call.lhs = cid;
|
||||
// QUALIFIED test.run (N_DOT base ident `test`, member `run`):
|
||||
// the sep producer sees lib/test as a real imported package,
|
||||
// so the call must carry the module qualifier; the combined
|
||||
// path tags auto-bundled lib/test `//ww:module test` too, so
|
||||
// it resolves+mangles identically (test.run). Cstage twin:
|
||||
// cmd/wcc/check.c synth. The base ident binds through the
|
||||
// synth N_USE below.
|
||||
let dot: *syntax.node = syntax.newnode(syntax.nkind.N_DOT, pf, pl, pc);
|
||||
let did: *syntax.node = syntax.newnode(syntax.nkind.N_IDENT, pf, pl, pc);
|
||||
did.str = "test";
|
||||
dot.lhs = did;
|
||||
dot.str = "run";
|
||||
call.lhs = dot;
|
||||
call.list = arg;
|
||||
let ret: *syntax.node = syntax.newnode(syntax.nkind.N_RETURN, pf, pl, pc);
|
||||
ret.lhs = call;
|
||||
body.list = ret;
|
||||
// synth `use test;` so the N_DOT base binds as a module
|
||||
// qualifier (SK_USE) and usepathfor maps `test` to its path.
|
||||
// Mirror installdecl's N_USE SK_USE install. Appended to
|
||||
// file.list below; emits no asm.
|
||||
let usenode: *syntax.node = syntax.newnode(syntax.nkind.N_USE, pf, pl, pc);
|
||||
usenode.str = "test";
|
||||
usenode.usepath = "test";
|
||||
syntax.scopedefine(c.top, "test", syntax.skind.SK_USE, nil, usenode);
|
||||
let ul: *syntax.node = file.list;
|
||||
if (ul == nil) { file.list = usenode; }
|
||||
else { for (ul.next != nil) { ul = ul.next; }; ul.next = usenode; };
|
||||
};
|
||||
let m: *syntax.node = syntax.newnode(syntax.nkind.N_FNDECL, pf, pl, pc);
|
||||
m.str = "main";
|
||||
|
||||
260
test/wcc/989_septest_run.c
Normal file
260
test/wcc/989_septest_run.c
Normal file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* 989_septest_run — M4 E1 (#79-B) regression gate: `ww test --sep`.
|
||||
*
|
||||
* E1 makes `ww test` work under the separate-compilation producer as an
|
||||
* ADDITIVE opt-in (combined stays default; 910/997 untouched). The synth
|
||||
* test-main emits `test.run(__wwtests)` (N_DOT, qualified) + injects a
|
||||
* synthetic `use test;`; the driver injects `test` as a graph dep of the
|
||||
* root, passes `-T` to the root producer only, and routes a single-file
|
||||
* `ww test --sep` through build_one_sep(is_test=1). This gate is the
|
||||
* STANDING regression for that capability until E2 migrates 997.
|
||||
*
|
||||
* Asserts (all COLD — fresh per-(case,stage) WW_PKGCACHE, scratch wiped):
|
||||
* 1. RUN-EXIT: `ww test --sep <fixture>` exits with the expected code for
|
||||
* BOTH driver stages — 0 when every @test passes, non-zero when one
|
||||
* fails. The failing row is the non-vacuity teeth: it proves the @tests
|
||||
* genuinely RUN under sep (a no-op --sep that linked an empty main would
|
||||
* exit 0 on the failing fixture and the gate would catch it).
|
||||
* 2. cs==ww (rule 10): the cstage `ww` and wwstage `ww_ww` sep-drivers emit
|
||||
* byte-identical per-package .s/.wwi for the passing fixture — including
|
||||
* __root.s, which carries the synth `CALL test.run`.
|
||||
* 3. SYNTH PRESENCE (non-vacuity on the -T path): the passing fixture's
|
||||
* __root.s defines `TEXT main` and emits `CALL test.run(SB)` — proving
|
||||
* the qualified-synth -T path fired under sep, not a stray combined fall-
|
||||
* back.
|
||||
*
|
||||
* Light wwstage-driver test (CLAUDE.md rule 14): every intermediate is
|
||||
* `-o`-redirected to /tmp, so it is phase-1 parallel-safe. Models
|
||||
* 989_c6soak_run.c conventions; 989 prefix per the sep-gate precedent.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.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 **outbuf, size_t *outlen)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return -1;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long n = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if (n < 0) { fclose(f); return -1; }
|
||||
char *b = malloc((size_t)n + 1);
|
||||
if (!b) { fclose(f); return -1; }
|
||||
if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; }
|
||||
b[n] = '\0';
|
||||
fclose(f);
|
||||
*outbuf = b;
|
||||
*outlen = (size_t)n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
files_eq(const char *a, const char *b)
|
||||
{
|
||||
char *ba = NULL, *bb = NULL;
|
||||
size_t na = 0, nb = 0;
|
||||
if (slurp(a, &ba, &na) < 0 || slurp(b, &bb, &nb) < 0) {
|
||||
free(ba); free(bb);
|
||||
return -1;
|
||||
}
|
||||
int eq = (na == nb && memcmp(ba, bb, na) == 0);
|
||||
free(ba); free(bb);
|
||||
return eq ? 0 : 1;
|
||||
}
|
||||
|
||||
/* 0 if `needle` occurs in the file at `path`, 1 if absent, -1 on read err. */
|
||||
static int
|
||||
file_contains(const char *path, const char *needle)
|
||||
{
|
||||
char *b = NULL;
|
||||
size_t n = 0;
|
||||
if (slurp(path, &b, &n) < 0) return -1;
|
||||
int found = (strstr(b, needle) != NULL);
|
||||
free(b);
|
||||
return found ? 0 : 1;
|
||||
}
|
||||
|
||||
static int
|
||||
write_file(const char *path, const char *body)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(body, f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* cs==ww over a sep test build's per-package output: every .s/.wwi the
|
||||
* cstage driver produced in `csdir` must be byte-identical to the wwstage
|
||||
* driver's same-named file in `wwdir`. Returns the count of mismatches. */
|
||||
static int
|
||||
cmp_sepwork(const char *csdir, const char *wwdir, const char *label)
|
||||
{
|
||||
DIR *d = opendir(csdir);
|
||||
if (!d) {
|
||||
fprintf(stderr, "septest FAIL: %s — no cs sepwork %s\n", label, csdir);
|
||||
return 1;
|
||||
}
|
||||
int bad = 0, seen = 0;
|
||||
struct dirent *ent;
|
||||
while ((ent = readdir(d)) != NULL) {
|
||||
const char *nm = ent->d_name;
|
||||
size_t nl = strlen(nm);
|
||||
int is_s = (nl > 2 && strcmp(nm + nl - 2, ".s") == 0);
|
||||
int is_wwi = (nl > 4 && strcmp(nm + nl - 4, ".wwi") == 0);
|
||||
if (!is_s && !is_wwi) continue;
|
||||
seen++;
|
||||
char a[2048], b[2048];
|
||||
snprintf(a, sizeof a, "%s/%s", csdir, nm);
|
||||
snprintf(b, sizeof b, "%s/%s", wwdir, nm);
|
||||
if (files_eq(a, b) != 0) {
|
||||
fprintf(stderr, "septest FAIL: %s — cs!=ww for %s (rule 10)\n",
|
||||
label, nm);
|
||||
bad++;
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
/* An existing-but-empty sepwork would pass the cs==ww loop vacuously. */
|
||||
if (seen == 0) {
|
||||
fprintf(stderr, "septest FAIL: %s — no .s/.wwi in %s\n", label, csdir);
|
||||
bad++;
|
||||
}
|
||||
return bad;
|
||||
}
|
||||
|
||||
struct tcase {
|
||||
const char *label;
|
||||
const char *src; /* inline fixture written to <td>/<label>.ww */
|
||||
int expect; /* expected `ww test --sep` run-exit */
|
||||
int byteid; /* run the cs==ww + synth-presence checks */
|
||||
};
|
||||
|
||||
static struct tcase cases[] = {
|
||||
/* every @test passes → run-exit 0; the byte-id + synth anchor. */
|
||||
{ "pass",
|
||||
"package septest;\n"
|
||||
"@test fn t_arith() void = {\n"
|
||||
" let a: i32 = 2;\n"
|
||||
" if (a + 3 != 5) { abort(); };\n"
|
||||
"};\n"
|
||||
"@test fn t_again() void = {\n"
|
||||
" let s: str = \"ok\";\n"
|
||||
" if (len(s) != 2) { abort(); };\n"
|
||||
"};\n", 0, 1 },
|
||||
/* a failing @test → run-exit 1: proves the tests actually RUN under
|
||||
* sep (the non-vacuity teeth). */
|
||||
{ "fail",
|
||||
"package septest;\n"
|
||||
"@test fn t_bad() void = {\n"
|
||||
" if (1 + 1 == 2) { abort(); };\n"
|
||||
"};\n", 1, 0 },
|
||||
{ NULL, NULL, 0, 0 },
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = absbin();
|
||||
if (!bin) return 1;
|
||||
|
||||
char td[64], cmd[8192];
|
||||
int fail = 0;
|
||||
snprintf(td, sizeof td, "/tmp/wwseptest_%d", getpid());
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||
runwait(cmd);
|
||||
mkdir(td, 0755);
|
||||
|
||||
for (int i = 0; cases[i].label; i++) {
|
||||
struct tcase *t = &cases[i];
|
||||
char rootww[1024];
|
||||
snprintf(rootww, sizeof rootww, "%s/%s.ww", td, t->label);
|
||||
if (write_file(rootww, t->src)) { fail++; continue; }
|
||||
|
||||
struct { const char *drv, *tag; int rc; }
|
||||
stg[] = { { "ww", "cs", -1 }, { "ww_ww", "ww", -1 } };
|
||||
for (int s = 0; s < 2; s++) {
|
||||
char prog[1024];
|
||||
/* `.bin` infix keeps the test binary distinct from the
|
||||
* `<label>.ww` SOURCE; -o leaves <prog>.sepwork for the
|
||||
* byte-id compare and runs the binary (exit = test result). */
|
||||
snprintf(prog, sizeof prog, "%s/%s.%s.bin", td, t->label, stg[s].tag);
|
||||
/* COLD per-(case,stage) cache so every package compiles fresh
|
||||
* (a warm cache would skip the per-pkg .s/.wwi this gate
|
||||
* inspects — mirrors 989_c6soak / 989_pkgcache). */
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"WW_PKGCACHE='%s/cache.%s.%s' %s/%s test --sep -o %s %s "
|
||||
">/dev/null 2>&1",
|
||||
td, t->label, stg[s].tag, bin, stg[s].drv, prog, rootww);
|
||||
stg[s].rc = runwait(cmd);
|
||||
}
|
||||
|
||||
/* 1. RUN-EXIT: both stages exit with the expected code. */
|
||||
if (stg[0].rc != t->expect || stg[1].rc != t->expect) {
|
||||
fprintf(stderr, "septest FAIL: %s exits cs=%d ww=%d (expected %d)\n",
|
||||
t->label, stg[0].rc, stg[1].rc, t->expect);
|
||||
fail++;
|
||||
}
|
||||
|
||||
if (!t->byteid) continue;
|
||||
|
||||
char csdir[1024], wwdir[1024];
|
||||
snprintf(csdir, sizeof csdir, "%s/%s.cs.bin.sepwork", td, t->label);
|
||||
snprintf(wwdir, sizeof wwdir, "%s/%s.ww.bin.sepwork", td, t->label);
|
||||
|
||||
/* 2. cs==ww (rule 10): per-package .s/.wwi byte-identical. */
|
||||
fail += cmp_sepwork(csdir, wwdir, t->label);
|
||||
|
||||
/* 3. SYNTH PRESENCE: the -T synth fired under sep. */
|
||||
char rs[2048];
|
||||
snprintf(rs, sizeof rs, "%s/__root.s", csdir);
|
||||
if (file_contains(rs, "TEXT main") != 0) {
|
||||
fprintf(stderr, "septest FAIL: %s — __root.s lacks `TEXT main` "
|
||||
"(synth -T main missing)\n", t->label);
|
||||
fail++;
|
||||
}
|
||||
if (file_contains(rs, "CALL\ttest.run(SB)") != 0) {
|
||||
fprintf(stderr, "septest FAIL: %s — __root.s lacks "
|
||||
"`CALL test.run(SB)` (qualified synth call missing)\n",
|
||||
t->label);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||
runwait(cmd);
|
||||
if (fail) {
|
||||
fprintf(stderr, "septest: %d check(s) failed\n", fail);
|
||||
return 1;
|
||||
}
|
||||
printf("septest: `ww test --sep` run-exit (pass=0, fail=1) on both driver "
|
||||
"stages + cs==ww per-pkg .s/.wwi + synth `CALL test.run` in __root.s\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user