cmd: build directory package test variants

This commit is contained in:
2026-08-12 03:46:47 +09:00
parent e59881cb30
commit 9862f0c05f
4 changed files with 914 additions and 270 deletions

View File

@@ -353,21 +353,20 @@ fn locateimport(dirs: *u8, name: *u8, namelen: u64,
return nil;
};
// Go's contract: only *_test.ww is a test source. A line-leading @test
// declaration anywhere else would be silently dropped by a non-T build
// (#6, the Hare model), so directory enumeration rejects it loudly.
// This is the wwstage twin of cmd/ww/main.c:file_has_line_test.
fn dirfileattest(dirpath: *u8, name: *u8) bool = {
// Go's contract: only *_test.ww is a test source. Ask the compiler parser,
// rather than a textual attribute scan, whether a production source contains
// @test; otherwise valid whitespace/comments could silently drop a test.
fn dirfileattest(dirpath: *u8, name: *u8) i32 = {
let path: *u8 = joinpath(dirpath, name);
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
if (fd < 0) { return false; };
if (fd < 0) { return -1; };
let sr: (i64 | os.oserror) = os.filesize(fd);
let n: i64 = -1i64;
match (sr) {
case let v: i64 => n = v;
case let e: os.oserror => { os.close(fd); return false; };
case let e: os.oserror => { os.close(fd); return -1; };
};
if (n <= 0i64) { os.close(fd); return false; };
if (n < 0i64) { os.close(fd); return -1; };
let b: []u8 = alloc([], n: u64)!;
b.len = n: i32;
let rr: (i64 | os.oserror) = os.readall(fd, b.ptr, n: u64);
@@ -375,29 +374,55 @@ fn dirfileattest(dirpath: *u8, name: *u8) bool = {
let got: i64 = -1i64;
match (rr) {
case let v: i64 => got = v;
case let e: os.oserror => return false;
case let e: os.oserror => return -1;
};
if (got != n) { return false; };
let i: i32 = 0;
for (i < b.len) {
for (i < b.len && (b[i] == ' ' || b[i] == '\t'
|| b[i] == '\r')) { i += 1; };
if (i + 5 < b.len && b[i] == '@' && b[i + 1] == 't'
&& b[i + 2] == 'e' && b[i + 3] == 's'
&& b[i + 4] == 't'
&& (b[i + 5] == ' ' || b[i + 5] == '\t')) {
return true;
if (got != n) { return -1; };
// Preserve the directory loader's normalized package-clause diagnostic
// before the full parser performs language-level recovery. This is also
// the C/WW parity boundary for malformed clauses.
let il: syntax.lex;
syntax.lexinit(&il, pathstr(path), b.ptr, n: u64);
let ips: syntax.parser;
syntax.parserinit(&ips, &il);
let imports: *syntax.node = syntax.parseimports(&ips);
if (il.errs > 0 || ips.errs > 0) { return -1; };
if (imports.nmod.len == 0) {
cerrpos(pathstr(path), 1, 1);
cerr(": error: invalid or missing package clause\n");
return -1;
};
let l: syntax.lex;
syntax.lexinit(&l, pathstr(path), b.ptr, n: u64);
let ps: syntax.parser;
syntax.parserinit(&ps, &l);
let f: *syntax.node = syntax.parsefile(&ps);
if (l.errs > 0 || ps.errs > 0) { return -1; };
let d: *syntax.node = f.list;
for (d != nil) {
if (d.kind == syntax.nkind.N_FNDECL) {
let at: *syntax.node = d.attr;
for (at != nil) {
if (at.kind == syntax.nkind.N_ATTR
&& syntax.streq(at.str, "test")) {
return 1;
};
at = at.next;
};
};
for (i < b.len && b[i] != '\n') { i += 1; };
if (i < b.len) { i += 1; };
d = d.next;
};
return false;
return 0;
};
// Classify a directory entry for production enumeration: 1 keep,
// 0 skip (non-source or *_test.ww), -1 @test outside *_test.ww
// (caller diagnoses and fails).
fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64) i32 = {
def SEP_VARIANT_PRODUCTION: i32 = 0;
def SEP_VARIANT_SAME_TEST: i32 = 1;
def SEP_VARIANT_EXTERNAL: i32 = 2;
def SEP_TEST_SUPPORT_MODULE: str = "__wwtest";
// Classify a selected directory entry: 1 production, 2 test, 0 skipped,
// -1 @test outside *_test.ww, -2 non-regular source.
fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64,
variant: i32) i32 = {
// nlen<=3 guard kept: a bare ".ww" (len 3) is rejected here but
// would pass strings.hassuffix(".ww"); preserves cstage parity.
if (nlen <= 3u64) { return 0; };
@@ -405,7 +430,9 @@ fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64) i32 = {
s.ptr = name;
s.len = nlen: i32;
if (!strings.hassuffix(s, ".ww")) { return 0; };
if (strings.hassuffix(s, "_test.ww")) { return 0; };
let istest: bool = strings.hassuffix(s, "_test.ww");
if (istest && variant == SEP_VARIANT_PRODUCTION) { return 0; };
if (!istest && variant == SEP_VARIANT_EXTERNAL) { return 0; };
let source: *u8 = joinpath(dirpath, name);
let fi: os.filestat;
let sr: (void | os.oserror) = os.lstat(&fi, pathstr(source));
@@ -422,10 +449,45 @@ fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64) i32 = {
cerr(": package source is not a regular file\n");
return -2;
};
if (dirfileattest(dirpath, name)) { return -1; };
if (!istest) {
let attest: i32 = dirfileattest(dirpath, name);
if (attest < 0) { return -2; };
if (attest > 0) { return -1; };
};
if (istest) { return 2; };
return 1;
};
fn dirpackagename(path: *u8) *u8 = {
let view: str;
view.ptr = path;
view.len = cstrlen(path): i32;
let bufp: *u8;
let blen: u64;
bufp, blen = slurp(path);
if (bufp == nil) {
cerr("ww: cannot read source\n");
return nil;
};
let l: syntax.lex;
syntax.lexinit(&l, view, bufp, blen);
let ps: syntax.parser;
syntax.parserinit(&ps, &l);
let imports: *syntax.node = syntax.parseimports(&ps);
if (l.errs > 0 || ps.errs > 0) { return nil; };
if (imports.nmod.len == 0) {
cerrpos(view, 1, 1);
cerr(": error: invalid or missing package clause\n");
return nil;
};
if (imports.nmod.len >= 256) {
cerrpos(imports.file, imports.line, imports.col);
cerr(": error: package name is too long\n");
return nil;
};
return arenadupcstr(imports.nmod.ptr, imports.nmod.len: u64);
};
// Rule-10 byte-id requires cstage and wwstage sort the same way;
// memcmp is the locale-independent total order (mirrors
// ref/hare/sort/cmp/cmp.ha strs).
@@ -445,12 +507,10 @@ fn bytecmp(a: *u8, alen: u64, b: *u8, blen: u64) i32 = {
return 0;
};
// enumeratedir — list production *.ww paths of `dirpath` (less *_test.ww
// test sources), byte-sort. A line-leading @test in any other source is
// diagnosed here and returns -2. Returns one exact pointer array of
// NUL-terminated full paths. This is the sole directory-membership
// discovery path; the owning seppkg retains the list.
fn enumeratedir(dirpath: *u8) (**u8, i32) = {
// Enumerate a production, same-test, or external-test directory variant.
// Production files precede matching test files; each partition is byte-sorted.
fn enumeratedir(dirpath: *u8, variant: i32,
testpackage: *u8) (**u8, i32) = {
let fd: i32 = os.open(pathstr(dirpath), os.flag.RDONLY, 0i32);
if (fd < 0) { return nil: **u8, -1; };
// #65: grow-dynamic (mirror cstage enumerate_dir_ww realloc-doubling,
@@ -460,6 +520,7 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
let cap: i32 = 8;
let names: []*u8 = alloc([], cap: u64)!;
let nlens: []u64 = alloc([], cap: u64)!;
let kinds: []i32 = alloc([], cap: u64)!;
let n: i32 = 0;
let buf: []u8 = alloc([], 8192u64)!;
buf.len = 8192;
@@ -473,7 +534,7 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
let reclen: u64 = blo + (bhi * 256u64);
let nm: *u8 = buf.ptr + off + 19u64;
let nl: u64 = cstrlen(nm);
let cls: i32 = dirfileclass(dirpath, nm, nl);
let cls: i32 = dirfileclass(dirpath, nm, nl, variant);
if (cls == -1) {
cerr("ww: ");
cerr(pathstr(joinpath(dirpath, nm)));
@@ -486,23 +547,38 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
return nil: **u8, -2;
};
if (cls > 0) {
let full: *u8 = joinpath(dirpath, nm);
if (cls == 2) {
let pn: *u8 = dirpackagename(full);
if (pn == nil) {
os.close(fd);
return nil: **u8, -2;
};
if (testpackage == nil || !cstreq(pn, testpackage)) {
off += reclen;
continue;
};
};
if (n >= cap) {
let ncap: i32 = cap * 2;
let nn: []*u8 = alloc([], ncap: u64)!;
let nl2: []u64 = alloc([], ncap: u64)!;
let nk: []i32 = alloc([], ncap: u64)!;
let k: i32 = 0;
for (k < n) {
nn[k] = names[k];
nl2[k] = nlens[k];
nk[k] = kinds[k];
k += 1;
};
names = nn;
nlens = nl2;
kinds = nk;
cap = ncap;
};
let full: *u8 = joinpath(dirpath, nm);
names[n] = full;
nlens[n] = cstrlen(full);
kinds[n] = cls;
n += 1;
};
off += reclen;
@@ -524,8 +600,12 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
for (i < n) {
let j: i32 = i;
for (j > 0) {
if (bytecmp(names[j - 1], nlens[j - 1],
names[j], nlens[j]) <= 0) { j = 0; }
let c: i32 = kinds[j - 1] - kinds[j];
if (c == 0) {
c = bytecmp(names[j - 1], nlens[j - 1],
names[j], nlens[j]);
};
if (c <= 0) { j = 0; }
else {
let t: *u8 = names[j];
names[j] = names[j - 1];
@@ -533,6 +613,9 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
let tl: u64 = nlens[j];
nlens[j] = nlens[j - 1];
nlens[j - 1] = tl;
let tk: i32 = kinds[j];
kinds[j] = kinds[j - 1];
kinds[j - 1] = tk;
j -= 1;
};
};
@@ -541,6 +624,7 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
if (n == 0) {
os.free(names.ptr: *void, (cap: u64) * (size(*u8): u64));
os.free(nlens.ptr: *void, (cap: u64) * (size(u64): u64));
os.free(kinds.ptr: *void, (cap: u64) * (size(i32): u64));
return nil: **u8, 0;
};
let exact: []*u8 = alloc([], n: u64)!;
@@ -551,6 +635,7 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
// shape correct for the driver's allocations.
os.free(names.ptr: *void, (cap: u64) * (size(*u8): u64));
os.free(nlens.ptr: *void, (cap: u64) * (size(u64): u64));
os.free(kinds.ptr: *void, (cap: u64) * (size(i32): u64));
return exact.ptr, n;
};
@@ -637,9 +722,12 @@ type seppkg = struct {
path: *u8, // dotted import path, NUL-term; root path[0]==0
entry: *u8, // resolved package dir (or file, file root), NUL-term
name: *u8, // validated declared name; directory packages only
sources: **u8, // owned, byte-sorted production paths; dirs only
testpackage: *u8,
sources: **u8, // owned, byte-sorted selected paths; dirs only
nsources: i32,
isdir: i32,
variant: i32,
testsupport: bool,
deps: []i32, // direct-dep indices into sepgraph.pkg
ndeps: i32,
color: i32, // tri-color DFS: 0 white, 1 gray, 2 black
@@ -650,15 +738,26 @@ type sepgraph = struct {
n: i32,
};
fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8,
isdir: i32, variant: i32, testpackage: *u8) i32 = {
if (cstrlen(path) >= 256u64) {
cerr("ww: package path is too long (limit 255 bytes)\n");
return -1;
};
let i: i32 = 0;
for (i < g.n) {
let testproductionpair: bool = i == 0
&& g.pkg[i].variant != SEP_VARIANT_PRODUCTION
&& variant == SEP_VARIANT_PRODUCTION && isdir != 0
&& g.pkg[i].isdir != 0
&& os.samefile(pathstr(g.pkg[i].entry), pathstr(entry));
if (cstreq(g.pkg[i].path, path)) {
if (!os.samefile(pathstr(g.pkg[i].entry), pathstr(entry))) {
if (testproductionpair) {
i += 1;
continue;
};
if (!os.samefile(pathstr(g.pkg[i].entry), pathstr(entry))
|| g.pkg[i].variant != variant) {
cerr("ww: package "); cerr(pathstr(path));
cerr(" resolves to more than one location\n");
return -1;
@@ -666,6 +765,10 @@ fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
return i;
};
if (os.samefile(pathstr(g.pkg[i].entry), pathstr(entry))) {
if (testproductionpair) {
i += 1;
continue;
};
cerr("ww: package directory "); cerr(pathstr(entry));
cerr(" has identities ");
if (g.pkg[i].path[0u64] == 0u8) { cerr("(root)"); }
@@ -687,9 +790,16 @@ fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
g.pkg[g.n].path = arenadupcstr(path, plen);
g.pkg[g.n].entry = arenadupcstr(entry, elen);
g.pkg[g.n].name = nil;
g.pkg[g.n].testpackage = nil;
if (testpackage != nil) {
g.pkg[g.n].testpackage = arenadupcstr(testpackage,
cstrlen(testpackage));
};
g.pkg[g.n].sources = nil;
g.pkg[g.n].nsources = 0;
g.pkg[g.n].isdir = isdir;
g.pkg[g.n].variant = variant;
g.pkg[g.n].testsupport = false;
let dslot: []i32 = alloc([], SEP_MAXPKG: u64)!;
dslot.len = SEP_MAXPKG;
g.pkg[g.n].deps = dslot;
@@ -700,6 +810,11 @@ fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
return r;
};
fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
return sepfindoraddvariant(g, path, entry, isdir,
SEP_VARIANT_PRODUCTION, nil);
};
// Release the package-owned directory-membership lists through one graph
// cleanup function. rt_free is a no-op in today's no-free runtime, but this
// records the same ownership boundary as the C bootstrap twin.
@@ -741,6 +856,36 @@ fn sepfname(g: *sepgraph, pi: i32, scratch: *u8, suffix: str) *u8 = {
return buf.ptr;
};
fn sepexternalname(pkg: *seppkg, path: *u8, n: u64,
leafonly: bool) bool = {
if (pkg.variant != SEP_VARIANT_EXTERNAL || pkg.testpackage == nil) {
return false;
};
let begin: u64 = 0u64;
if (leafonly) {
let i: u64 = 0u64;
for (i < n) {
if (path[i] == '.') { begin = i + 1u64; };
i += 1u64;
};
};
let leafn: u64 = n - begin;
let tn: u64 = cstrlen(pkg.testpackage);
if (tn != leafn + 5u64) { return false; };
if (bytecmp(pkg.testpackage, leafn, path + begin, leafn) != 0) {
return false;
};
return pkg.testpackage[leafn] == '_'
&& pkg.testpackage[leafn + 1u64] == 't'
&& pkg.testpackage[leafn + 2u64] == 'e'
&& pkg.testpackage[leafn + 3u64] == 's'
&& pkg.testpackage[leafn + 4u64] == 't';
};
fn sepexternalproduction(pkg: *seppkg, path: *u8, n: u64) bool = {
return sepexternalname(pkg, path, n, false);
};
// Scan one already-selected source file for its leading package clause
// (when it is an owned directory source) and top-level imports. A DIRECTORY
// import is a package boundary: add as a direct dep of pi. A FILE import is an
@@ -776,6 +921,11 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
&& (ownedsource != 0 || g.pkg[pi].name == nil)) {
let declared: *u8 = imports.nmod.ptr;
let declaredn: u64 = imports.nmod.len: u64;
if (declaredn >= 256u64) {
cerrpos(imports.file, imports.line, imports.col);
cerr(": error: package name is too long\n");
return -1;
};
if (g.pkg[pi].name == nil) {
g.pkg[pi].name = arenadupcstr(declared, declaredn);
} else { if (bytecmp(g.pkg[pi].name, cstrlen(g.pkg[pi].name),
@@ -852,10 +1002,24 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
return -1;
};
let isdir: i32 = 0;
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
let externalproduction: bool = sepexternalproduction(
&g.pkg[pi], idp, idn);
let ipath: *u8 = nil;
if (externalproduction) {
ipath = g.pkg[pi].entry;
isdir = 1;
} else {
ipath = locateimport(searchpath, idp, idn, &isdir);
};
if (ipath != nil) {
if (isdir != 0) {
if (os.samefile(pathstr(ipath), pathstr(g.pkg[pi].entry))) {
let self: bool = os.samefile(pathstr(ipath),
pathstr(g.pkg[pi].entry));
if (self && sepexternalname(&g.pkg[pi], idp, idn, true)) {
externalproduction = true;
};
if (self
&& !externalproduction) {
cerrpos(u.file, u.line, u.col);
cerr(": error: self-import: package '");
if (g.pkg[pi].path[0u64] != 0u8) {
@@ -931,7 +1095,8 @@ fn seploadpkg(g: *sepgraph, pi: i32, searchpath: *u8) i32 = {
if (g.pkg[pi].isdir != 0) {
let sources: **u8;
let nsources: i32;
sources, nsources = enumeratedir(g.pkg[pi].entry);
sources, nsources = enumeratedir(g.pkg[pi].entry,
g.pkg[pi].variant, g.pkg[pi].testpackage);
g.pkg[pi].sources = sources;
g.pkg[pi].nsources = nsources;
if (g.pkg[pi].nsources == -2) {
@@ -956,7 +1121,8 @@ fn seploadpkg(g: *sepgraph, pi: i32, searchpath: *u8) i32 = {
};
i += 1;
};
if (rc == 0 && g.pkg[pi].path[0u64] != 0u8) {
if (rc == 0 && g.pkg[pi].path[0u64] != 0u8
&& !g.pkg[pi].testsupport) {
let plen: u64 = cstrlen(g.pkg[pi].path);
let leaf: *u8 = g.pkg[pi].path;
let j: u64 = 0u64;
@@ -1056,7 +1222,7 @@ fn sepwriteall(fd: i32, buf: *u8, n: u64) bool = {
};
fn sepemitbody(fd: i32, path: *u8, visit: *expctx, searchpath: *u8,
modpath: *u8) i32 = {
modpath: *u8, pkg: *seppkg) i32 = {
let pview: str;
pview.ptr = path;
pview.len = cstrlen(path): i32;
@@ -1114,12 +1280,16 @@ fn sepemitbody(fd: i32, path: *u8, visit: *expctx, searchpath: *u8,
u = uses[ui];
let idp: *u8 = u.usepath.ptr;
let idn: u64 = u.usepath.len: u64;
if (sepexternalproduction(pkg, idp, idn)) {
ui += 1;
continue;
};
let isdir: i32 = 0;
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
if (ipath != nil) {
if (isdir == 0) {
if (sepemitbody(fd, ipath, visit, searchpath,
modpath) < 0) { return -1; };
modpath, pkg) < 0) { return -1; };
};
};
ui += 1;
@@ -1195,12 +1365,12 @@ fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8,
let i: i32 = 0;
for (i < g.pkg[pi].nsources && bodyrc == 0) {
bodyrc = sepemitbody(u, g.pkg[pi].sources[i], &bv, searchpath,
g.pkg[pi].path);
g.pkg[pi].path, &g.pkg[pi]);
i += 1;
};
} else {
bodyrc = sepemitbody(u, g.pkg[pi].entry, &bv, searchpath,
g.pkg[pi].path);
g.pkg[pi].path, &g.pkg[pi]);
};
if (os.close(u) != 0) {
cerr("ww: cannot close package unit\n");
@@ -1405,14 +1575,14 @@ fn copyfileatomic(src: *u8, dst: *u8) i32 = {
fn workdirstamptext(istest: i32, emitasm: i32) str = {
if (istest != 0) {
if (emitasm != 0) {
return "ww workdir fmt 2 mode test asm 1\n";
return "ww workdir fmt 3 mode test asm 1\n";
};
return "ww workdir fmt 2 mode test asm 0\n";
return "ww workdir fmt 3 mode test asm 0\n";
};
if (emitasm != 0) {
return "ww workdir fmt 2 mode build asm 1\n";
return "ww workdir fmt 3 mode build asm 1\n";
};
return "ww workdir fmt 2 mode build asm 0\n";
return "ww workdir fmt 3 mode build asm 0\n";
};
fn stampmatches(path: *u8, want: str) bool = {
@@ -1460,7 +1630,8 @@ fn cerrpath(head: str, path: *u8, tail: str) void = {
fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
rootidentity: *u8, out: *u8,
objstem: *u8, incs: *u8, lf: *lflags, packageonly: i32, istest: i32,
emitasm: i32, workdir: *u8, scratchout: **u8,
rootvariant: i32, testpackage: *u8, emitasm: i32,
workdir: *u8, scratchout: **u8,
graphout: **sepgraph) i32 = {
let c6: *u8 = joinpathlit(selfdir, "w6c_ww");
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
@@ -1605,34 +1776,65 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
if (graphout != nil) { *graphout = g; };
let rootpath: *u8 = "\0".ptr;
if (packageonly != 0 && rootidentity != nil) { rootpath = rootidentity; };
let root: i32 = sepfindoradd(g, rootpath, src, entryisdir);
let root: i32 = sepfindoraddvariant(g, rootpath, src, entryisdir,
rootvariant, testpackage);
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
// seploadpkg pulls test + its transitive deps; the producer adds -T to
// the root and `test.run` links against test's `.a` — via the
// sepscanfile dedup-guarded dep append.
let testsupportmodule: str = "test";
// -T generates a dispatcher whose support qualifier is selected by the
// command. Represent that compiler-generated requirement as a direct root
// edge. It normally coalesces with an explicit toolchain `import test`;
// when user source occupies that identity, the reserved graph alias keeps
// it distinct. The linker receives the same support archive closure.
if (istest != 0) {
let td: i32 = 0;
let tp: *u8 = locateimport(searchpath.ptr, "test".ptr, "test".len: u64, &td);
let tp: *u8 = locateimport(dotdotlib.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;
let rootissupport: bool = entryisdir != 0
&& os.samefile(pathstr(tp), pathstr(src));
let collision: bool = false;
if (!rootissupport && testpackage != nil) {
collision = cstreqlit(testpackage, "test")
|| cstreqlit(testpackage, "test_test");
};
if (!seen) {
if (g.pkg[root].ndeps < SEP_MAXPKG) {
g.pkg[root].deps[g.pkg[root].ndeps] = ti;
g.pkg[root].ndeps += 1;
if (!rootissupport && !collision) {
let ud: i32 = 0;
let up: *u8 = locateimport(searchpath.ptr, "test".ptr,
"test".len: u64, &ud);
if (up != nil && !os.samefile(pathstr(tp), pathstr(up))) {
collision = true;
};
};
if (collision) { testsupportmodule = SEP_TEST_SUPPORT_MODULE; };
// A same-test build of the runtime package already owns run
// and its source imports. An external test still needs the
// colocated production node, also its one support dependency.
if (!rootissupport || rootvariant == SEP_VARIANT_EXTERNAL) {
let ti: i32 = sepfindoradd(g,
testsupportmodule.ptr, tp, td);
if (ti < 0) { return 1; };
g.pkg[ti].testsupport = true;
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 (seploadpkg(g, root, searchpath.ptr) < 0) { return 1; };
if (rootvariant != SEP_VARIANT_PRODUCTION
&& (testpackage == nil || !cstreq(g.pkg[root].name, testpackage))) {
cerr("ww: package-test selector does not match loaded package\n");
return 1;
};
let rootpackage: bool = packageonly != 0;
if (rootpackage && cstreqlit(g.pkg[root].name, "main")) {
cerr("ww: -p requires a non-main package\n");
@@ -1719,11 +1921,21 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
// #79: the root carries -T under `ww test` so w6c
// synthesizes the test main; deps never get -T.
let roott: bool = (pi == root) && (istest != 0);
let supportt: bool = g.pkg[pi].testsupport;
let alen: u64 = 8u64;
if (!needsexport) { alen = 6u64; if (roott) { alen = 7u64; }; };
if (!needsexport) { alen = 6u64; if (roott) { alen = 9u64; }; };
if (supportt) { alen += 2u64; };
let argv: []str = alloc([], alen)!;
append(argv, "w6c");
if (roott) { append(argv, "-T"); };
if (roott) {
append(argv, "-T");
append(argv, "--test-support-module");
append(argv, testsupportmodule);
};
if (supportt) {
append(argv, "--test-support-module");
append(argv, testsupportmodule);
};
append(argv, "-c");
if (needsexport) {
append(argv, "-I");
@@ -1859,8 +2071,11 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
};
// Reverse-topo link: root `.o` first (order[norder-1]), dependency `.a`
// files after, then libwwrt.a (which still
// selectively pulls only the runtime members a live undef needs).
// files after, then libwwrt.a (which still selectively pulls only the
// runtime members a live undef needs). A same-test root already contains
// its production sources; if that production variant is also reached
// through the runtime closure, retain its dependencies but omit its
// duplicate archive.
// argv: 3 fixed (w6l,-o,out) + one root object/archive per package
// + 1 libwwrt + 2*nlibdirs + 2*nlibs + 1 nil.
let nldirs: i32 = 0;
@@ -1882,10 +2097,19 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
let pos: i32 = 3;
let li: i32 = norder - 1;
for (li >= 0) {
let pi: i32 = order[li];
if (g.pkg[root].variant == SEP_VARIANT_SAME_TEST
&& pi != root
&& g.pkg[pi].variant == SEP_VARIANT_PRODUCTION
&& os.samefile(pathstr(g.pkg[pi].entry),
pathstr(g.pkg[root].entry))) {
li -= 1;
continue;
};
// root: positional `.o` (force-load); deps: `.a` (selective).
let suf: str = ".a";
if (order[li] == root) { suf = ".o"; };
largv[pos] = sepfname(g, order[li], scratch, suf);
if (pi == root) { suf = ".o"; };
largv[pos] = sepfname(g, pi, scratch, suf);
pos += 1;
li -= 1;
};
@@ -1934,12 +2158,14 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32,
rootidentity: *u8, out: *u8,
objstem: *u8, incs: *u8, lf: *lflags, packageonly: i32, istest: i32,
emitasm: i32, keepscratch: i32, workdir: *u8) i32 = {
rootvariant: i32, testpackage: *u8, emitasm: i32,
keepscratch: i32, workdir: *u8) i32 = {
let scratch: *u8 = nil;
let g: *sepgraph = nil;
let r: i32 = buildonesepimpl(selfdir, src, entryisdir, rootidentity,
out, objstem,
incs, lf, packageonly, istest, emitasm, workdir, &scratch, &g);
incs, lf, packageonly, istest, rootvariant, testpackage,
emitasm, workdir, &scratch, &g);
sepgraphfree(g);
if (keepscratch == 0 && scratch != nil) {
if (cstrendswithlit(scratch, ".sepwork")) {
@@ -2248,7 +2474,8 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
if (packageonly != 0 && !requestedliteral) { rootidentity = src; };
return buildonesep(selfdir, resolved, isdir, rootidentity,
out, objstem, incs.ptr, &lf,
packageonly, 0i32, emitasm, 1i32, workdir);
packageonly, 0i32, SEP_VARIANT_PRODUCTION, nil,
emitasm, 1i32, workdir);
};
// Format the owned driver workspace /tmp/<prefix><pid> into buf. Pid is
@@ -2413,7 +2640,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
lf.nlibs = nlibs;
// The freshly acquired directory owns both main and main.sepwork.
if (buildonesep(selfdir, resolved, isdir, nil, outp, outp, incs.ptr, &lf,
0i32, 0i32, 0i32, 0i32, nil) != 0) {
0i32, 0i32, SEP_VARIANT_PRODUCTION, nil, 0i32, 0i32, nil) != 0) {
let cleanrc: i32 = os.remove(pathstr(outp));
if (cleanrc != 0 && cleanrc != -2i32) {
cerr("ww: cannot remove temporary output\n");
@@ -2503,7 +2730,7 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
let keep: i32 = 0;
if (outstem != nil) { keep = 1; };
let bres: i32 = buildonesep(selfdir, src, 0, nil, outp, objstem, incs, &lf,
0i32, 1i32, emitasm, keep, workdir);
0i32, 1i32, SEP_VARIANT_PRODUCTION, nil, emitasm, keep, workdir);
if (bres != 0) {
if (owntmp) {
let cleanrc: i32 = os.remove(pathstr(outp));
@@ -2590,6 +2817,8 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let emitasm: i32 = 0;
let outstem: *u8 = nil;
let workdir: *u8 = nil;
let packagetestkind: *u8 = nil;
let packagetestname: *u8 = nil;
let packageopts: bool = false;
let afterdash: bool = false;
let i: i32 = start;
@@ -2600,6 +2829,27 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
if (cstreqlit(p, "--")) {
packageopts = true; afterdash = true; i += 1; continue;
};
if (cstreqlit(p, "--ww-package-test")) {
if (i + 2 >= argc || packagetestkind != nil) {
cerr("ww test: --ww-package-test needs kind and package\n");
return 2;
};
packagetestkind = argv[i + 1];
packagetestname = argv[i + 2];
let pn: u64 = cstrlen(packagetestname);
if ((!cstreqlit(packagetestkind, "same")
&& !cstreqlit(packagetestkind, "external"))
|| pn == 0u64 || pn >= 256u64
|| (cstreqlit(packagetestkind, "external")
&& (pn <= 5u64
|| !cstrendswithlit(packagetestname,
"_test")))) {
cerr("ww test: invalid --ww-package-test variant\n");
return 2;
};
i += 3;
continue;
};
if (p[1u64] == 73u8) { // '-I'
let dir: *u8 = nil;
if (p[2u64] != 0u8) {
@@ -2679,6 +2929,10 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
cerr("ww test: -S needs -o\n");
return 2;
};
if (packagetestkind != nil && packageopts) {
cerr("ww test: package-test variant rejects package options\n");
return 2;
};
// Go's ./... form: a trailing "..." element is a package-tree
// request for the coordinator, never a literal path — recognized
@@ -2692,6 +2946,10 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
&& target[tlen - 1u64] == '.';
};
if (istree) {
if (packagetestkind != nil) {
cerr("ww test: package-test variant needs one directory\n");
return 2;
};
if (emitasm != 0) {
cerr("ww test: -S needs a single test file\n");
return 2;
@@ -2733,6 +2991,10 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
};
};
if (isdir == 0) {
if (packagetestkind != nil) {
cerr("ww test: package-test variant needs one directory\n");
return 2;
};
if (packageopts) {
cerr("ww test: package options need a directory\n"); return 2;
};
@@ -2752,6 +3014,22 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
cerr("ww test: pattern needs a single test file\n");
return 2;
};
if (packagetestkind != nil) {
if (compileonly == 0 || outstem == nil) {
cerr("ww test: package-test variant needs -c -o\n");
return 2;
};
let variant: i32 = SEP_VARIANT_EXTERNAL;
if (cstreqlit(packagetestkind, "same")) {
variant = SEP_VARIANT_SAME_TEST;
};
let lf: lflags;
lf.libdirs = nil; lf.nlibdirs = 0;
lf.libs = nil; lf.nlibs = 0;
return buildonesep(selfdir, resolved, 1, nil, outstem, outstem,
incs.ptr, &lf, 0i32, 1i32, variant, packagetestname,
0i32, 1i32, workdir);
};
let replacement: *u8 = nil;
if (resolved != target) { replacement = resolved; };
return execpackagetests(selfdir, argv, argc, start, targetindex,