ww test: retain directory test binaries like Go

This commit is contained in:
2026-08-20 20:13:08 +09:00
parent f28843f2f5
commit db5782eff9
10 changed files with 1014 additions and 213 deletions

View File

@@ -24,6 +24,8 @@ type pkgfolder = struct {
type pkggroup = struct {
dir: str,
pkg: str,
testname: str,
publish: str,
prodpkg: str,
samepkg: str,
externalpkg: str,
@@ -467,7 +469,7 @@ fn pkgusage() void = {
pkgput(os.STDERR_FILENO,
" *_test.ww is the sole test-source form; @test elsewhere is rejected\n");
pkgput(os.STDERR_FILENO,
" -c retains the compiled package binaries; -j N runs up to N build or test processes at once\n");
" -c retains without running; -o retains and still runs unless -c is present\n");
pkgput(os.STDERR_FILENO,
" -w DIR is one persistent semantic-action store shared by the selected packages\n");
};
@@ -616,6 +618,28 @@ fn pkgbase(path: str) str = {
return path;
};
// Directory test binaries are presentation artifacts. Their Go-like visible
// basename comes from the selected canonical import spelling, never from the
// declared package name or any test variant. A physical root is the fallback
// when the request has no explicit logical identity.
fn pkgimportbase(path: str) str = {
let i: i32 = path.len - 1;
for (i >= 0) {
if (path[i] == '.') {
let r: str;
r.ptr = path.ptr + ((i + 1): u64);
r.len = path.len - i - 1;
return r;
};
i -= 1;
};
return path;
};
fn pkgisabs(path: str) bool = {
return path.len != 0 && path[0] == '/';
};
fn pkgmodeis(m: os.mode, want: os.mode) bool = {
return (((m: u32) & 61440u32) == (want: u32));
};
@@ -1319,7 +1343,7 @@ fn pkgemitfile(path: str, fd: i32) bool = {
fn pkgsetplanpaths(p: *pkgplan, groups: []pkggroup, root: str, index: i32,
compileonly: bool, buildonly: bool, outname: str, outputdir: bool,
workroot: str) bool = {
createdir: str, workroot: str) bool = {
let num: str = strconv.i32tos(index, strconv.base.DEC);
if (!pkgstring(&p.root, root, "/plan-", num)) { return false; };
if (!pkgmakedir(p.root)) {
@@ -1330,8 +1354,7 @@ fn pkgsetplanpaths(p: *pkgplan, groups: []pkggroup, root: str, index: i32,
// spelling or traversal prefix must never select another cache container.
// The private driver creates it only after graph preflight succeeds.
p.workdir = workroot;
p.outputdir = "";
if (outputdir) { p.outputdir = outname; };
p.outputdir = createdir;
if (!pkgstring(&p.buildout, p.root, "/build.stdout")
|| !pkgstring(&p.builderr, p.root, "/build.stderr")) { return false; };
let i: i32 = p.start;
@@ -1349,12 +1372,6 @@ fn pkgsetplanpaths(p: *pkgplan, groups: []pkggroup, root: str, index: i32,
if (!pkgjoinpath(outname, pkgbase(g.dir), &g.bin)) { return false; };
} else if (outname.len != 0 && !outputdir) { g.bin = outname; }
else if (!pkgstring(&g.bin, g.root, "/package.build")) { return false; };
} else if (compileonly) {
if (outname.len != 0) {
g.bin = outname;
} else {
if (!pkgstring(&g.bin, g.dir, "/", g.pkg, ".test")) { return false; };
};
} else {
if (!pkgstring(&g.bin, g.root, "/package.test")) { return false; };
};
@@ -1400,11 +1417,11 @@ fn pkgstartbuild(p: *pkgplan, groups: []pkggroup, builder: str, includes: []str,
libdirs: []str, libs: []str, h: *exec.process) bool = {
let nproducts: i32 = p.end - p.start;
let capacity: i32 = 16;
if (nproducts < 0 || nproducts > (PKG_COUNT_MAX - capacity) / 9) {
if (nproducts < 0 || nproducts > (PKG_COUNT_MAX - capacity) / 10) {
pkgputln(os.STDERR_FILENO, "wwtest package: package graph is too large");
return false;
};
capacity += nproducts * 9;
capacity += nproducts * 10;
if (includes.len > (PKG_COUNT_MAX - capacity) / 2) {
pkgputln(os.STDERR_FILENO, "wwtest package: package graph is too large");
return false;
@@ -1459,6 +1476,8 @@ fn pkgstartbuild(p: *pkgplan, groups: []pkggroup, builder: str, includes: []str,
else { append(ba, "-"); };
append(ba, g.dir);
append(ba, g.bin);
if (g.publish.len != 0) { append(ba, g.publish); }
else { append(ba, "-"); };
append(ba, g.buildok);
i += 1;
};
@@ -1572,10 +1591,9 @@ fn pkgemitgroup(g: *pkggroup, compileonly: bool) bool = {
return true;
};
if (compileonly) {
pkgput(os.STDOUT_FILENO, "built ");
pkglabel(g);
pkgput(os.STDOUT_FILENO, " -> ");
pkgputln(os.STDOUT_FILENO, g.bin);
// Go's compile-only print action is a nop after the retained binary
// install completes. Build and publication failures still diagnose on
// stderr through the plan result above.
return true;
};
if (g.runstartfailed) { return false; };
@@ -1835,14 +1853,9 @@ export fn packagecommand(args: []str) int = {
pkgusage();
return 2;
};
// A non-compile run leaves no caller-owned artifact for -o to name.
if (outname.len != 0 && !compileonly) {
pkgputln(os.STDERR_FILENO, "wwtest package: -o needs -c");
return 2;
};
// -c publishes caller-owned sepwork artifacts; mixing that
// contract with a persistent workdir is unwired — reject rather
// than guess which tree the caller owns.
// -c suppresses execution and requests a caller-visible binary. Keep it
// separate from -w until compile-only persistent-action ownership is wired;
// -o without -c already permits a retained copy beside a persistent store.
if (workroot.len != 0 && compileonly && !buildonly) {
pkgputln(os.STDERR_FILENO,
"wwtest package: -w conflicts with -c");
@@ -2034,6 +2047,8 @@ export fn packagecommand(args: []str) int = {
let g: pkggroup;
g.dir = f.path;
g.pkg = f.prodpkg;
g.testname = "";
g.publish = "";
g.prodpkg = f.prodpkg;
g.samepkg = "";
g.externalpkg = "";
@@ -2157,6 +2172,8 @@ export fn packagecommand(args: []str) int = {
g.dir = f.path;
g.pkg = family;
if (g.pkg.len == 0) { g.pkg = srcs[f.start].pkg; };
g.testname = "";
g.publish = "";
g.prodpkg = f.prodpkg;
g.samepkg = samepkg;
g.externalpkg = externalpkg;
@@ -2240,9 +2257,89 @@ export fn packagecommand(args: []str) int = {
i += 1;
};
};
// A non-directory caller-owned name cannot fan out. A directory output
// publishes each selected command under its canonical directory basename.
if (outname.len != 0 && !outputdir && groups.len > 1) {
// Go's test binary is always linked into request-private storage. -c and
// -o independently request a caller-visible executable copy; only -c
// suppresses execution. Visible names are import-leaf metadata and never
// action, package, variant, symbol, or persistence identity.
let testretain: bool = !buildonly && (compileonly || explicitout);
let testnull: bool = !buildonly && explicitout
&& strings.compare(outname, "/dev/null") == 0;
let testoutdir: bool = !buildonly && explicitout && !testnull
&& pkgoutputdir(outname);
let invocationdir: str = "";
if (testretain) {
let cwdoom: bool = false;
if (!pkgcanonicaldir(".", &invocationdir, &cwdoom)) {
if (!cwdoom) {
pkgputln(os.STDERR_FILENO,
"wwtest package: cannot determine invocation directory");
};
return 1;
};
};
i = 0;
for (i < groups.len) {
let leaf: str = pkgbase(groups[i].dir);
if (roots.len == 1 && !anyrecurse && requestidentity.len != 0) {
leaf = pkgimportbase(requestidentity);
};
if (!pkgstring(&groups[i].testname, leaf, ".test")) { return 1; };
groups[i].publish = "";
if (testretain && !groups[i].notests && !testnull) {
if (!explicitout) {
if (!pkgjoinpath(invocationdir, groups[i].testname,
&groups[i].publish)) { return 1; };
} else if (testoutdir) {
let targetdir: str = outname;
if (!pkgisabs(outname)
&& !pkgjoinpath(invocationdir, outname, &targetdir)) {
return 1;
};
if (!pkgjoinpath(targetdir, groups[i].testname,
&groups[i].publish)) { return 1; };
} else if (pkgisabs(outname)) {
groups[i].publish = outname;
} else if (!pkgjoinpath(invocationdir, outname,
&groups[i].publish)) { return 1; };
};
i += 1;
};
if (!buildonly && explicitout && groups.len > 1
&& !testnull && !testoutdir) {
pkgputln(os.STDERR_FILENO,
"ww test: with multiple packages, -o must refer to a directory or /dev/null");
return 1;
};
if (!buildonly && groups.len > 1 && testretain && !testnull) {
i = 0;
for (i < groups.len) {
let j: i32 = 0;
for (j < i) {
if (strings.compare(groups[j].testname,
groups[i].testname) == 0) {
pkgput(os.STDERR_FILENO,
"ww test: cannot write test binary ");
pkgput(os.STDERR_FILENO, groups[i].testname);
pkgputln(os.STDERR_FILENO,
" for multiple packages:");
let k: i32 = 0;
for (k < groups.len) {
if (strings.compare(groups[k].testname,
groups[i].testname) == 0) {
pkgputln(os.STDERR_FILENO, groups[k].dir);
};
k += 1;
};
return 1;
};
j += 1;
};
i += 1;
};
};
// A non-directory caller-owned build name cannot fan out. A directory
// build output publishes each selected command under its directory leaf.
if (buildonly && outname.len != 0 && !outputdir && groups.len > 1) {
pkgputln(os.STDERR_FILENO,
"wwtest package: cannot use -o with multiple packages");
return 2;
@@ -2277,13 +2374,27 @@ export fn packagecommand(args: []str) int = {
&& strings.compare(groups[0].pkg, "main") != 0;
plan.emitasm = emitasm;
append(plans, plan);
let createdir: str = "";
if (buildonly && outputdir) {
createdir = outname;
} else if (!buildonly && testretain && !testnull) {
i = 0;
for (i < groups.len) {
if (groups[i].publish.len != 0) {
createdir = pkgdirname(groups[i].publish);
break;
};
i += 1;
};
};
let tmproot: str = temp.dir();
let failed: i32 = 0;
i = 0;
for (i < plans.len) {
if (!pkgsetplanpaths(&plans[i], groups, tmproot, i,
compileonly, buildonly, outname, outputdir, workroot)) {
compileonly, buildonly, outname, outputdir, createdir,
workroot)) {
if (!pkgremoveall(tmproot)) {
pkgput(os.STDERR_FILENO,
"wwtest package: cleanup failed; retained ");