ww: keep generated test support file-scoped

This commit is contained in:
2026-08-14 04:20:29 +09:00
parent 9b015e764c
commit 695983f7c3
5 changed files with 85 additions and 34 deletions

View File

@@ -3245,13 +3245,17 @@ check_file(Checker *c, Node *file)
* already emits the qualified call. wwstage twin in check.ww. */
if (c->is_test) {
int present = 0;
/* The synthetic runner belongs to file->sourceid. An import in an
* earlier module-reset section neither supplies nor uses its binding. */
for (Node *u = file->list; u; u = u->next) {
if (c->test_target != NULL && u->kind == N_USE
&& !u->imported && u->usepath
&& !u->imported && u->sourceid == file->sourceid
&& u->usepath
&& (strcmp(u->usepath, c->test_target) == 0
|| strcmp(u->usepath, c->test_module) == 0))
u->used = 1;
if (u->kind == N_USE && !u->imported
&& u->sourceid == file->sourceid
&& u->usepath && strcmp(u->usepath, c->test_module) == 0) {
present = 1;
}

View File

@@ -4581,7 +4581,10 @@ tested target through
`--test-target-package <canonical-path>` so a command variant declared `main`
does not collide with the dispatcher's own synthesized `main`; this is
compiler-generated wiring, not source alias syntax. Zero-test dispatchers mark
their compiler-owned target/support metadata imports consumed.
their compiler-owned target/support metadata imports consumed. That private
binding is installed or marked consumed only in the generated dispatcher's
source section; an import from an earlier test-file section neither supplies
nor satisfies it.
Vendor expansion changes only canonical identity and physical selection. A
source spelling such as `lib.codec` can resolve to

View File

@@ -7655,15 +7655,18 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
// cstage cmd/wcc/check.c.
if (c.istest != 0) {
let present: bool = false;
// The synthetic runner belongs to file.sourceid. An import in an
// earlier module-reset section neither supplies nor uses its binding.
let su: *syntax.node = file.list;
for (su != nil) {
if (c.testtarget.len > 0 && su.kind == syntax.nkind.N_USE
&& su.imported == 0
&& su.imported == 0 && su.sourceid == file.sourceid
&& (syntax.streq(su.usepath, c.testtarget)
|| syntax.streq(su.usepath, c.testmodule))) {
su.used = 1i32;
};
if (su.kind == syntax.nkind.N_USE && su.imported == 0
&& su.sourceid == file.sourceid
&& syntax.streq(su.usepath, c.testmodule)) {
present = true;
};

View File

@@ -134,7 +134,6 @@ fn nooprow(label: str, src: str) void = {
" return 0;\n",
"};\n"));
nooprow("free_alloc_roundtrip", strings.concat(
"import rt;\n",
"export fn main() i32 = {\n",
" let p: *i64 = alloc(11i64)!;\n",
" free(p);\n",

View File

@@ -52,6 +52,7 @@ type ent = struct {
cite: str, // task #59 entry for non-ID modes
sentinel: str, // probe-only: proof the module body landed
moddir: str, // probe-only: completeness key
direct: bool, // build moddir itself; used when it exports no source name
};
// Row constructors; the roster appends their results directly (#34
@@ -59,7 +60,7 @@ type ent = struct {
fn fx(f: str) ent = {
let e: ent;
e.fixture = f; e.probe = ""; e.inc = ""; e.mode = MID;
e.cite = ""; e.sentinel = ""; e.moddir = "";
e.cite = ""; e.sentinel = ""; e.moddir = ""; e.direct = false;
return e;
};
@@ -82,10 +83,10 @@ fn pri(p: str, inc: str, sentinel: str, moddir: str) ent = {
return e;
};
// Dir-mode entry: byte-id the module's COMPOSED test package (every
// *_test.ww under moddir, module-reset separators, byte-lex order
// the coordinator's pkgcombined shape) through both driver stages.
// Needed once a package's test files share a helpers file and stop
// Dir-mode entry: byte-id the module's composed test package (every
// *_test.ww under moddir, module-reset separators, byte-lex order) through
// both driver stages. The separators preserve each source file's import
// scope. Needed once a package's test files share a helpers file and stop
// being standalone single-file roots (the B3 strings split).
fn dx(moddir: str) ent = {
let e: ent = fx("");
@@ -93,7 +94,17 @@ fn dx(moddir: str) ent = {
return e;
};
// The 60-unit roster. Graduation history lives in git (the retired C
// Direct-directory entry for packages with no exported source name. Such a
// package cannot have a valid nonblank import probe until blank imports exist.
fn dr(moddir: str, sentinel: str) ent = {
let e: ent = fx("");
e.moddir = moddir;
e.sentinel = sentinel;
e.direct = true;
return e;
};
// The 56-unit roster. Graduation history lives in git (the retired C
// carrier's table comments); cites are kept only where a non-ID pin
// would need them.
fn corpus() []ent = {
@@ -120,7 +131,7 @@ fn corpus() []ent = {
append(es, fx("lib/math/random/random_test.ww"));
append(es, fx("lib/memio/memio_test.ww"));
append(es, fx("lib/os/os_test.ww"));
append(es, pr("package main;\nimport os.exec;\nfn main() i32 = { return 0; };\n",
append(es, pr("package main;\nimport os.exec;\nfn main() i32 = { return exec.termination.EXIT as i32; };\n",
"package exec;", "lib/os/exec"));
append(es, fx("lib/regex/regex_test.ww"));
append(es, fx("lib/strconv/ftos_test.ww"));
@@ -136,26 +147,25 @@ fn corpus() []ent = {
append(es, fx("lib/bufio/stream_test.ww"));
append(es, fx("lib/bufio/scanner_test.ww"));
append(es, fx("lib/fmt/fmt_test.ww"));
append(es, pr("package main;\nimport sort;\nfn main() i32 = { return 0; };\n",
append(es, pr("package main;\nimport sort;\nfn use(v: sort.cmpfunc) void = {};\nfn main() i32 = { return 0; };\n",
"package sort;", "lib/sort"));
append(es, pr("package main;\nimport path;\nfn main() i32 = { return 0; };\n",
append(es, pr("package main;\nimport path;\nfn use(v: path.buffer) void = {};\nfn main() i32 = { return 0; };\n",
"package path;", "lib/path"));
append(es, pr("package main;\nimport endian;\nfn main() i32 = { return 0; };\n",
append(es, pr("package main;\nimport endian;\nfn main() i32 = { let b: [2]u8; return endian.begetu16(b[0:2]): i32; };\n",
"package endian;", "lib/endian"));
append(es, pr("package main;\nimport net;\nfn main() i32 = { return 0; };\n",
append(es, pr("package main;\nimport net;\nfn use(v: net.sockaddrin) void = {};\nfn main() i32 = { return 0; };\n",
"package net;", "lib/net"));
append(es, pr("package main;\nimport hash;\nfn main() i32 = { return 0; };\n",
append(es, pr("package main;\nimport hash;\nfn use(v: hash.hash) void = {};\nfn main() i32 = { return 0; };\n",
"package hash;", "lib/hash"));
// fnv lives below the driver's default root, so its canonical
// directory is found from the explicit parent import root.
append(es, pri("package main;\nimport fnv;\nfn main() i32 = { return 0; };\n",
append(es, pri("package main;\nimport fnv;\nfn main() i32 = { let b: []u8; let _ = fnv.fnv1a(b); return 0; };\n",
"lib/hash", "package fnv;", "lib/hash/fnv"));
// Sentinel is a fn, not `package math;` — lib/math is also package
// math, so the package clause alone would not prove the right body.
append(es, pr("package main;\nimport crypto.math;\nfn main() i32 = { return 0; };\n",
append(es, pr("package main;\nimport crypto.math;\nfn main() i32 = { return math.rotl32(0u32, 0): i32; };\n",
"fn rotl32", "lib/crypto/math"));
append(es, pr("package main;\nimport c.libc;\nfn main() i32 = { return 0; };\n",
"package libc;", "lib/c/libc"));
append(es, dr("lib/c/libc", "package libc;"));
// root-only, ZERO-dep build -S: the one driver-leg edge every
// import probe misses (dep-count-0 unit composition). Folded in
// from the retired 815/940/951 driver-parity carriers. No
@@ -245,6 +255,28 @@ fn catasm(sepdir: str) str = {
return out;
};
fn hasunit(sepdir: str) bool = {
if (!testenv.isdir(sepdir)) { return false; };
let names: []str = testenv.listdir(sepdir);
let i: i32 = 0;
for (i < names.len) {
if (strings.hassuffix(names[i], ".unit.ww")) { return true; };
i += 1;
};
return false;
};
fn hasasm(sepdir: str) bool = {
if (!testenv.isdir(sepdir)) { return false; };
let names: []str = testenv.listdir(sepdir);
let i: i32 = 0;
for (i < names.len) {
if (strings.hassuffix(names[i], ".s")) { return true; };
i += 1;
};
return false;
};
fn buildstage(td: str, name: str, drv: str, sub: str, incs: []str,
stem: str, base: str) bool = {
let av: []str = [];
@@ -280,10 +312,10 @@ fn checkone(e: *ent) void = {
e.fixture)));
} else { if (e.probe.len != 0) {
testenv.writefile(strings.concat(td, "/", base), e.probe);
} else { if (e.direct) {
base = strings.concat(testenv.repo(), "/", e.moddir);
} else {
// dir-mode: compose every *_test.ww under moddir exactly
// as pkgcombined does (external group: test sources only,
// module-reset separators; testenv.listdir sorts).
// Dir-mode keeps every source in its own module-reset section.
base = "combined.ww";
let srcdir: str = strings.concat(testenv.repo(), "/",
e.moddir);
@@ -301,7 +333,7 @@ fn checkone(e: *ent) void = {
ni += 1;
};
testenv.writefile(strings.concat(td, "/", base), body);
}; };
}; }; };
// Most packages resolve from the driver's canonical lib/ root.
// Nested packages whose tests use a bare leaf import carry an
@@ -317,8 +349,9 @@ fn checkone(e: *ent) void = {
// lib/test); import probes carry their own fn main(), which -T
// loud-rejects (910), so they `build -S`.
let sub: str = "test";
if (e.probe.len != 0) { sub = "build"; };
let stem: str = stripext(base);
if (e.probe.len != 0 || e.direct) { sub = "build"; };
let stem: str = "direct";
if (!e.direct) { stem = stripext(base); };
let stemc: str = strings.concat(td, "/c_", stem);
let stemw: str = strings.concat(td, "/w_", stem);
@@ -327,10 +360,14 @@ fn checkone(e: *ent) void = {
let we: bool = buildstage(td, "wwstage", testenv.driver("ww_ww"), sub,
incs, stemw, base);
// the cstage build resolves the units; its __root.unit.ww proves
// resolution ran. WWREJECT still requires cstage to compile.
if (!ce || !testenv.exists(strings.concat(stemc,
".sepwork/__root.unit.ww"))) {
// The cstage build resolves the units. A source probe owns __root; a
// directly selected package retains its canonical import identity.
// WWREJECT still requires cstage to compile.
let resolvedunit: str = strings.concat(stemc,
".sepwork/__root.unit.ww");
let resolved: bool = testenv.exists(resolvedunit);
if (e.direct) { resolved = hasunit(strings.concat(stemc, ".sepwork")); };
if (!ce || !resolved) {
fail(label, "cstage produced no resolved unit");
};
@@ -367,10 +404,15 @@ fn checkone(e: *ent) void = {
};
if (!we) { fail(label, "wwstage rejected"); };
let cs: str = catasm(strings.concat(stemc, ".sepwork"));
let ws: str = catasm(strings.concat(stemw, ".sepwork"));
// two empty concats compare equal — that green covers nothing
if (cs.len == 0 || ws.len == 0) {
let csep: str = strings.concat(stemc, ".sepwork");
let wsep: str = strings.concat(stemw, ".sepwork");
let cs: str = catasm(csep);
let ws: str = catasm(wsep);
// Two missing assembly sets compare equal and cover nothing. A directly
// selected declaration-only package legitimately emits an empty .s file;
// its resolved owner unit and sentinel above prove the source was compiled.
if (!hasasm(csep) || !hasasm(wsep)
|| (!e.direct && (cs.len == 0 || ws.len == 0))) {
fail(label, "empty .s concat");
};
let idsame: bool = testenv.same(cs, ws);