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 order remains unchanged. Direct invocation of a retained binary continues to
inherit caller cwd, environment, and separate standard descriptors. inherit caller cwd, environment, and separate standard descriptors.
`-o` can accompany `-w`: unchanged semantic actions are reused, changed source `-c` and `-o` can accompany `-w`: the workdir owns only semantic actions while
invalidates the applicable test actions, the always-run link refreshes the the invocation/output path owns only the retained copy. Unchanged actions are
private runnable and retained copy, and the test still runs. This is build reused, changed source invalidates the applicable test actions, and the
reuse, not a result cache. `-c` retains its established incompatibility with always-run link refreshes the private runnable and retained copy. Only `-c`
`-w`; this slice does not invent persistent compile-only ownership. suppresses execution. This is build reuse, not a result cache.
No persisted byte schema changed. Build workdir format remains `18`, test No persisted byte schema changed. Build workdir format remains `18`, test
workdir format remains `19`, and semantic storage remains `3`. 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 prefixes. Test execution starts only after that transaction commits, so a
runtime failure leaves an explicitly retained binary. A no-test product runtime failure leaves an explicitly retained binary. A no-test product
publishes no binary and does not create a directory solely for one. 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 When `ww test` executes a directory-owned product, the child process working
directory is that product's canonical absolute physical package source 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 nor package-group name derives a persistent subdirectory. The delegated driver
may create a missing store only after graph and request preflight, and rolls 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 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 stays with the driver's content-identity contract above. Test `-c` may use the
not mix — the coordinator rejects the combination rather than guess which same store: the workdir owns semantic actions and the invocation/output path
artifact tree the caller owns. `test-library` uses exactly this: one owns the separately staged retained executable. The `test-library` route uses
`ww test -j $(JOBS) -w out/wwbuild/wwtest-lib lib/...` line owns library the same command-global store contract: one `ww test -j $(JOBS) -w
behavior (measured: 2.3s cold, 1.0s warm at 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). `-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 `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(); pkgusage();
return 2; 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 ds: pkgdiscover;
let emptypaths: []str; let emptypaths: []str;
ds.paths = emptypaths; ds.paths = emptypaths;

View File

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