ww test: allow -c with persistent workdirs

This commit is contained in:
2026-08-20 20:40:01 +09:00
parent db5782eff9
commit 77168fd891
5 changed files with 66 additions and 45 deletions

View File

@@ -6286,11 +6286,11 @@ transaction prevents partial sibling publication and canonical result emission
order remains unchanged. Direct invocation of a retained binary continues to
inherit caller cwd, environment, and separate standard descriptors.
`-o` can accompany `-w`: unchanged semantic actions are reused, changed source
invalidates the applicable test actions, the always-run link refreshes the
private runnable and retained copy, and the test still runs. This is build
reuse, not a result cache. `-c` retains its established incompatibility with
`-w`; this slice does not invent persistent compile-only ownership.
`-c` and `-o` can accompany `-w`: the workdir owns only semantic actions while
the invocation/output path owns only the retained copy. Unchanged actions are
reused, changed source invalidates the applicable test actions, and the
always-run link refreshes the private runnable and retained copy. Only `-c`
suppresses execution. This is build reuse, not a result cache.
No persisted byte schema changed. Build workdir format remains `18`, test
workdir format remains `19`, and semantic storage remains `3`.

View File

@@ -665,6 +665,9 @@ destinations and removes temporary stages and invocation-created output
prefixes. Test execution starts only after that transaction commits, so a
runtime failure leaves an explicitly retained binary. A no-test product
publishes no binary and does not create a directory solely for one.
`-w` may persist the unchanged semantic actions for either `-c` or running
retention without changing publication identity or introducing a test-result
cache.
When `ww test` executes a directory-owned product, the child process working
directory is that product's canonical absolute physical package source

View File

@@ -482,11 +482,12 @@ path as the one command-global semantic-action store; neither pattern spelling
nor package-group name derives a persistent subdirectory. The delegated driver
may create a missing store only after graph and request preflight, and rolls
back newly created empty prefixes if later setup fails. Every reuse decision
stays with the driver's content-identity contract above. `-w` and test `-c` do
not mix — the coordinator rejects the combination rather than guess which
artifact tree the caller owns. `test-library` uses exactly this: one
`ww test -j $(JOBS) -w out/wwbuild/wwtest-lib lib/...` line owns library
behavior (measured: 2.3s cold, 1.0s warm at
stays with the driver's content-identity contract above. Test `-c` may use the
same store: the workdir owns semantic actions and the invocation/output path
owns the separately staged retained executable. The `test-library` route uses
the same command-global store contract: one `ww test -j $(JOBS) -w
out/wwbuild/wwtest-lib lib/...` line owns library behavior (measured: 2.3s
cold, 1.0s warm at
`-j 4`, vs 4.7s for the retired 59-target per-file fan-out).
`test/lang` currently uses one package per source file, so its complete gate

View File

@@ -1853,15 +1853,6 @@ export fn packagecommand(args: []str) int = {
pkgusage();
return 2;
};
// -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");
return 2;
};
let ds: pkgdiscover;
let emptypaths: []str;
ds.paths = emptypaths;

View File

@@ -9215,18 +9215,26 @@ fn cwdwritedata(dir: str, label: str) void = {
(30i64 * (time.second: i64)): time.duration, &outc);
expectexit(&outc, 0);
assert(same(outc.stdout, seq));
// -c publishes caller-owned artifacts; a persistent workdir is a
// different ownership contract — the coordinator rejects the mix.
// The retained executable is separate from the persistent action store, so
// -c can suppress execution while reusing the same complete graph.
let wcbin: str = strings.concat(root, "/tree-wc-c.test");
let wwbin: str = strings.concat(root, "/tree-wc-ww.test");
let wcc: []str = [driver("ww"), "test", "-c", "-w", wdroot,
"-I", tree, spec];
"-o", wcbin, "-I", tree, strings.concat(tree, "/alpha")];
let wcw: []str = [driver("ww_ww"), "test", "-c", "-w", wdroot,
"-I", tree, spec];
runcommand(root, "tree-wc-c", wcc, time.second, &outc);
runcommand(root, "tree-wc-ww", wcw, time.second, &outw);
expectexit(&outc, 2);
expectexit(&outw, 2);
"-o", wwbin, "-I", tree, strings.concat(tree, "/alpha")];
runcommand(root, "tree-wc-c", wcc,
(30i64 * (time.second: i64)): time.duration, &outc);
runcommand(root, "tree-wc-ww", wcw,
(30i64 * (time.second: i64)): time.duration, &outw);
expectexit(&outc, 0);
expectexit(&outw, 0);
assert(outc.stdout.len == 0 && outc.stderr.len == 0);
assert(outw.stdout.len == 0 && outw.stderr.len == 0);
assert(os.exists(wcbin) && os.exists(wwbin));
assert(same(readfile(wcbin), readfile(wwbin)));
assert(same(outc.stderr, outw.stderr));
assert(has(outc.stderr, "wwtest package: -w conflicts with -c\n"));
clean(wcbin); clean(wwbin);
let bare: str = strings.concat(root, "/bare");
let barez: str = strings.concat(root, "/zz-bare");
@@ -12566,42 +12574,60 @@ fn runtimepath(relative: str) str = {
if (si == 0) { failurebytes = readfile(failurebin); }
else { assert(same(failurebytes, readfile(failurebin))); };
// -w keeps semantic actions persistent while -o remains a presentation
// copy. Unchanged input reuses actions; changed input replaces the copy.
// -w keeps semantic actions persistent while -c and -o remain execution
// and presentation policy. Compile-only can cold-build and invalidate;
// an unchanged running request reuses those actions and the same bytes.
rewritefile(persisttest, persistbase);
let persistwork: str = strings.concat(root, "/persist-work-", tags[si]);
let persistbin: str = strings.concat(root, "/persist.test");
if (os.exists(persistbin)) { clean(persistbin); };
let persistcompilebin: str = strings.concat(root, "/persist-compile-",
tags[si], ".test");
let persistbin: str = strings.concat(root, "/persist-run-", tags[si],
".test");
rewritefile(ctrace, ""); rewritefile(ltrace, "");
let persistcompileav: []str = [driver(stages[si]), "test", "-c",
"-w", persistwork, "-I", suite, "-o", persistcompilebin,
persist];
let persistav: []str = [driver(stages[si]), "test", "-w",
persistwork, "-I", suite, "-o", persistbin, persist];
runcommandenvdir(root, strings.concat("publish-persist-cold-", tags[si]),
runcommandenvdir(root, strings.concat("publish-persist-cold-c-", tags[si]),
persistcompileav, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0);
assert(readfile(ctrace).len != 0);
let basebytes: str = readfile(persistcompilebin);
if (si == 0) { persistbasebytes = strings.dup(basebytes); }
else { assert(same(persistbasebytes, basebytes)); };
rewritefile(ctrace, ""); rewritefile(ltrace, "");
runcommandenvdir(root, strings.concat("publish-persist-warm-run-", tags[si]),
persistav, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(has(out.stdout, "persistent_base ... ok\n"));
let basebytes: str = readfile(persistbin);
if (si == 0) { persistbasebytes = strings.dup(basebytes); }
else { assert(same(persistbasebytes, basebytes)); };
rewritefile(ctrace, ""); rewritefile(ltrace, "");
runcommandenvdir(root, strings.concat("publish-persist-warm-", tags[si]),
persistav, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(readfile(ctrace).len == 0);
assert(same(basebytes, readfile(persistbin)));
rewritefile(persisttest, persistchanged);
rewritefile(ctrace, ""); rewritefile(ltrace, "");
runcommandenvdir(root, strings.concat("publish-persist-change-", tags[si]),
persistav, env, root,
runcommandenvdir(root, strings.concat("publish-persist-change-c-", tags[si]),
persistcompileav, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(has(out.stdout, "persistent_changed ... ok\n"));
assert(out.stdout.len == 0 && out.stderr.len == 0);
assert(readfile(ctrace).len != 0);
let changedbytes: str = readfile(persistbin);
let changedbytes: str = readfile(persistcompilebin);
assert(!same(basebytes, changedbytes));
if (si == 0) { persistchangedbytes = strings.dup(changedbytes); }
else { assert(same(persistchangedbytes, changedbytes)); };
rewritefile(ctrace, ""); rewritefile(ltrace, "");
runcommandenvdir(root, strings.concat("publish-persist-change-run-",
tags[si]), persistav, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(has(out.stdout, "persistent_changed ... ok\n"));
assert(readfile(ctrace).len == 0);
assert(same(changedbytes, readfile(persistbin)));
assert(!os.exists(strings.concat(persistcompilebin, ".new")));
assert(!os.exists(strings.concat(persistbin, ".new")));
assert(!directoryhasnew(persistwork));
si += 1;
};