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

@@ -12,7 +12,6 @@ type pkgsource = struct {
dir: str,
pkg: str,
test: bool,
attest: bool,
};
type pkgfolder = struct {
@@ -20,18 +19,13 @@ type pkgfolder = struct {
start: i32,
end: i32,
prodpkg: str,
hastests: bool,
};
type pkggroup = struct {
dir: str,
pkg: str,
start: i32,
end: i32,
prodpkg: str,
external: bool,
root: str,
combined: str,
workdir: str,
bin: str,
buildout: str,
@@ -53,7 +47,6 @@ def PKGDONE: i32 = 3;
// pkggroup.fail values recorded at launch, reported at ordered emission.
def PKGFAILNONE: i32 = 0;
def PKGFAILSETUP: i32 = 1;
def PKGFAILCOMPOSE: i32 = 2;
def pkgpoll: time.duration = 1000000i64: time.duration;
@@ -212,22 +205,6 @@ fn pkgclause(src: str, out: *str) bool = {
return true;
};
fn pkgattest(src: str) bool = {
let i: i32 = 0;
for (i < src.len) {
for (i < src.len && (src[i] == ' ' || src[i] == '\t' ||
src[i] == '\r')) { i += 1; };
if (i + 5 < src.len && src[i] == '@' && src[i + 1] == 't' &&
src[i + 2] == 'e' && src[i + 3] == 's' && src[i + 4] == 't' &&
(src[i + 5] == ' ' || src[i + 5] == '\t')) {
return true;
};
for (i < src.len && src[i] != '\n') { i += 1; };
if (i < src.len) { i += 1; };
};
return false;
};
fn pkgdirname(path: str) str = {
let i: i32 = path.len - 1;
for (i >= 0) {
@@ -271,7 +248,6 @@ fn pkgisdir(path: str) bool = {
fn pkgkeepfile(name: str) bool = {
if (!strings.hassuffix(name, ".ww")) { return false; };
if (strings.hassuffix(name, ".combined.ww")) { return false; };
return true;
};
@@ -372,11 +348,19 @@ fn pkgdiscoverdir(path: str, st: *pkgdiscover, recurse: bool) void = {
os.close(fd);
};
// Group paths by containing directory before filename order. A plain full-path
// sort can interleave a child directory between two files in its parent and
// split one package into multiple folder records during recursive discovery.
fn pkgsort(ss: []str) void = {
let i: i32 = 1;
for (i < ss.len) {
let j: i32 = i;
for (j > 0 && strings.compare(ss[j - 1], ss[j]) > 0) {
for (j > 0) {
let a: str = pkgdirname(ss[j - 1]);
let b: str = pkgdirname(ss[j]);
let c: int = strings.compare(a, b);
if (c == 0) { c = strings.compare(ss[j - 1], ss[j]); };
if (c <= 0) { break; };
let t: str = ss[j];
ss[j] = ss[j - 1];
ss[j - 1] = t;
@@ -505,46 +489,25 @@ fn pkgemitfile(path: str, fd: i32) bool = {
return true;
};
fn pkgcombined(g: *pkggroup, srcs: []pkgsource) bool = {
let fd: i32 = os.open(g.combined,
os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 384i32);
if (fd < 0) { return false; };
let ok: bool = true;
if (!g.external) {
let i: i32 = g.start;
for (i < g.end) {
if (!srcs[i].test) {
if (!pkgwrite(fd, "//ww:module-reset\n")) { ok = false; };
let s: str;
if (!pkgread(srcs[i].path, &s) || !pkgwrite(fd, s)
|| !pkgwrite(fd, "\n")) { ok = false; };
};
i += 1;
};
};
let i: i32 = g.start;
for (i < g.end) {
if (srcs[i].test && strings.compare(srcs[i].pkg, g.pkg) == 0) {
if (!pkgwrite(fd, "//ww:module-reset\n")) { ok = false; };
let s: str;
if (!pkgread(srcs[i].path, &s) || !pkgwrite(fd, s)
|| !pkgwrite(fd, "\n")) { ok = false; };
};
fn pkgworkescape(s: str) str = {
let out: []u8 = alloc([], (s.len * 2 + 1): u64)!;
let i: i32 = 0;
for (i < s.len) {
if (s[i] == '_') {
append(out, '_'); append(out, 'u');
} else { if (s[i] == '/') {
append(out, '_'); append(out, 's');
} else {
append(out, s[i]);
}; };
i += 1;
};
os.close(fd);
return ok;
return strings.frombytes(out);
};
fn pkgworkkey(dir: str, pkg: str) str = {
let s: str = strings.dup(strings.concat(dir, "_", pkg));
let b: []u8 = strings.toutf8(s);
let i: i32 = 0;
for (i < b.len) {
if (b[i] == '/') { b[i] = '_'; };
i += 1;
};
return strings.frombytes(b);
return strings.concat("d_", pkgworkescape(dir), "_p_",
pkgworkescape(pkg));
};
fn pkgsetpaths(g: *pkggroup, root: str, index: i32,
@@ -552,7 +515,6 @@ fn pkgsetpaths(g: *pkggroup, root: str, index: i32,
let num: str = strconv.i32tos(index, strconv.base.DEC);
g.root = strings.concat(root, "/group-", num);
if (!pkgmakedir(g.root)) { return false; };
g.combined = strings.concat(g.root, "/package.ww");
// A caller-owned persistent workdir root keys one driver -w dir
// per (dir, pkg) group; the driver's content-identity contract
// owns every reuse decision, so this stays a pure path policy.
@@ -615,14 +577,16 @@ fn pkgreportcommand(kind: str, g: *pkggroup, r: *exec.result) void = {
fn pkgstartbuild(g: *pkggroup, builder: str, includes: []str,
h: *exec.process) void = {
let ba: []str = alloc([], (11 + includes.len * 2): u64)!;
let ba: []str = alloc([], (13 + includes.len * 2): u64)!;
append(ba, builder);
append(ba, "test");
append(ba, "-c");
append(ba, "-o");
append(ba, g.bin);
append(ba, "-I");
append(ba, g.dir);
append(ba, "--ww-package-test");
if (g.external) { append(ba, "external"); }
else { append(ba, "same"); };
append(ba, g.pkg);
let ii: i32 = 0;
for (ii < includes.len) {
append(ba, "-I");
@@ -633,7 +597,7 @@ fn pkgstartbuild(g: *pkggroup, builder: str, includes: []str,
append(ba, "-w");
append(ba, g.workdir);
};
append(ba, g.combined);
append(ba, g.dir);
let bcmd: exec.command;
bcmd.path = builder;
bcmd.argv = ba;
@@ -675,6 +639,12 @@ fn pkgbuildok(g: *pkggroup) bool = {
&& g.buildres.code == 0;
};
fn pkgrunok(g: *pkggroup) bool = {
return g.runres.errno == 0 && g.runres.cleanuperrno == 0
&& g.runres.termination == exec.termination.EXIT
&& g.runres.code == 0;
};
// Ordered emission: a group's captures, diagnostics, and verdict line are
// written only here, strictly in group order, so concurrent scheduling
// produces the byte stream sequential scheduling produced.
@@ -683,10 +653,6 @@ fn pkgemitgroup(g: *pkggroup, tmproot: str, compileonly: bool) bool = {
pkgfailpath(tmproot, "cannot create group temporary directory");
return false;
};
if (g.fail == PKGFAILCOMPOSE) {
pkgfailpath(g.combined, "cannot compose package test source");
return false;
};
let buildcaptures: bool = pkgemitfile(g.buildout, os.STDOUT_FILENO);
buildcaptures = pkgemitfile(g.builderr, os.STDERR_FILENO) && buildcaptures;
if (!buildcaptures) {
@@ -704,15 +670,24 @@ fn pkgemitgroup(g: *pkggroup, tmproot: str, compileonly: bool) bool = {
pkgputln(os.STDOUT_FILENO, g.bin);
return true;
};
let runcaptures: bool = pkgemitfile(g.runout, os.STDOUT_FILENO);
runcaptures = pkgemitfile(g.runerr, os.STDERR_FILENO) && runcaptures;
if (!runcaptures) {
let runstdout: str;
let runstderr: str;
if (!pkgread(g.runout, &runstdout) || !pkgread(g.runerr, &runstderr)) {
pkgfailpath(g.root, "cannot read test capture");
return false;
};
if (g.runres.errno != 0 || g.runres.cleanuperrno != 0
|| g.runres.termination != exec.termination.EXIT
|| g.runres.code != 0) {
// The compiler-owned dispatcher is the authority on whether a variant
// contains runnable tests. With an empty generated table, test.run returns
// zero without output; no coordinator-side source scan is involved.
if (pkgrunok(g) && runstdout.len == 0 && runstderr.len == 0) {
pkgput(os.STDOUT_FILENO, "? ");
pkgput(os.STDOUT_FILENO, g.dir);
pkgputln(os.STDOUT_FILENO, " [no tests]");
return true;
};
pkgput(os.STDOUT_FILENO, runstdout);
pkgput(os.STDERR_FILENO, runstderr);
if (!pkgrunok(g)) {
pkgreportcommand("test", g, &g.runres);
return false;
};
@@ -874,15 +849,7 @@ export fn packagecommand(args: []str) int = {
s.path = ds.paths[i];
s.dir = strings.dup(pkgdirname(ds.paths[i]));
s.pkg = strings.dup(pn);
s.attest = pkgattest(body);
s.test = strings.hassuffix(pkgbase(ds.paths[i]), "_test.ww");
// Go's contract: only *_test.ww is a test source; @test
// anywhere else fails loudly rather than run or drop silently.
if (s.attest && !s.test) {
pkgfailpath(ds.paths[i],
"@test declaration outside *_test.ww");
return 1;
};
append(srcs, s);
i += 1;
};
@@ -895,15 +862,9 @@ export fn packagecommand(args: []str) int = {
f.start = i;
f.end = i;
f.prodpkg = "";
f.hastests = false;
for (f.end < srcs.len && strings.compare(srcs[f.end].dir, f.path) == 0) {
if (srcs[f.end].test) {
if (srcs[f.end].attest) { f.hastests = true; };
} else if (f.prodpkg.len == 0) {
if (!srcs[f.end].test && f.prodpkg.len == 0) {
f.prodpkg = srcs[f.end].pkg;
} else if (strings.compare(f.prodpkg, srcs[f.end].pkg) != 0) {
pkgfailpath(f.path, "production sources declare conflicting packages");
return 1;
};
f.end += 1;
};
@@ -915,21 +876,16 @@ export fn packagecommand(args: []str) int = {
i = 0;
for (i < folders.len) {
let f: pkgfolder = folders[i];
if (!f.hastests) {
pkgput(os.STDOUT_FILENO, "? ");
pkgput(os.STDOUT_FILENO, f.path);
pkgputln(os.STDOUT_FILENO, " [no tests]");
i += 1;
continue;
};
let externalpkg: str = "";
if (f.prodpkg.len != 0) {
externalpkg = strings.concat(f.prodpkg, "_test");
};
let standalonepkg: str = "";
let sawtestfile: bool = false;
let j: i32 = f.start;
for (j < f.end) {
if (srcs[j].test) {
sawtestfile = true;
if (f.prodpkg.len != 0
&& strings.compare(srcs[j].pkg, f.prodpkg) != 0
&& strings.compare(srcs[j].pkg, externalpkg) != 0) {
@@ -947,7 +903,6 @@ export fn packagecommand(args: []str) int = {
return 1;
};
};
if (!srcs[j].attest) { j += 1; continue; };
let found: bool = false;
let k: i32 = 0;
for (k < groups.len) {
@@ -962,9 +917,6 @@ export fn packagecommand(args: []str) int = {
let g: pkggroup;
g.dir = f.path;
g.pkg = srcs[j].pkg;
g.start = f.start;
g.end = f.end;
g.prodpkg = f.prodpkg;
g.external = f.prodpkg.len != 0
&& strings.compare(f.prodpkg, g.pkg) != 0;
append(groups, g);
@@ -972,6 +924,14 @@ export fn packagecommand(args: []str) int = {
};
j += 1;
};
if (!sawtestfile) {
let g: pkggroup;
g.dir = f.path;
g.pkg = f.prodpkg;
if (g.pkg.len == 0) { g.pkg = srcs[f.start].pkg; };
g.external = false;
append(groups, g);
};
i += 1;
};
if (groups.len == 0) { return 0; };
@@ -1012,9 +972,6 @@ export fn packagecommand(args: []str) int = {
g.fail = PKGFAILSETUP;
g.state = PKGDONE;
stopped = true;
} else if (!pkgcombined(g, srcs)) {
g.fail = PKGFAILCOMPOSE;
g.state = PKGDONE;
} else {
pkgstartbuild(g, builder, includes,
&handles[launched]);