test: cover shared directory test build plans

This commit is contained in:
2026-08-12 11:42:29 +09:00
parent dd975d195c
commit 02574c88f1
2 changed files with 326 additions and 72 deletions

View File

@@ -2849,11 +2849,16 @@ has one test variant; the coordinator rejects one output name for a multi-varian
or recursive request. Explicit `ww test FILE` retains its compatibility path. or recursive request. Explicit `ww test FILE` retains its compatibility path.
The test coordinator still discovers requested directories, enumerates the The test coordinator still discovers requested directories, enumerates the
test package names, schedules independent binaries, executes them, and emits test package names, selects variants, executes independent binaries, and emits
captured results in byte-sorted package order. It no longer concatenates a captured results in byte-sorted package order. It no longer concatenates a
generated production/test root or resolves imports. Instead it asks the Cstage generated production/test root, resolves imports, or starts one package graph
or WWstage command to build one of two non-importable root variants from the per binary. Instead it sends one ordered build request per directory to the
real directory: Cstage or WWstage command. Its semantic selections are only the directory and
the selected variant/package identities; it also carries output destinations,
coordinator-private completion paths, import search roots, and the optional
shared work-directory policy. The command owns source selection,
package loading, compiler inputs, archive construction, and linking for both
non-importable roots:
- `same-test` selects the byte-sorted production files followed by the - `same-test` selects the byte-sorted production files followed by the
byte-sorted matching `package p` test files. They form one compiler unit, so byte-sorted matching `package p` test files. They form one compiler unit, so
@@ -2863,32 +2868,55 @@ real directory:
directory. That node selects every production file, emits `p.wwi` and `p.a`, directory. That node selects every production file, emits `p.wwi` and `p.a`,
and exposes no private declaration to the external root. and exposes no private declaration to the external root.
The command loads those roots into one command-scoped package universe. The
roots retain distinct artifact keys (`__ww-test-same` and
`__ww-test-external`) that cannot collide with a legal import identity,
while imports of the same canonical production directory intern to one
production node. A deterministic dependency-first traversal of the union
therefore invokes the compiler and archiver once for every reachable canonical
production package, even when both test products need it. Each root is still
compiled once with its own selected sources and linked separately. The shared
plan is deliberately package-test-specific: it is not a generalized scheduler,
action schema, cache, or protocol.
A production action failure blocks exactly the roots that reach it. A
root-local compile or link failure does not suppress a successfully built
sibling product: the command records completion per product, and the
coordinator can run and report successful siblings while attributing each
missing product as a build failure. Directory builds and completed test
products share the coordinator's existing `-j` process bound; captured output
is still emitted only in byte-sorted directory/package order.
Every non-root dependency is always a production variant, so dependency Every non-root dependency is always a production variant, so dependency
`*_test.ww` files never enter the graph. Imports that occur only in selected `*_test.ww` files never enter the graph. Imports that occur only in selected
test files add edges only to that test root. The compiler unit for each package test files add edges only to that test root. The compiler unit for each package
contains only its byte-sorted direct dependency `.wwi` artifacts; the final contains only its byte-sorted direct dependency `.wwi` artifacts; the final
test link still receives the root object and the complete reverse-topological test link still receives the root object and the complete reverse-topological
archive closure. The compiler-generated `-T` dispatcher owns the implicit archive closure. The compiler-generated `-T` dispatcher owns the implicit
direct test-runtime support edge and remains embedded in each independent test direct test-runtime support edge and remains embedded in each independently
root. The command resolves that edge from the selected toolchain source tree, compiled test root; it is the narrow test-main variant, not a
not the user search path. Normally its graph qualifier is `test`, so an explicit coordinator-generated graph package. The shared plan compiles the common
source `import test` coalesces with the same canonical package. When a real user runtime production package once. The command resolves that edge from the
package occupies that identity, the command presents the runtime edge to the selected toolchain source tree, not the user search path. Normally its graph
compiler under the reserved `__wwtest` qualifier. This keeps a production qualifier is `test`, so an explicit source `import test` coalesces with the same
package named `test` available to external tests while preserving raw `w6c -T` canonical package. When a real user package occupies that identity, the command
compatibility, whose default unresolved qualifier remains `test`. presents the runtime edge to the compiler under the reserved `__wwtest`
qualifier. This keeps a production package named `test` available to external
tests while preserving raw `w6c -T` compatibility, whose default unresolved
qualifier remains `test`.
The selected test root and a production variant reached by its imports or test The selected test roots and a production variant reached by their imports or
runtime closure are the only sanctioned pair of graph nodes that may share a test-runtime closure are the only sanctioned graph nodes that may share a
physical directory. This also lets a toolchain package's own tests coexist with physical directory. This also lets a toolchain package's own tests coexist with
the production variant required by the test runtime. A same-package root the production variant required by the test runtime. A same-package root
already defines those production symbols, so the duplicate production archive already defines those production symbols, so the production archive is omitted
is omitted from its final link while that node's dependency archives remain in from that product's final link while the production node's dependency archives
the closure. All ordinary logical and physical package-identity collision remain in its closure. The external product includes the production archive.
checks remain unchanged. Recursive Variant-only archives are never linked into the other product. All ordinary
discovery groups by physical directory before sorting filenames, and persistent logical and physical package-identity collision checks remain unchanged.
coordinator work directories use an injective escaped `(directory, test Recursive discovery groups by physical directory before sorting filenames, and
package)` key. Every `*_test.ww` package variant is built even when a file only persistent coordinator work directories use one injective escaped directory
plan key. Every `*_test.ww` package variant is built even when a file only
declares helpers and contains no `@test`, so its package clause and imports are declares helpers and contains no `@test`, so its package clause and imports are
still checked by the shared loader. Every built variant is run, and a successful still checked by the shared loader. Every built variant is run, and a successful
compiler-owned dispatcher with empty output is reported as `[no tests]`. compiler-owned dispatcher with empty output is reported as `[no tests]`.
@@ -2896,9 +2924,15 @@ compiler-owned dispatcher with empty output is reported as `[no tests]`.
This ownership split follows Go 1.26.5's separation of production, This ownership split follows Go 1.26.5's separation of production,
same-package-test, external-test, and generated test-main inputs in same-package-test, external-test, and generated test-main inputs in
[`cmd/go/internal/load/test.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/test.go), [`cmd/go/internal/load/test.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/test.go),
while retaining WW's compiler-owned dispatcher. Direct compile dependencies and while retaining WW's compiler-owned dispatcher. Go's loader returns the same
the separately expanded link closure follow the boundary in cached package pointer for repeated imports, and its command-scoped builder
[`cmd/go/internal/work/action.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/action.go). deduplicates compile actions by package identity; WW borrows that one-universe,
multi-root ownership boundary without borrowing Go's cache machinery. Direct
compile dependencies and the separately expanded link closure follow the
boundary in
[`cmd/go/internal/work/action.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/action.go),
with compiler and linker import configurations emitted separately in
[`cmd/go/internal/work/exec.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/exec.go).
## 12. Candidate architectures and hard-gate decision ## 12. Candidate architectures and hard-gate decision

View File

@@ -71,6 +71,17 @@ fn writefile(path: str, content: str) void = {
assert(os.close(fd) == 0); assert(os.close(fd) == 0);
}; };
fn writeexecutable(path: str, content: str) void = {
let fd: i32 = os.open(path,
os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 448i32);
assert(fd >= 0);
match (os.writeall(fd, content.ptr, content.len: u64)) {
case let n: i64 => assert(n == content.len: i64);
case let e: os.oserror => abort("write failed");
};
assert(os.close(fd) == 0);
};
fn runcommand(root: str, name: str, argv: []str, fn runcommand(root: str, name: str, argv: []str,
lifetime: time.duration, out: *commandout) void = { lifetime: time.duration, out: *commandout) void = {
let c: exec.command; let c: exec.command;
@@ -169,6 +180,22 @@ fn occurrences(haystack: str, needle: str) i32 = {
return count; return count;
}; };
fn linecontaining(text: str, needle: str) str = {
let start: i32 = 0;
let i: i32 = 0;
for (i <= text.len) {
if (i == text.len || text[i] == '\n': u8) {
let line: str;
line.ptr = text.ptr + (start: u64);
line.len = i - start;
if (has(line, needle)) { return line; };
start = i + 1;
};
i += 1;
};
abort("missing trace line");
};
fn expectexit(out: *commandout, code: i32) void = { fn expectexit(out: *commandout, code: i32) void = {
assert(out.termination == exec.termination.EXIT); assert(out.termination == exec.termination.EXIT);
assert(out.code == code); assert(out.code == code);
@@ -617,14 +644,13 @@ fn workescape(s: str) str = {
assert(has(outc.stdout, assert(has(outc.stdout,
strings.concat(" -> ", root, "/route_test.test\n"))); strings.concat(" -> ", root, "/route_test.test\n")));
let cwhite: str = readfile(strings.concat(root, let cwhite: str = readfile(strings.concat(root,
"/route.test.sepwork/__root.s")); "/route.test.sepwork/__ww-test-same.s"));
let cexternal: str = readfile(strings.concat(root, let cexternal: str = readfile(strings.concat(root,
"/route_test.test.sepwork/__root.s")); "/route.test.sepwork/__ww-test-external.s"));
// The explicit package `-c` outputs are caller-owned artifacts. Release // The explicit package `-c` outputs are caller-owned artifacts. Release
// the two exact C-stage trees before asking the WW driver to acquire the // the shared C-stage tree before asking the WW driver to acquire the same
// same stems; the driver never deletes a pre-existing `.sepwork` path. // stem; the driver never deletes a pre-existing `.sepwork` path.
clean(strings.concat(root, "/route.test.sepwork")); clean(strings.concat(root, "/route.test.sepwork"));
clean(strings.concat(root, "/route_test.test.sepwork"));
let wc: []str = [driver("ww_ww"), "test", "-c", root]; let wc: []str = [driver("ww_ww"), "test", "-c", root];
runcommand(root, "compile-ww", wc, runcommand(root, "compile-ww", wc,
@@ -633,9 +659,9 @@ fn workescape(s: str) str = {
assert(same(outc.stdout, outw.stdout)); assert(same(outc.stdout, outw.stdout));
assert(same(outc.stderr, outw.stderr)); assert(same(outc.stderr, outw.stderr));
assert(same(cwhite, readfile(strings.concat(root, assert(same(cwhite, readfile(strings.concat(root,
"/route.test.sepwork/__root.s")))); "/route.test.sepwork/__ww-test-same.s"))));
assert(same(cexternal, readfile(strings.concat(root, assert(same(cexternal, readfile(strings.concat(root,
"/route_test.test.sepwork/__root.s")))); "/route.test.sepwork/__ww-test-external.s"))));
clean(root); clean(root);
}; };
@@ -643,8 +669,8 @@ fn workescape(s: str) str = {
let root: str = fresh(); let root: str = fresh();
let implementation: str = strings.concat(root, "/implementation"); let implementation: str = strings.concat(root, "/implementation");
let api: str = strings.concat(root, "/api"); let api: str = strings.concat(root, "/api");
let testonly: str = strings.concat(root, "/testonly"); let testonly: str = strings.concat(root, "/__same");
let externalonly: str = strings.concat(root, "/externalonly"); let externalonly: str = strings.concat(root, "/__external");
let pkg: str = strings.concat(root, "/pkg"); let pkg: str = strings.concat(root, "/pkg");
assert(os.mkdir(implementation, 448i32) == 0); assert(os.mkdir(implementation, 448i32) == 0);
assert(os.mkdir(api, 448i32) == 0); assert(os.mkdir(api, 448i32) == 0);
@@ -662,11 +688,11 @@ fn workescape(s: str) str = {
writefile(strings.concat(api, "/api_test.ww"), strings.concat( writefile(strings.concat(api, "/api_test.ww"), strings.concat(
"package api;\n", "package api;\n",
"TEST_DEPENDENCY_MUST_NOT_COMPILE\n")); "TEST_DEPENDENCY_MUST_NOT_COMPILE\n"));
writefile(strings.concat(testonly, "/testonly.ww"), strings.concat( writefile(strings.concat(testonly, "/__same.ww"), strings.concat(
"package testonly;\n", "package __same;\n",
"export fn value() i32 = { return 1; };\n")); "export fn value() i32 = { return 1; };\n"));
writefile(strings.concat(externalonly, "/externalonly.ww"), strings.concat( writefile(strings.concat(externalonly, "/__external.ww"), strings.concat(
"package externalonly;\n", "package __external;\n",
"export fn value() i32 = { return 1; };\n")); "export fn value() i32 = { return 1; };\n"));
writefile(strings.concat(pkg, "/a.ww"), strings.concat( writefile(strings.concat(pkg, "/a.ww"), strings.concat(
"package pkg;\nimport api;\n", "package pkg;\nimport api;\n",
@@ -678,16 +704,16 @@ fn workescape(s: str) str = {
"fn secret() i32 = { return 6; };\n", "fn secret() i32 = { return 6; };\n",
"export fn second() i32 = { return 9; };\n")); "export fn second() i32 = { return 9; };\n"));
writefile(strings.concat(pkg, "/same_test.ww"), strings.concat( writefile(strings.concat(pkg, "/same_test.ww"), strings.concat(
"package pkg;\nimport testonly;\n", "package pkg;\nimport __same;\n",
"// SAME_TEST_SOURCE\n", "// SAME_TEST_SOURCE\n",
"@test fn same_graph() void = {\n", "@test fn same_graph() void = {\n",
" assert(value() + testonly.value() == 42);\n", " assert(value() + __same.value() == 42);\n",
" assert(secret() == 6);\n};\n")); " assert(secret() == 6);\n};\n"));
writefile(strings.concat(pkg, "/external_test.ww"), strings.concat( writefile(strings.concat(pkg, "/external_test.ww"), strings.concat(
"package pkg_test;\nimport externalonly;\nimport pkg;\n", "package pkg_test;\nimport __external;\nimport pkg;\n",
"// EXTERNAL_TEST_SOURCE\n", "// EXTERNAL_TEST_SOURCE\n",
"@test fn external_graph() void = {\n", "@test fn external_graph() void = {\n",
" assert(pkg.value() + externalonly.value() == 42);\n", " assert(pkg.value() + __external.value() == 42);\n",
" assert(pkg.second() == 9);\n};\n")); " assert(pkg.second() == 9);\n};\n"));
let prodout: str = strings.concat(root, "/production.a"); let prodout: str = strings.concat(root, "/production.a");
@@ -702,19 +728,54 @@ fn workescape(s: str) str = {
"pkg.unit.ww")); "pkg.unit.ww"));
assert(has(produnit, "//ww:module api\n")); assert(has(produnit, "//ww:module api\n"));
assert(!has(produnit, "//ww:module implementation\n")); assert(!has(produnit, "//ww:module implementation\n"));
assert(!has(produnit, "testonly")); assert(!has(produnit, "__same"));
assert(!has(produnit, "externalonly")); assert(!has(produnit, "__external"));
assert(!os.exists(strings.concat(prodwork, "testonly.unit.ww"))); assert(!os.exists(strings.concat(prodwork, "__same.unit.ww")));
assert(!os.exists(strings.concat(prodwork, "externalonly.unit.ww"))); assert(!os.exists(strings.concat(prodwork, "__external.unit.ww")));
assert(!has(readfile(strings.concat(prodwork, "api.unit.ww")), assert(!has(readfile(strings.concat(prodwork, "api.unit.ww")),
"TEST_DEPENDENCY_MUST_NOT_COMPILE")); "TEST_DEPENDENCY_MUST_NOT_COMPILE"));
let samebin: str = strings.concat(pkg, "/pkg.test"); let samebin: str = strings.concat(pkg, "/pkg.test");
let externalbin: str = strings.concat(pkg, "/pkg_test.test"); let externalbin: str = strings.concat(pkg, "/pkg_test.test");
let sameworkroot: str = strings.concat(samebin, ".sepwork"); let sharedworkroot: str = strings.concat(samebin, ".sepwork");
let externalworkroot: str = strings.concat(externalbin, ".sepwork"); let sharedwork: str = strings.concat(sharedworkroot, "/");
let samework: str = strings.concat(sameworkroot, "/"); let compilertrace: str = strings.concat(root, "/compiler.trace");
let externalwork: str = strings.concat(externalworkroot, "/"); let linkertrace: str = strings.concat(root, "/linker.trace");
let compilerwrapper: str = strings.concat(root, "/trace-w6c.sh");
let linkerwrapper: str = strings.concat(root, "/trace-w6l.sh");
writefile(compilertrace, "");
writefile(linkertrace, "");
writeexecutable(compilerwrapper, strings.concat(
"#!/bin/sh\n",
"printf '%s\\n' \"$*\" >> \"$WW_PACKAGE_COMPILER_TRACE\"\n",
"exec \"$WW_PACKAGE_W6C\" \"$@\"\n"));
writeexecutable(linkerwrapper, strings.concat(
"#!/bin/sh\n",
"printf '%s\\n' \"$*\" >> \"$WW_PACKAGE_LINKER_TRACE\"\n",
"exec \"$WW_PACKAGE_W6L\" \"$@\"\n"));
let baseenv: []str = os.getenvs();
let traceenv: []str = alloc([], (baseenv.len + 6): u64)!;
let ei: i32 = 0;
for (ei < baseenv.len) {
if (!strings.hasprefix(baseenv[ei], "WW_W6C=")
&& !strings.hasprefix(baseenv[ei], "WW_W6L=")
&& !strings.hasprefix(baseenv[ei],
"WW_PACKAGE_COMPILER_TRACE=")
&& !strings.hasprefix(baseenv[ei],
"WW_PACKAGE_LINKER_TRACE=")
&& !strings.hasprefix(baseenv[ei], "WW_PACKAGE_W6C=")
&& !strings.hasprefix(baseenv[ei], "WW_PACKAGE_W6L=")) {
append(traceenv, baseenv[ei]);
};
ei += 1;
};
append(traceenv, strings.concat("WW_W6C=", compilerwrapper));
append(traceenv, strings.concat("WW_W6L=", linkerwrapper));
append(traceenv, strings.concat("WW_PACKAGE_COMPILER_TRACE=",
compilertrace));
append(traceenv, strings.concat("WW_PACKAGE_LINKER_TRACE=", linkertrace));
append(traceenv, strings.concat("WW_PACKAGE_W6C=", driver("w6c")));
append(traceenv, strings.concat("WW_PACKAGE_W6L=", driver("w6l")));
let drivers: []str = ["ww", "ww", "ww_ww"]; let drivers: []str = ["ww", "ww", "ww_ww"];
let tags: []str = ["c1", "c2", "ww"]; let tags: []str = ["c1", "c2", "ww"];
let referenceout: str = ""; let referenceout: str = "";
@@ -728,34 +789,41 @@ fn workescape(s: str) str = {
for (i < drivers.len) { for (i < drivers.len) {
let av: []str = [driver(drivers[i]), "test", "-c", "-I", root, let av: []str = [driver(drivers[i]), "test", "-c", "-I", root,
pkg]; pkg];
runcommand(root, strings.concat("variant-build-", tags[i]), av, if (i == 0) {
(120i64 * (time.second: i64)): time.duration, &out); runcommandenv(root, strings.concat("variant-build-", tags[i]), av,
traceenv, (120i64 * (time.second: i64)): time.duration,
&out);
} else {
runcommand(root, strings.concat("variant-build-", tags[i]), av,
(120i64 * (time.second: i64)): time.duration, &out);
};
expectexit(&out, 0); expectexit(&out, 0);
let buildstdout: str = strings.dup(out.stdout); let buildstdout: str = strings.dup(out.stdout);
let buildstderr: str = strings.dup(out.stderr); let buildstderr: str = strings.dup(out.stderr);
let sameunit: str = readfile(strings.concat(samework, let sameunit: str = readfile(strings.concat(sharedwork,
"__root.unit.ww")); "__ww-test-same.unit.ww"));
let externalunit: str = readfile(strings.concat(externalwork, let externalunit: str = readfile(strings.concat(sharedwork,
"__root.unit.ww")); "__ww-test-external.unit.ww"));
let externalproduction: str = readfile(strings.concat(externalwork, let externalproduction: str = readfile(strings.concat(sharedwork,
"pkg.unit.ww")); "pkg.unit.ww"));
assert(!os.exists(strings.concat(externalbin, ".sepwork")));
assert(has(sameunit, "//ww:module api\n")); assert(has(sameunit, "//ww:module api\n"));
assert(has(sameunit, "//ww:module testonly\n")); assert(has(sameunit, "//ww:module __same\n"));
assert(has(sameunit, "//ww:module test\n")); assert(has(sameunit, "//ww:module test\n"));
assert(!has(sameunit, "//ww:module implementation\n")); assert(!has(sameunit, "//ww:module implementation\n"));
assert(!has(sameunit, "//ww:module externalonly\n")); assert(!has(sameunit, "//ww:module __external\n"));
assert(!os.exists(strings.concat(samework, assert(os.exists(strings.concat(sharedwork,
"externalonly.unit.ww"))); "__external.unit.ww")));
assert(has(sameunit, "PACKAGE_PRODUCTION_A")); assert(has(sameunit, "PACKAGE_PRODUCTION_A"));
assert(has(sameunit, "PACKAGE_PRODUCTION_Z")); assert(has(sameunit, "PACKAGE_PRODUCTION_Z"));
assert(has(sameunit, "SAME_TEST_SOURCE")); assert(has(sameunit, "SAME_TEST_SOURCE"));
assert(!has(sameunit, "EXTERNAL_TEST_SOURCE")); assert(!has(sameunit, "EXTERNAL_TEST_SOURCE"));
assert(has(externalunit, "//ww:module pkg\n")); assert(has(externalunit, "//ww:module pkg\n"));
assert(has(externalunit, "//ww:module externalonly\n")); assert(has(externalunit, "//ww:module __external\n"));
assert(has(externalunit, "//ww:module test\n")); assert(has(externalunit, "//ww:module test\n"));
assert(!has(externalunit, "//ww:module api\n")); assert(!has(externalunit, "//ww:module api\n"));
assert(!has(externalunit, "//ww:module implementation\n")); assert(!has(externalunit, "//ww:module implementation\n"));
assert(!has(externalunit, "//ww:module testonly\n")); assert(!has(externalunit, "//ww:module __same\n"));
assert(has(externalunit, "EXTERNAL_TEST_SOURCE")); assert(has(externalunit, "EXTERNAL_TEST_SOURCE"));
assert(!has(externalunit, "PACKAGE_PRODUCTION_A")); assert(!has(externalunit, "PACKAGE_PRODUCTION_A"));
assert(!has(externalunit, "PACKAGE_PRODUCTION_Z")); assert(!has(externalunit, "PACKAGE_PRODUCTION_Z"));
@@ -765,12 +833,61 @@ fn workescape(s: str) str = {
assert(!has(externalproduction, "//ww:module implementation\n")); assert(!has(externalproduction, "//ww:module implementation\n"));
assert(!has(externalproduction, "SAME_TEST_SOURCE")); assert(!has(externalproduction, "SAME_TEST_SOURCE"));
assert(!has(externalproduction, "EXTERNAL_TEST_SOURCE")); assert(!has(externalproduction, "EXTERNAL_TEST_SOURCE"));
assert(!has(readfile(strings.concat(externalwork, "api.unit.ww")), assert(!has(readfile(strings.concat(sharedwork, "api.unit.ww")),
"TEST_DEPENDENCY_MUST_NOT_COMPILE")); "TEST_DEPENDENCY_MUST_NOT_COMPILE"));
assert(os.exists(strings.concat(samework, "implementation.a"))); let artifacts: []str = ["implementation", "api", "pkg",
assert(os.exists(strings.concat(externalwork, "implementation.a"))); "__same", "__external", "test"];
assert(os.exists(strings.concat(externalwork, "api.a"))); let ai: i32 = 0;
assert(os.exists(strings.concat(externalwork, "pkg.a"))); for (ai < artifacts.len) {
assert(os.exists(strings.concat(sharedwork, artifacts[ai], ".wwi")));
assert(os.exists(strings.concat(sharedwork, artifacts[ai], ".a")));
ai += 1;
};
assert(os.exists(strings.concat(sharedwork, "__ww-test-same.o")));
assert(os.exists(strings.concat(sharedwork, "__ww-test-external.o")));
assert(!os.exists(strings.concat(sharedwork, "__ww-test-same.wwi")));
assert(!os.exists(strings.concat(sharedwork,
"__ww-test-external.wwi")));
assert(!os.exists(strings.concat(sharedwork, "__ww-test-same.a")));
assert(!os.exists(strings.concat(sharedwork,
"__ww-test-external.a")));
assert(os.exists(samebin));
assert(os.exists(externalbin));
if (i == 0) {
let ctrace: str = readfile(compilertrace);
assert(occurrences(ctrace, "implementation.unit.ww") == 1);
assert(occurrences(ctrace, "api.unit.ww") == 1);
assert(occurrences(ctrace, "pkg.unit.ww") == 1);
assert(occurrences(ctrace, "/__same.unit.ww") == 1);
assert(occurrences(ctrace, "/__external.unit.ww") == 1);
assert(occurrences(ctrace, "/test.unit.ww") == 1);
assert(occurrences(ctrace, "__ww-test-same.unit.ww") == 1);
assert(occurrences(ctrace,
"__ww-test-external.unit.ww") == 1);
assert(occurrences(ctrace,
"-T --test-support-module") == 2);
let ltrace: str = readfile(linkertrace);
assert(occurrences(ltrace, "\n") == 2);
let samelink: str = linecontaining(ltrace,
strings.concat("-o ", samebin, " "));
let externallink: str = linecontaining(ltrace,
strings.concat("-o ", externalbin, " "));
assert(has(samelink, strings.concat(sharedwork, "api.a")));
assert(has(samelink,
strings.concat(sharedwork, "implementation.a")));
assert(has(samelink, strings.concat(sharedwork, "__same.a")));
assert(!has(samelink, strings.concat(sharedwork, "pkg.a")));
assert(!has(samelink,
strings.concat(sharedwork, "__external.a")));
assert(has(externallink, strings.concat(sharedwork, "pkg.a")));
assert(has(externallink, strings.concat(sharedwork, "api.a")));
assert(has(externallink,
strings.concat(sharedwork, "implementation.a")));
assert(has(externallink,
strings.concat(sharedwork, "__external.a")));
assert(!has(externallink,
strings.concat(sharedwork, "__same.a")));
};
let runav: []str = [samebin]; let runav: []str = [samebin];
runcommand(root, strings.concat("variant-run-same-", tags[i]), runav, runcommand(root, strings.concat("variant-run-same-", tags[i]), runav,
(60i64 * (time.second: i64)): time.duration, &out); (60i64 * (time.second: i64)): time.duration, &out);
@@ -799,11 +916,39 @@ fn workescape(s: str) str = {
assert(same(referenceerr, buildstderr)); assert(same(referenceerr, buildstderr));
}; };
if (i + 1 < drivers.len) { if (i + 1 < drivers.len) {
clean(sameworkroot); clean(externalworkroot); clean(sharedworkroot);
clean(samebin); clean(externalbin); clean(samebin); clean(externalbin);
}; };
i += 1; i += 1;
}; };
// The illegal-in-import root keys stay disjoint from the valid packages
// literally named __same and __external, including warm reuse.
let warmroot: str = strings.concat(root, "/warm");
assert(os.mkdir(warmroot, 448i32) == 0);
let warmav: []str = [driver("ww"), "test", "-w", warmroot,
"-I", root, pkg];
runcommandenv(root, "variant-warm-cold", warmav, traceenv,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
let warmstdout: str = strings.dup(out.stdout);
let warmstderr: str = strings.dup(out.stderr);
let warmtrace: str = readfile(compilertrace);
let warmwork: str = strings.concat(warmroot, "/d_", workescape(pkg),
"_p_pkg/");
assert(has(readfile(strings.concat(warmwork, "__same.unit.ww")),
"package __same;"));
assert(has(readfile(strings.concat(warmwork,
"__ww-test-same.unit.ww")), "SAME_TEST_SOURCE"));
assert(has(readfile(strings.concat(warmwork, "__external.unit.ww")),
"package __external;"));
assert(has(readfile(strings.concat(warmwork,
"__ww-test-external.unit.ww")), "EXTERNAL_TEST_SOURCE"));
runcommandenv(root, "variant-warm-reuse", warmav, traceenv,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(same(warmstdout, out.stdout));
assert(same(warmstderr, out.stderr));
assert(same(warmtrace, readfile(compilertrace)));
let aliasunit: str = strings.concat(root, "/alias.unit.ww"); let aliasunit: str = strings.concat(root, "/alias.unit.ww");
writefile(aliasunit, strings.concat( writefile(aliasunit, strings.concat(
"//ww:module-reset __wwtest\n", "//ww:module-reset __wwtest\n",
@@ -834,6 +979,79 @@ fn workescape(s: str) str = {
clean(root); clean(root);
}; };
// One failing root is a product failure, not permission for the shared build
// request to suppress an independently compilable sibling root.
@test fn sibling_test_variant_failures_are_isolated() void = {
let root: str = fresh();
let badexternal: str = strings.concat(root, "/badexternal");
let badsame: str = strings.concat(root, "/badsame");
assert(os.mkdir(badexternal, 448i32) == 0);
assert(os.mkdir(badsame, 448i32) == 0);
writefile(strings.concat(badexternal, "/badexternal.ww"), strings.concat(
"package badexternal;\n",
"fn hidden() i32 = { return 41; };\n",
"export fn shown() i32 = { return 41; };\n"));
writefile(strings.concat(badexternal, "/same_test.ww"), strings.concat(
"package badexternal;\n",
"@test fn valid_same_runs() void = { assert(hidden() == 41); };\n"));
writefile(strings.concat(badexternal, "/external_test.ww"), strings.concat(
"package badexternal_test;\nimport badexternal;\n",
"@test fn invalid_external() void = {\n",
" assert(badexternal.hidden() == 41);\n};\n"));
writefile(strings.concat(badsame, "/badsame.ww"), strings.concat(
"package badsame;\n",
"export fn shown() i32 = { return 42; };\n"));
writefile(strings.concat(badsame, "/same_test.ww"), strings.concat(
"package badsame;\n",
"@test fn invalid_same() void = { assert(missing() == 0); };\n"));
writefile(strings.concat(badsame, "/external_test.ww"), strings.concat(
"package badsame_test;\nimport badsame;\n",
"@test fn valid_external_runs() void = {\n",
" assert(badsame.shown() == 42);\n};\n"));
let stages: []str = ["ww", "ww_ww"];
let tags: []str = ["c", "ww"];
let externaldiagnostics: []str = ["", ""];
let samediagnostics: []str = ["", ""];
let out: commandout;
let i: i32 = 0;
for (i < stages.len) {
let externalav: []str = [driver(stages[i]), "test", "-I", root,
badexternal];
runcommand(root, strings.concat("isolated-external-", tags[i]),
externalav, (120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(has(out.stdout, "valid_same_runs ... ok\n"));
assert(has(out.stdout, "[badexternal, same-package]\n"));
assert(has(out.stderr,
"has no exported declaration 'hidden'"));
assert(has(out.stderr,
strings.concat("FAIL ", badexternal,
" [badexternal_test] (build exit 1)\n")));
assert(!has(out.stderr, strings.concat("FAIL ", badexternal,
" [badexternal]")));
externaldiagnostics[i] = strings.dup(primarydiagnostic(out.stderr));
let sameav: []str = [driver(stages[i]), "test", "-I", root,
badsame];
runcommand(root, strings.concat("isolated-same-", tags[i]), sameav,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(has(out.stdout, "valid_external_runs ... ok\n"));
assert(has(out.stdout, "[badsame_test, external]\n"));
assert(has(out.stderr, "undefined: missing"));
assert(has(out.stderr, strings.concat("FAIL ", badsame,
" [badsame] (build exit 1)\n")));
assert(!has(out.stderr, strings.concat("FAIL ", badsame,
" [badsame_test]")));
samediagnostics[i] = strings.dup(primarydiagnostic(out.stderr));
i += 1;
};
assert(same(externaldiagnostics[0], externaldiagnostics[1]));
assert(same(samediagnostics[0], samediagnostics[1]));
clean(root);
};
// The compiler-generated runner uses a reserved graph qualifier rather than // The compiler-generated runner uses a reserved graph qualifier rather than
// claiming the user-visible import path `test`. A real multi-file production // claiming the user-visible import path `test`. A real multi-file production
// package named test can therefore be the normal direct dependency of its // package named test can therefore be the normal direct dependency of its
@@ -872,7 +1090,8 @@ fn workescape(s: str) str = {
runcommand(root, strings.concat("named-test-build-", stages[i]), av, runcommand(root, strings.concat("named-test-build-", stages[i]), av,
(120i64 * (time.second: i64)): time.duration, &out); (120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0); expectexit(&out, 0);
let rootunit: str = readfile(strings.concat(work, "__root.unit.ww")); let rootunit: str = readfile(strings.concat(work,
"__ww-test-external.unit.ww"));
let produnit: str = readfile(strings.concat(work, "test.unit.ww")); let produnit: str = readfile(strings.concat(work, "test.unit.ww"));
let supportunit: str = readfile(strings.concat(work, let supportunit: str = readfile(strings.concat(work,
"__wwtest.unit.ww")); "__wwtest.unit.ww"));
@@ -967,7 +1186,8 @@ fn workescape(s: str) str = {
expectexit(&out, 0); expectexit(&out, 0);
assert(occurrences(readfile(trace), "shared.unit.ww") == 1); assert(occurrences(readfile(trace), "shared.unit.ww") == 1);
let work: str = strings.concat(bin, ".sepwork/"); let work: str = strings.concat(bin, ".sepwork/");
let unit: str = readfile(strings.concat(work, "__root.unit.ww")); let unit: str = readfile(strings.concat(work,
"__ww-test-same.unit.ww"));
assert(has(unit, "//ww:module left\n")); assert(has(unit, "//ww:module left\n"));
assert(has(unit, "//ww:module right\n")); assert(has(unit, "//ww:module right\n"));
assert(!has(unit, "//ww:module shared\n")); assert(!has(unit, "//ww:module shared\n"));
@@ -1185,8 +1405,8 @@ fn workescape(s: str) str = {
assert(has(outc.stderr, assert(has(outc.stderr,
"ww test: -o needs -c for a package target\n")); "ww test: -o needs -c for a package target\n"));
// -w on a package target forwards to the coordinator: one // -w on a package target forwards to the coordinator: one shared
// persistent driver workdir per package group under the root, // persistent driver workdir per directory plan under the root,
// reuse owned by the driver's content-identity contract. The // reuse owned by the driver's content-identity contract. The
// byte stream must match the plain run, cold and warm. // byte stream must match the plain run, cold and warm.
let wdroot: str = strings.concat(root, "/wd"); let wdroot: str = strings.concat(root, "/wd");