build: route .ww directories as packages
This commit is contained in:
@@ -10244,6 +10244,138 @@ No serialized format changes accompany this omission. Build workdir format
|
|||||||
remains `18`, test workdir format remains `19`, semantic storage format remains
|
remains `18`, test workdir format remains `19`, semantic storage format remains
|
||||||
`3`, and the slice adds no cache or persistent record.
|
`3`, and the slice adds no cache or persistent record.
|
||||||
|
|
||||||
|
### 11.52 Implemented `.ww`-spelled directory build routing
|
||||||
|
|
||||||
|
An existing local literal passed to `ww build` whose requested final basename
|
||||||
|
ends `.ww` is a directory-package request when ordinary `stat` reports a
|
||||||
|
directory. This includes a direct directory named `.ww` and a visible
|
||||||
|
`_test.ww` symlink to a directory. The suffix does not turn the directory into
|
||||||
|
a raw source and does not trigger the non-directory test-source omission in
|
||||||
|
11.51.
|
||||||
|
|
||||||
|
#### Pinned authority and applicability
|
||||||
|
|
||||||
|
- **behavior directly implemented or asserted by pinned Go** — official Go
|
||||||
|
1.26.5 commit `c19862e5f8415b4f24b189d065ed739517c548ba` implements
|
||||||
|
symlink-following `Stat` in `cmd/go/internal/fsys/fsys.go:633–638`.
|
||||||
|
`PackagesAndErrors` enters named-file mode only when an operand ends `.go`,
|
||||||
|
`Stat` succeeds, and the result is not a directory
|
||||||
|
(`cmd/go/internal/load/pkg.go:2903–2932`, especially 2911–2918). Local
|
||||||
|
literals that do not enter that route are matched and directory-loaded by
|
||||||
|
`cmd/go/internal/modload/load.go:251–307,526–597` and
|
||||||
|
`cmd/go/internal/search/search.go:276–293`. `runBuild` uses that loader at
|
||||||
|
`cmd/go/internal/work/build.go:459–477`.
|
||||||
|
- **behavior directly implemented or asserted by pinned Go** — official
|
||||||
|
`cmd/go/testdata/script/list_ambiguous_path.txt:1–15,29–36` directly proves
|
||||||
|
that directory `./foo.go` is a package while regular file `./a.go` is a
|
||||||
|
`command-line-arguments` package. Official
|
||||||
|
`cmd/go/testdata/script/mod_symlink_dotgo.txt:1–9,11–17` directly asserts
|
||||||
|
that `dir.go -> dir` is not a source entry; `go/build/build.go:886–900`
|
||||||
|
implements that directory-enumeration rule.
|
||||||
|
- **behavior derived from the pinned implementation** — because the initial
|
||||||
|
`Stat` follows the terminal symlink, a requested `.go` path whose target is a
|
||||||
|
directory takes the directory package route under build. That file-kind rule
|
||||||
|
applies directly to WW's existing local raw-source/directory distinction and
|
||||||
|
needs no module, manifest, registry, cache, network resolution, generalized
|
||||||
|
import syntax, or build expression.
|
||||||
|
|
||||||
|
#### Ownership and command boundary
|
||||||
|
|
||||||
|
- **directly measured WW behavior** — before this change, Cstage already
|
||||||
|
stat-routed a valid `visible_test.ww -> pkgdir` build, produced a normal
|
||||||
|
directory graph and executable, and returned status 0 with empty streams.
|
||||||
|
WWstage instead returned status 1, empty stdout, exact
|
||||||
|
`ww: cannot read source\n` stderr, and no artifact. The same split occurred
|
||||||
|
for `visible.ww -> pkgdir` and a real directory named `literal.ww`; a
|
||||||
|
`regular_test.ww` symlink to a regular source retained the aligned 11.51
|
||||||
|
omission.
|
||||||
|
- **directly measured WW behavior** — the true owner is the
|
||||||
|
requested-kind branch in `selfhost/cmd/ww/main.ww::dobuild`, after its
|
||||||
|
existing `Stat`, prefix exclusion, and non-directory `_test.ww` check and
|
||||||
|
before logical resolution. Cstage and both test fronts already have the
|
||||||
|
required classification and do not change.
|
||||||
|
- **behavior directly implemented or asserted by pinned Go** — `go run` has a
|
||||||
|
deliberately different front door: `cmd/go/internal/run/run.go:95–112`
|
||||||
|
consumes leading `.go` suffix operands as named files without build/test's
|
||||||
|
directory guard, after which `GoFilesPackage` rejects a directory at
|
||||||
|
`cmd/go/internal/load/pkg.go:3274–3281`.
|
||||||
|
- **behavior derived from the pinned implementation** — therefore this slice
|
||||||
|
does not change the shared WW resolver or `ww run`. Multiple operands,
|
||||||
|
recursive patterns, logical names, ordinary directories, regular and
|
||||||
|
non-regular sources, dangling symlinks, raw `ww test`, `test -c`, and
|
||||||
|
`test -S` retain their existing front doors.
|
||||||
|
|
||||||
|
#### Build, test, package, and import effects
|
||||||
|
|
||||||
|
- **directly measured WW behavior** — build is the primary
|
||||||
|
axis. The selected request enters the same directory enumeration, graph,
|
||||||
|
action, output, and transaction route as its direct target and the Cstage
|
||||||
|
spelling. A requested `_test.ww` basename is only request metadata; ordinary
|
||||||
|
directory production selection still excludes actual `*_test.ww` entries.
|
||||||
|
- **directly measured WW behavior** — package selection is
|
||||||
|
the mechanism. The requested symlink spelling and physical target remain
|
||||||
|
loader/presentation metadata; downstream canonical local package, graph,
|
||||||
|
action, symbol, artifact, `.wwi`, publication, and persistence identities are
|
||||||
|
unchanged. No fake raw root or spelling-specific generation is created.
|
||||||
|
- **directly measured WW behavior** — imports are exactly
|
||||||
|
the selected directory package's existing dotted imports. The change adds no
|
||||||
|
qualifier, syntax, search order, edge kind, cycle rule, or identity. It makes
|
||||||
|
the established graph reachable instead of failing before directory load.
|
||||||
|
- **directly measured WW behavior** — raw `ww test` was already aligned because
|
||||||
|
both stages independently stat literal test requests. Running tests, `-c`
|
||||||
|
retained binaries, `-S` directory diagnostics, stdout/stderr, and binary
|
||||||
|
bytes matched through the `.ww` symlink before the build fix. Test-process
|
||||||
|
topology and `-run` matching are explicit non-effects.
|
||||||
|
|
||||||
|
#### Diagnostics, artifacts, and lifecycle
|
||||||
|
|
||||||
|
- **directly measured WW behavior** — CLI parsing,
|
||||||
|
multi-root/tree delegation, leading-prefix selection, and non-directory
|
||||||
|
`_test.ww` header diagnostics keep their precedence. After directory
|
||||||
|
selection, no-source, package/import/source, output, producer, publication,
|
||||||
|
and collision diagnostics use the existing directory order and requested
|
||||||
|
path presentation. The false raw-source read diagnostic disappears only for
|
||||||
|
this build route.
|
||||||
|
- **directly measured WW behavior** — file output,
|
||||||
|
output-directory publication, exact `/dev/null`, assembly-only `-S`, default
|
||||||
|
output, and persistent `-w` reuse use the existing directory actions and
|
||||||
|
transaction. Cold success creates the same units, `.wwi`, assembly, objects,
|
||||||
|
archives, initializer products, and optional executable as Cstage. Cold
|
||||||
|
source or producer failure commits no partial generation; warm injected
|
||||||
|
compiler failure and signaled-linker failure preserve every prior public and
|
||||||
|
semantic byte and leave no `.new`, install backup, or `.wwtxn.*`.
|
||||||
|
- **directly measured WW behavior** — source/import diagnostics, compiler
|
||||||
|
failure, and a signaled linker preserve the equivalent directory request's
|
||||||
|
prior output and work bytes. The focused concurrent row proves isolated
|
||||||
|
Cstage/WWstage selected requests. The classifier itself is request-local and
|
||||||
|
read-only; unchanged directory machinery continues to own assembler,
|
||||||
|
archiver, ordinary linker and publication failures, shared action locking,
|
||||||
|
driver interruption, process reaping, rollback, and filesystem cleanup.
|
||||||
|
- **directly measured WW behavior** — direct `SIGTERM` of either driver while a
|
||||||
|
selected warm build is blocked in compilation is an inherited lifecycle
|
||||||
|
non-effect, not part of the classifier change. Both stages terminate with
|
||||||
|
shell status 143, reap the complete owned process group, and preserve the
|
||||||
|
prior public output and committed semantic bytes, but the existing directory
|
||||||
|
machinery leaves three request-private `.new` files. That independently
|
||||||
|
verified cleanup gap remains open and this slice does not describe it as
|
||||||
|
fixed.
|
||||||
|
- **directly measured WW behavior** — Cstage and WWstage agree byte-for-byte
|
||||||
|
on status, stdout, stderr, diagnostics, public output, and every semantic
|
||||||
|
artifact for selected success and failure rows. Complete
|
||||||
|
within-stage preservation includes the invoking driver's `.wwtool.ww`
|
||||||
|
provenance snapshot; cross-stage semantic comparison excludes only those
|
||||||
|
intentionally different producer bytes.
|
||||||
|
|
||||||
|
The focused WW-native observer directly measures direct and symlinked `.ww`
|
||||||
|
directories, the visible `_test.ww` spelling, regular-file omission, directory
|
||||||
|
no-source and source/import precedence, a reachable dotted dependency,
|
||||||
|
default/file/directory/null/assembly outputs, cold and warm
|
||||||
|
reuse/invalidation/rollback, compiler failure and signaled-linker interruption,
|
||||||
|
runtime reachability, selected-route concurrency, cleanup, and the exact
|
||||||
|
test/run command boundaries. No serialized representation changes: build
|
||||||
|
workdir format remains `18`, test workdir format remains `19`, semantic storage
|
||||||
|
format remains `3`, and no cache or result record is added.
|
||||||
|
|
||||||
## 12. Candidate architectures and hard-gate decision
|
## 12. Candidate architectures and hard-gate decision
|
||||||
|
|
||||||
Five candidates were developed as coherent systems, not as feature bins.
|
Five candidates were developed as coherent systems, not as feature bins.
|
||||||
|
|||||||
38
docs/spec.md
38
docs/spec.md
@@ -358,6 +358,22 @@ ImportPath = ident { "." ident } .
|
|||||||
symbol, `.wwi`, publication, or persistence identity. Build workdir format
|
symbol, `.wwi`, publication, or persistence identity. Build workdir format
|
||||||
remains 18, test workdir format remains 19, and semantic storage format
|
remains 18, test workdir format remains 19, and semantic storage format
|
||||||
remains 3.
|
remains 3.
|
||||||
|
- A local literal passed to `ww build` whose requested final basename ends
|
||||||
|
`.ww` remains a directory request when ordinary `stat` reports a directory,
|
||||||
|
including when the spelling is a symlink or ends `_test.ww`. The directory
|
||||||
|
target therefore bypasses raw named-source prefix and test-source
|
||||||
|
classification and enters ordinary directory enumeration. Actual eligible
|
||||||
|
production entries, package declarations, imports, graph actions, producers,
|
||||||
|
output policy, publication, persistence, invalidation, rollback, and cleanup
|
||||||
|
are exactly those of the existing directory package route. The requested
|
||||||
|
spelling and physical target are loader/presentation metadata only and create
|
||||||
|
no new package, dotted-import, graph, action, symbol, artifact, `.wwi`,
|
||||||
|
publication, or persistence identity. Raw `ww test`, `ww test -c`, and
|
||||||
|
`ww test -S` already use the same directory classification and are unchanged.
|
||||||
|
`ww run` retains its separate named-operand front door. Multiple operands,
|
||||||
|
recursive and logical requests, regular and non-regular source targets, and
|
||||||
|
dangling symlinks are not changed. Build workdir format remains 18, test
|
||||||
|
workdir format remains 19, and semantic storage format remains 3.
|
||||||
- `import acme.codec;` loads the canonical package `acme.codec`. If that
|
- `import acme.codec;` loads the canonical package `acme.codec`. If that
|
||||||
package declares `package wire;`, the importing file sees its exported names
|
package declares `package wire;`, the importing file sees its exported names
|
||||||
as `wire.Name`; `codec.Name` is not an additional binding. An explicit alias
|
as `wire.Name`; `codec.Name` is not an additional binding. An explicit alias
|
||||||
@@ -465,14 +481,24 @@ ImportPath = ident { "." ident } .
|
|||||||
the action list; non-main command dependencies retain ordinary actions, and
|
the action list; non-main command dependencies retain ordinary actions, and
|
||||||
a selection containing no command rejects before tools or output creation.
|
a selection containing no command rejects before tools or output creation.
|
||||||
A raw command-line `.ww` source uses its source basename without `.ww`, and
|
A raw command-line `.ww` source uses its source basename without `.ww`, and
|
||||||
a raw non-main source is the same no-command rejection. Missing
|
a raw non-main source is the same no-command rejection. An existing
|
||||||
trailing-slash hierarchies are created transactionally from `0777`, filtered
|
directory whose requested basename ends `.ww` is not such a raw source: it
|
||||||
by the caller's umask.
|
retains directory-package output naming. With no `-o`, its requested leaf
|
||||||
|
is used verbatim (for example, `directory_test.ww`); with an existing
|
||||||
|
output-directory `-o`, publication instead uses the canonical selected
|
||||||
|
target leaf (for example, `directory-target`).
|
||||||
|
Missing trailing-slash hierarchies are created transactionally from `0777`,
|
||||||
|
filtered by the caller's umask.
|
||||||
Loading and package/import diagnostics precede no-main, derived-path,
|
Loading and package/import diagnostics precede no-main, derived-path,
|
||||||
duplicate-destination, implicit-default, and other output preflight; all of
|
duplicate-destination, implicit-default, and other output preflight; all of
|
||||||
those checks precede creation. Producer failure or interruption preserves
|
those checks precede creation. Producer failure and coordinator-owned
|
||||||
existing directory contents and removes only request-created prefixes and
|
interruption preserve existing directory contents and remove only
|
||||||
stages. A non-directory output retains the single-product file/archive rule.
|
request-created prefixes and stages. Direct external `SIGTERM` of a build
|
||||||
|
driver is a verified-open exception: both stages preserve public and
|
||||||
|
committed work bytes and reap their process group, but may leave `.new`
|
||||||
|
staging files that make a later persistent-work request reject until those
|
||||||
|
files are removed. A non-directory output retains the single-product
|
||||||
|
file/archive rule.
|
||||||
If a lone
|
If a lone
|
||||||
command's synthesized default basename already names a directory, loading
|
command's synthesized default basename already names a directory, loading
|
||||||
and graph validation complete and the build rejects before tools without
|
and graph validation complete and the build rejects before tools without
|
||||||
|
|||||||
@@ -265,6 +265,26 @@ directory/recursive selection, package/import identities, graph/action
|
|||||||
identities, persistence formats (build 18, test 19, semantic 3), and test
|
identities, persistence formats (build 18, test 19, semantic 3), and test
|
||||||
process topology are explicit non-effects.
|
process topology are explicit non-effects.
|
||||||
|
|
||||||
|
An existing local directory whose requested build basename ends `.ww`
|
||||||
|
(including a visible `_test.ww` symlink to a directory) remains a directory
|
||||||
|
package, not a raw named test source. WWstage `ww build` now uses the same
|
||||||
|
symlink-following requested-kind decision as Cstage and enters the established
|
||||||
|
directory coordinator. The directory's actual `*_test.ww` entries remain
|
||||||
|
excluded from production and available to `ww test`; the requested suffix does
|
||||||
|
not itself create a test variant. Raw `ww test`, `test -c`, and `test -S` were
|
||||||
|
already stat-routed and do not change. The focused package observer directly
|
||||||
|
measures Cstage/WWstage build diagnostics and semantic/public artifact-byte
|
||||||
|
parity, direct and symlinked `.ww` directories, a dotted dependency, output
|
||||||
|
modes, cold/warm transaction behavior, compiler and signaled-linker failure
|
||||||
|
preservation, selected-route concurrency, cleanup, and the exact test-command
|
||||||
|
behavior. Direct driver interruption remains owned by the unchanged directory
|
||||||
|
machinery and is not claimed fixed here. The shared resolver and `ww run` are
|
||||||
|
deliberately unchanged—and the observer
|
||||||
|
retains their pre-existing stage difference—because pinned Go's run command
|
||||||
|
has a distinct named-file front door.
|
||||||
|
No test product, result, package/import identity, or serialized format changes;
|
||||||
|
build remains 18, test remains 19, and semantic storage remains 3.
|
||||||
|
|
||||||
List mode uses that same product process and initialization boundary but starts
|
List mode uses that same product process and initialization boundary but starts
|
||||||
no per-test child. The shared language harness emits only selected qualified
|
no per-test child. The shared language harness emits only selected qualified
|
||||||
test names, one per line in descriptor order. A valid filter selecting no tests
|
test names, one per line in descriptor order. A valid filter selecting no tests
|
||||||
|
|||||||
@@ -9069,7 +9069,14 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
let isdir: i32 = 0;
|
let isdir: i32 = 0;
|
||||||
let resolved: *u8 = resolvemodule(selfdir, src, incs.ptr, &isdir);
|
let resolved: *u8 = nil;
|
||||||
|
if (requestedliteral && !requestednondirectory
|
||||||
|
&& cstrendswithlit(src, ".ww")) {
|
||||||
|
isdir = 1;
|
||||||
|
resolved = src;
|
||||||
|
} else {
|
||||||
|
resolved = resolvemodule(selfdir, src, incs.ptr, &isdir);
|
||||||
|
};
|
||||||
if (resolved == nil) {
|
if (resolved == nil) {
|
||||||
cerrpath("ww build: cannot find module ", src, "\n");
|
cerrpath("ww build: cannot find module ", src, "\n");
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
@@ -1466,14 +1466,20 @@ fn cwdwritedata(dir: str, label: str) void = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// A visible raw test-source build observes only its initial package/import
|
// A visible raw test-source build observes only its initial package/import
|
||||||
// header. It must not turn that observation into a package, import, action,
|
// header and then omits it. A .ww-spelled directory, including a symlink with
|
||||||
// artifact, or work-state identity.
|
// a _test.ww spelling, instead enters the ordinary directory-package route.
|
||||||
|
// Neither classification may redefine package or dotted-import identity.
|
||||||
@test fn named_test_sources_are_header_loaded_then_omitted() void = {
|
@test fn named_test_sources_are_header_loaded_then_omitted() void = {
|
||||||
let root: str = fresh();
|
let root: str = fresh();
|
||||||
let source: str = strings.concat(root, "/source");
|
let source: str = strings.concat(root, "/source");
|
||||||
let dirsource: str = strings.concat(source, "/directory");
|
let include: str = strings.concat(root, "/include");
|
||||||
|
let dependency: str = strings.concat(include, "/domain/value");
|
||||||
|
let dirsource: str = strings.concat(source, "/directory.ww");
|
||||||
let dirtarget: str = strings.concat(source, "/directory-target");
|
let dirtarget: str = strings.concat(source, "/directory-target");
|
||||||
mkdirall(source); mkdirall(dirsource); mkdirall(dirtarget);
|
let emptydir: str = strings.concat(source, "/empty.ww");
|
||||||
|
let baddirtarget: str = strings.concat(source, "/broken-target");
|
||||||
|
mkdirall(source); mkdirall(dependency); mkdirall(dirsource); mkdirall(dirtarget);
|
||||||
|
mkdirall(emptydir); mkdirall(baddirtarget);
|
||||||
|
|
||||||
let ignored: str = strings.concat(source, "/raw_windows_test.ww");
|
let ignored: str = strings.concat(source, "/raw_windows_test.ww");
|
||||||
let platform: str = strings.concat(source, "/platform_windows_test.ww");
|
let platform: str = strings.concat(source, "/platform_windows_test.ww");
|
||||||
@@ -1486,11 +1492,18 @@ fn cwdwritedata(dir: str, label: str) void = {
|
|||||||
let linktarget: str = strings.concat(source, "/target.ww");
|
let linktarget: str = strings.concat(source, "/target.ww");
|
||||||
let link: str = strings.concat(source, "/requested_test.ww");
|
let link: str = strings.concat(source, "/requested_test.ww");
|
||||||
let dirlink: str = strings.concat(source, "/directory_test.ww");
|
let dirlink: str = strings.concat(source, "/directory_test.ww");
|
||||||
|
let baddirlink: str = strings.concat(source, "/broken_test.ww");
|
||||||
let rawmain: str = strings.concat(source, "/raw.ww");
|
let rawmain: str = strings.concat(source, "/raw.ww");
|
||||||
let runsource: str = strings.concat(source, "/run_test.ww");
|
let runsource: str = strings.concat(source, "/run_test.ww");
|
||||||
let testroute: str = strings.concat(source, "/route_test.ww");
|
let testroute: str = strings.concat(source, "/route_test.ww");
|
||||||
let warmpath: str = strings.concat(source, "/warm.ww");
|
let warmpath: str = strings.concat(source, "/warm.ww");
|
||||||
let warmtest: str = strings.concat(source, "/warm_test.ww");
|
let warmtest: str = strings.concat(source, "/warm_test.ww");
|
||||||
|
let dirtargettest: str = strings.concat(dirtarget, "/ignored_test.ww");
|
||||||
|
let dirtargettestexcluded: str =
|
||||||
|
"package main;\nimport missing.test.only;\n";
|
||||||
|
let dirtargettestselected: str = strings.concat(
|
||||||
|
"package main;\nimport domain.value;\n",
|
||||||
|
"@test fn directory_selected() void = { assert(value.number() == 5); };\n");
|
||||||
let header: str = strings.concat(
|
let header: str = strings.concat(
|
||||||
"package raw_test;\nimport absent.pkg;\n",
|
"package raw_test;\nimport absent.pkg;\n",
|
||||||
"this body is deliberately malformed and never reaches a producer {\n",
|
"this body is deliberately malformed and never reaches a producer {\n",
|
||||||
@@ -1513,24 +1526,40 @@ fn cwdwritedata(dir: str, label: str) void = {
|
|||||||
"package route;\n",
|
"package route;\n",
|
||||||
"@test fn still_selected() void = { assert(true); };\n"));
|
"@test fn still_selected() void = { assert(true); };\n"));
|
||||||
writefile(warmpath, "package main;\nfn main() i32 = { return 6; };\n");
|
writefile(warmpath, "package main;\nfn main() i32 = { return 6; };\n");
|
||||||
|
writefile(strings.concat(dependency, "/value.ww"), strings.concat(
|
||||||
|
"package value;\n",
|
||||||
|
"export fn number() i32 = { return 5; };\n"));
|
||||||
writefile(strings.concat(dirsource, "/main.ww"),
|
writefile(strings.concat(dirsource, "/main.ww"),
|
||||||
"package main;\nfn main() i32 = { return 3; };\n");
|
"package main;\nfn main() i32 = { return 3; };\n");
|
||||||
writefile(strings.concat(dirtarget, "/main.ww"),
|
writefile(strings.concat(dirsource, "/ignored_test.ww"),
|
||||||
"package main;\nfn main() i32 = { return 5; };\n");
|
"package main;\nimport missing.test.only;\n");
|
||||||
|
writefile(strings.concat(dirtarget, "/main.ww"), strings.concat(
|
||||||
|
"package main;\nimport domain.value;\n",
|
||||||
|
"fn main() i32 = { return value.number(); };\n"));
|
||||||
|
writefile(dirtargettest, dirtargettestexcluded);
|
||||||
|
writefile(strings.concat(baddirtarget, "/main.ww"),
|
||||||
|
"package main;\nimport ;\n");
|
||||||
assert(os.symlink(linktarget, link) == 0);
|
assert(os.symlink(linktarget, link) == 0);
|
||||||
assert(os.symlink(dirtarget, dirlink) == 0);
|
assert(os.symlink(dirtarget, dirlink) == 0);
|
||||||
|
assert(os.symlink(baddirtarget, baddirlink) == 0);
|
||||||
|
|
||||||
let compilerwrapper: str = strings.concat(root, "/testonly-w6c.sh");
|
let compilerwrapper: str = strings.concat(root, "/testonly-w6c.sh");
|
||||||
let assemblerwrapper: str = strings.concat(root, "/testonly-w6a.sh");
|
let assemblerwrapper: str = strings.concat(root, "/testonly-w6a.sh");
|
||||||
let linkerwrapper: str = strings.concat(root, "/testonly-w6l.sh");
|
let linkerwrapper: str = strings.concat(root, "/testonly-w6l.sh");
|
||||||
writeexecutable(compilerwrapper, strings.concat(
|
writeexecutable(compilerwrapper, strings.concat(
|
||||||
"#!/bin/sh\nprintf 'compile\\n' >> \"$WW_TESTONLY_CTRACE\"\n",
|
"#!/bin/sh\nprintf 'compile\\n' >> \"$WW_TESTONLY_CTRACE\"\n",
|
||||||
|
"if test \"$WW_TESTONLY_FAIL_C\" = 1; then\n",
|
||||||
|
" printf 'injected directory compiler failure\\n' >&2\n",
|
||||||
|
" exit 97\nfi\n",
|
||||||
"exec \"$WW_TESTONLY_REAL_C\" \"$@\"\n"));
|
"exec \"$WW_TESTONLY_REAL_C\" \"$@\"\n"));
|
||||||
writeexecutable(assemblerwrapper, strings.concat(
|
writeexecutable(assemblerwrapper, strings.concat(
|
||||||
"#!/bin/sh\nprintf 'assemble\\n' >> \"$WW_TESTONLY_ATRACE\"\n",
|
"#!/bin/sh\nprintf 'assemble\\n' >> \"$WW_TESTONLY_ATRACE\"\n",
|
||||||
"exec \"$WW_TESTONLY_REAL_A\" \"$@\"\n"));
|
"exec \"$WW_TESTONLY_REAL_A\" \"$@\"\n"));
|
||||||
writeexecutable(linkerwrapper, strings.concat(
|
writeexecutable(linkerwrapper, strings.concat(
|
||||||
"#!/bin/sh\nprintf 'link\\n' >> \"$WW_TESTONLY_LTRACE\"\n",
|
"#!/bin/sh\nprintf 'link\\n' >> \"$WW_TESTONLY_LTRACE\"\n",
|
||||||
|
"if test \"$WW_TESTONLY_LINK_MODE\" = signal; then\n",
|
||||||
|
" kill -TERM \"$$\"\n",
|
||||||
|
"fi\n",
|
||||||
"exec \"$WW_TESTONLY_REAL_L\" \"$@\"\n"));
|
"exec \"$WW_TESTONLY_REAL_L\" \"$@\"\n"));
|
||||||
|
|
||||||
let stages: []str = ["ww", "ww_ww"];
|
let stages: []str = ["ww", "ww_ww"];
|
||||||
@@ -1552,15 +1581,32 @@ fn cwdwritedata(dir: str, label: str) void = {
|
|||||||
let ccompiled: str = "";
|
let ccompiled: str = "";
|
||||||
let ctestasm: str = "";
|
let ctestasm: str = "";
|
||||||
let crawbin: str = "";
|
let crawbin: str = "";
|
||||||
|
let cdirectdirbin: str = "";
|
||||||
|
let cdirectdirartifacts: str = "";
|
||||||
|
let clinkdirbin: str = "";
|
||||||
|
let clinkdirartifacts: str = "";
|
||||||
|
let clinkdirasm: str = "";
|
||||||
|
let clinkdirplainasm: str = "";
|
||||||
|
let clinkdirchanged: str = "";
|
||||||
|
let clinkdirchangedartifacts: str = "";
|
||||||
|
let compilerfaildiag: str = "";
|
||||||
|
let linkersignaldiag: str = "";
|
||||||
|
let directorytestrunout: str = "";
|
||||||
|
let directorytestrunerr: str = "";
|
||||||
|
let directorytestrunartifacts: str = "";
|
||||||
|
let directorytestbin: str = "";
|
||||||
|
let directorytestcompileartifacts: str = "";
|
||||||
|
let directorytestasm: str = "";
|
||||||
let baseenv: []str = os.getenvs();
|
let baseenv: []str = os.getenvs();
|
||||||
let si: i32 = 0;
|
let si: i32 = 0;
|
||||||
for (si < stages.len) {
|
for (si < stages.len) {
|
||||||
|
rewritefile(dirtargettest, dirtargettestexcluded);
|
||||||
let ctrace: str = strings.concat(root, "/testonly-c-", tags[si]);
|
let ctrace: str = strings.concat(root, "/testonly-c-", tags[si]);
|
||||||
let atrace: str = strings.concat(root, "/testonly-a-", tags[si]);
|
let atrace: str = strings.concat(root, "/testonly-a-", tags[si]);
|
||||||
let ltrace: str = strings.concat(root, "/testonly-l-", tags[si]);
|
let ltrace: str = strings.concat(root, "/testonly-l-", tags[si]);
|
||||||
writefile(ctrace, ""); writefile(atrace, ""); writefile(ltrace, "");
|
writefile(ctrace, ""); writefile(atrace, ""); writefile(ltrace, "");
|
||||||
append(tracecs, ctrace); append(traceas, atrace); append(tracels, ltrace);
|
append(tracecs, ctrace); append(traceas, atrace); append(tracels, ltrace);
|
||||||
let env: []str = alloc([], (baseenv.len + 10): u64)!;
|
let env: []str = alloc([], (baseenv.len + 12): u64)!;
|
||||||
let ei: i32 = 0;
|
let ei: i32 = 0;
|
||||||
for (ei < baseenv.len) {
|
for (ei < baseenv.len) {
|
||||||
if (!strings.hasprefix(baseenv[ei], "WW_W6C=")
|
if (!strings.hasprefix(baseenv[ei], "WW_W6C=")
|
||||||
@@ -1580,6 +1626,10 @@ fn cwdwritedata(dir: str, label: str) void = {
|
|||||||
append(env, strings.concat("WW_TESTONLY_REAL_C=", driver(compilers[si])));
|
append(env, strings.concat("WW_TESTONLY_REAL_C=", driver(compilers[si])));
|
||||||
append(env, strings.concat("WW_TESTONLY_REAL_A=", driver(assemblers[si])));
|
append(env, strings.concat("WW_TESTONLY_REAL_A=", driver(assemblers[si])));
|
||||||
append(env, strings.concat("WW_TESTONLY_REAL_L=", driver(linkers[si])));
|
append(env, strings.concat("WW_TESTONLY_REAL_L=", driver(linkers[si])));
|
||||||
|
let failcindex: i32 = env.len;
|
||||||
|
append(env, "WW_TESTONLY_FAIL_C=");
|
||||||
|
let linkmodeindex: i32 = env.len;
|
||||||
|
append(env, "WW_TESTONLY_LINK_MODE=");
|
||||||
append(environments, env);
|
append(environments, env);
|
||||||
|
|
||||||
let out: commandout;
|
let out: commandout;
|
||||||
@@ -1745,29 +1795,289 @@ fn cwdwritedata(dir: str, label: str) void = {
|
|||||||
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
|
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
|
||||||
&& readfile(ltrace).len == 0);
|
&& readfile(ltrace).len == 0);
|
||||||
|
|
||||||
|
// Directory loading wins before raw-source suffix classification. Empty
|
||||||
|
// and malformed directories retain directory diagnostics, including the
|
||||||
|
// requested symlink spelling of a selected source.
|
||||||
|
let emptywork: str = strings.concat(root, "/empty-dir-work-", tags[si]);
|
||||||
|
let emptyout: str = strings.concat(root, "/empty-dir-output-", tags[si]);
|
||||||
|
mkdirall(emptywork);
|
||||||
|
let emptyav: []str = [driver(stages[si]), "build", "-w", emptywork,
|
||||||
|
"-I", include, "-o", emptyout, emptydir];
|
||||||
|
runcommandenv(root, strings.concat("testonly-empty-directory-", tags[si]),
|
||||||
|
emptyav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 1);
|
||||||
|
let emptywant: str = strings.concat("ww: ", emptydir,
|
||||||
|
": directory contains no WW package sources\n");
|
||||||
|
assert(out.stdout.len == 0 && same(out.stderr, emptywant)
|
||||||
|
&& !has(out.stderr, "cannot read source") && directoryisempty(emptywork)
|
||||||
|
&& !os.exists(emptyout) && readfile(ctrace).len == 0
|
||||||
|
&& readfile(atrace).len == 0 && readfile(ltrace).len == 0);
|
||||||
|
let baddirwork: str = strings.concat(root, "/bad-dir-work-", tags[si]);
|
||||||
|
let baddirout: str = strings.concat(root, "/bad-dir-output-", tags[si]);
|
||||||
|
mkdirall(baddirwork);
|
||||||
|
let baddirav: []str = [driver(stages[si]), "build", "-w", baddirwork,
|
||||||
|
"-I", include, "-o", baddirout, baddirlink];
|
||||||
|
runcommandenv(root, strings.concat("testonly-bad-directory-", tags[si]),
|
||||||
|
baddirav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 1);
|
||||||
|
let baddirwant: str = strings.concat(baddirlink,
|
||||||
|
"/main.ww:2:8: error: expected identifier, got ;\n");
|
||||||
|
assert(out.stdout.len == 0 && same(out.stderr, baddirwant)
|
||||||
|
&& !has(out.stderr, "cannot read source") && directoryisempty(baddirwork)
|
||||||
|
&& !os.exists(baddirout) && readfile(ctrace).len == 0
|
||||||
|
&& readfile(atrace).len == 0 && readfile(ltrace).len == 0);
|
||||||
|
|
||||||
|
// A real directory whose requested basename ends in .ww remains a
|
||||||
|
// directory request. The suffix does not make the directory a raw source.
|
||||||
|
let directdirwork: str = strings.concat(root, "/direct-dir-work-",
|
||||||
|
tags[si]);
|
||||||
|
let directdirout: str = strings.concat(root, "/direct-dir-output-",
|
||||||
|
tags[si]);
|
||||||
|
mkdirall(directdirwork);
|
||||||
|
let directdirav: []str = [driver(stages[si]), "build", "-w",
|
||||||
|
directdirwork, "-I", include, "-o", directdirout, dirsource];
|
||||||
|
runcommandenv(root, strings.concat("testonly-direct-directory-",
|
||||||
|
tags[si]), directdirav, env,
|
||||||
|
(60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||||
|
&& os.exists(directdirout) && readfile(ctrace).len != 0
|
||||||
|
&& readfile(atrace).len != 0 && readfile(ltrace).len != 0);
|
||||||
|
let directdirbytes: str = strings.dup(readfile(directdirout));
|
||||||
|
let directdirartifacts: str = strings.dup(
|
||||||
|
artifacttreesnapshot(directdirwork));
|
||||||
|
if (si == 0) {
|
||||||
|
cdirectdirbin = strings.dup(directdirbytes);
|
||||||
|
cdirectdirartifacts = strings.dup(directdirartifacts);
|
||||||
|
} else {
|
||||||
|
assert(same(cdirectdirbin, directdirbytes)
|
||||||
|
&& same(cdirectdirartifacts, directdirartifacts));
|
||||||
|
};
|
||||||
|
let directrun: []str = [directdirout];
|
||||||
|
runcommand(root, strings.concat("testonly-direct-directory-run-",
|
||||||
|
tags[si]), directrun, time.second, &out);
|
||||||
|
expectexit(&out, 3);
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||||
|
|
||||||
// A directory target remains a directory request despite a test-looking
|
// A directory target remains a directory request despite a test-looking
|
||||||
// requested spelling; this is deliberately the one producer-positive row.
|
// requested symlink spelling. Both stages reach the ordinary directory
|
||||||
|
// graph and produce byte-identical commands and semantic artifacts.
|
||||||
let linkdirwork: str = strings.concat(root, "/link-dir-work-", tags[si]);
|
let linkdirwork: str = strings.concat(root, "/link-dir-work-", tags[si]);
|
||||||
let linkdirout: str = strings.concat(root, "/link-dir-output-", tags[si]);
|
let linkdirout: str = strings.concat(root, "/link-dir-output-", tags[si]);
|
||||||
mkdirall(linkdirwork);
|
mkdirall(linkdirwork);
|
||||||
let linkdirav: []str = [driver(stages[si]), "build", "-w", linkdirwork,
|
let linkdirav: []str = [driver(stages[si]), "build", "-w", linkdirwork,
|
||||||
"-o", linkdirout, dirlink];
|
"-I", include, "-o", linkdirout, dirlink];
|
||||||
runcommandenv(root, strings.concat("testonly-directory-link-", tags[si]),
|
runcommandenv(root, strings.concat("testonly-directory-link-", tags[si]),
|
||||||
linkdirav, env, (60i64 * (time.second: i64)): time.duration, &out);
|
linkdirav, env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||||
|
&& os.exists(linkdirout) && readfile(ctrace).len != 0
|
||||||
|
&& readfile(atrace).len != 0 && readfile(ltrace).len != 0);
|
||||||
|
let linkdirbytes: str = strings.dup(readfile(linkdirout));
|
||||||
|
let linkdirartifacts: str = strings.dup(
|
||||||
|
artifacttreesnapshot(linkdirwork));
|
||||||
if (si == 0) {
|
if (si == 0) {
|
||||||
expectexit(&out, 0);
|
clinkdirbin = strings.dup(linkdirbytes);
|
||||||
assert(out.stdout.len == 0 && out.stderr.len == 0
|
clinkdirartifacts = strings.dup(linkdirartifacts);
|
||||||
&& os.exists(linkdirout) && readfile(ctrace).len != 0
|
|
||||||
&& readfile(atrace).len != 0 && readfile(ltrace).len != 0);
|
|
||||||
} else {
|
} else {
|
||||||
// The selected non-directory classifier must not absorb this
|
assert(same(clinkdirbin, linkdirbytes)
|
||||||
// pre-existing WWstage resolution difference into the slice.
|
&& same(clinkdirartifacts, linkdirartifacts));
|
||||||
expectexit(&out, 1);
|
|
||||||
assert(out.stdout.len == 0
|
|
||||||
&& same(out.stderr, "ww: cannot read source\n")
|
|
||||||
&& !os.exists(linkdirout) && readfile(ctrace).len == 0
|
|
||||||
&& readfile(atrace).len == 0 && readfile(ltrace).len == 0);
|
|
||||||
};
|
};
|
||||||
|
let linkdirrun: []str = [linkdirout];
|
||||||
|
runcommand(root, strings.concat("testonly-directory-link-run-", tags[si]),
|
||||||
|
linkdirrun, time.second, &out);
|
||||||
|
expectexit(&out, 5);
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||||
|
runcommandenv(root, strings.concat("testonly-directory-link-warm-",
|
||||||
|
tags[si]), linkdirav, env,
|
||||||
|
(60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||||
|
&& same(linkdirbytes, readfile(linkdirout))
|
||||||
|
&& same(clinkdirartifacts, artifacttreesnapshot(linkdirwork))
|
||||||
|
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
|
||||||
|
&& readfile(ltrace).len != 0);
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||||
|
|
||||||
|
// Output-directory presentation comes from the canonical selected target,
|
||||||
|
// while exact-null and assembly-only disposition do not change selection.
|
||||||
|
let linkoutdir: str = strings.concat(root, "/link-output-directory-",
|
||||||
|
tags[si]);
|
||||||
|
let linkoutwork: str = strings.concat(root, "/link-output-work-",
|
||||||
|
tags[si]);
|
||||||
|
mkdirall(linkoutdir); mkdirall(linkoutwork);
|
||||||
|
writefile(strings.concat(linkoutdir, "/sentinel"), "caller-owned\n");
|
||||||
|
let linkoutav: []str = [driver(stages[si]), "build", "-w", linkoutwork,
|
||||||
|
"-I", include, "-o", linkoutdir, dirlink];
|
||||||
|
runcommandenv(root, strings.concat("testonly-link-output-directory-",
|
||||||
|
tags[si]), linkoutav, env,
|
||||||
|
(60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
let linkoutchild: str = strings.concat(linkoutdir, "/directory-target");
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||||
|
&& same(readfile(strings.concat(linkoutdir, "/sentinel")),
|
||||||
|
"caller-owned\n")
|
||||||
|
&& same(linkdirbytes, readfile(linkoutchild))
|
||||||
|
&& readfile(ctrace).len != 0 && readfile(atrace).len != 0
|
||||||
|
&& readfile(ltrace).len != 0);
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||||
|
let linknullwork: str = strings.concat(root, "/link-null-work-", tags[si]);
|
||||||
|
mkdirall(linknullwork);
|
||||||
|
let linknullav: []str = [driver(stages[si]), "build", "-w", linknullwork,
|
||||||
|
"-I", include, "-o", "/dev/null", dirlink];
|
||||||
|
runcommandenv(root, strings.concat("testonly-link-null-", tags[si]),
|
||||||
|
linknullav, env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||||
|
&& readfile(ctrace).len != 0 && readfile(atrace).len != 0
|
||||||
|
&& readfile(ltrace).len != 0
|
||||||
|
&& artifacttreesnapshot(linknullwork).len != 0);
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||||
|
let asmdirwork: str = strings.concat(root, "/link-asm-work-", tags[si]);
|
||||||
|
let asmdirstem: str = strings.concat(root, "/link-asm-output-", tags[si]);
|
||||||
|
mkdirall(asmdirwork);
|
||||||
|
let asmdirav: []str = [driver(stages[si]), "build", "-S", "-w",
|
||||||
|
asmdirwork, "-I", include, "-o", asmdirstem, dirlink];
|
||||||
|
runcommandenv(root, strings.concat("testonly-link-assembly-", tags[si]),
|
||||||
|
asmdirav, env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||||
|
&& !os.exists(asmdirstem) && readfile(ctrace).len != 0
|
||||||
|
&& readfile(atrace).len == 0 && readfile(ltrace).len == 0);
|
||||||
|
let asmdirsnapshot: str = strings.dup(
|
||||||
|
artifacttreesnapshot(asmdirwork));
|
||||||
|
assert(asmdirsnapshot.len != 0);
|
||||||
|
if (si == 0) { clinkdirasm = strings.dup(asmdirsnapshot); }
|
||||||
|
else { assert(same(clinkdirasm, asmdirsnapshot)); };
|
||||||
|
|
||||||
|
// Without -o, the requested directory spelling owns presentation only.
|
||||||
|
// Plain -S keeps that selection but stops before public output, assembler,
|
||||||
|
// and link actions just like the explicit assembly-only form.
|
||||||
|
let defaultcaller: str = strings.concat(root, "/link-default-caller-",
|
||||||
|
tags[si]);
|
||||||
|
let defaultwork: str = strings.concat(root, "/link-default-work-",
|
||||||
|
tags[si]);
|
||||||
|
mkdirall(defaultcaller); mkdirall(defaultwork);
|
||||||
|
let defaultdirav: []str = [driver(stages[si]), "build", "-w",
|
||||||
|
defaultwork, "-I", include, dirlink];
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||||
|
runcommandenvdir(root, strings.concat("testonly-link-default-", tags[si]),
|
||||||
|
defaultdirav, env, defaultcaller,
|
||||||
|
(60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
let defaultdirout: str = strings.concat(defaultcaller,
|
||||||
|
"/directory_test.ww");
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||||
|
&& same(linkdirbytes, readfile(defaultdirout))
|
||||||
|
&& !os.exists(strings.concat(defaultcaller, "/directory-target"))
|
||||||
|
&& same(clinkdirartifacts, artifacttreesnapshot(defaultwork))
|
||||||
|
&& readfile(ctrace).len != 0 && readfile(atrace).len != 0
|
||||||
|
&& readfile(ltrace).len != 0);
|
||||||
|
let defaultrun: []str = [defaultdirout];
|
||||||
|
runcommand(root, strings.concat("testonly-link-default-run-", tags[si]),
|
||||||
|
defaultrun, time.second, &out);
|
||||||
|
expectexit(&out, 5);
|
||||||
|
let plainasmcaller: str = strings.concat(root, "/link-plain-asm-caller-",
|
||||||
|
tags[si]);
|
||||||
|
let plainasmwork: str = strings.concat(root, "/link-plain-asm-work-",
|
||||||
|
tags[si]);
|
||||||
|
mkdirall(plainasmcaller); mkdirall(plainasmwork);
|
||||||
|
let plainasmav: []str = [driver(stages[si]), "build", "-S", "-w",
|
||||||
|
plainasmwork, "-I", include, dirlink];
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||||
|
runcommandenvdir(root, strings.concat("testonly-link-plain-assembly-",
|
||||||
|
tags[si]), plainasmav, env, plainasmcaller,
|
||||||
|
(60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||||
|
&& !os.exists(strings.concat(plainasmcaller, "/directory_test.ww"))
|
||||||
|
&& readfile(ctrace).len != 0 && readfile(atrace).len == 0
|
||||||
|
&& readfile(ltrace).len == 0);
|
||||||
|
let plainasmsnapshot: str = strings.dup(
|
||||||
|
artifacttreesnapshot(plainasmwork));
|
||||||
|
assert(same(asmdirsnapshot, plainasmsnapshot));
|
||||||
|
if (si == 0) { clinkdirplainasm = strings.dup(plainasmsnapshot); }
|
||||||
|
else { assert(same(clinkdirplainasm, plainasmsnapshot)); };
|
||||||
|
|
||||||
|
// A failed invalidation leaves the prior command and committed work
|
||||||
|
// byte-for-byte intact. A later successful invalidation and restoration
|
||||||
|
// reuse the same directory identity rather than a raw-source identity.
|
||||||
|
let dirtargetmain: str = strings.concat(dirtarget, "/main.ww");
|
||||||
|
let dirtargetoriginal: str = strings.concat(
|
||||||
|
"package main;\nimport domain.value;\n",
|
||||||
|
"fn main() i32 = { return value.number(); };\n");
|
||||||
|
let dirtargetchanged: str = strings.concat(
|
||||||
|
"package main;\nimport domain.value;\n",
|
||||||
|
"fn main() i32 = { return value.number() + 2; };\n");
|
||||||
|
let linkdirworkbefore: str = strings.dup(treesnapshot(linkdirwork));
|
||||||
|
rewritefile(dirtargetmain, dirtargetchanged);
|
||||||
|
env[failcindex] = "WW_TESTONLY_FAIL_C=1";
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||||
|
runcommandenv(root, strings.concat("testonly-link-rollback-", tags[si]),
|
||||||
|
linkdirav, env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 1);
|
||||||
|
assert(out.stdout.len == 0
|
||||||
|
&& has(out.stderr, "injected directory compiler failure\n")
|
||||||
|
&& same(linkdirbytes, readfile(linkdirout))
|
||||||
|
&& same(linkdirworkbefore, treesnapshot(linkdirwork))
|
||||||
|
&& readfile(ctrace).len != 0 && readfile(atrace).len == 0
|
||||||
|
&& readfile(ltrace).len == 0
|
||||||
|
&& !os.exists(strings.concat(linkdirout, ".new"))
|
||||||
|
&& !directoryhasnew(linkdirwork)
|
||||||
|
&& !directoryhasfragment(linkdirwork, ".wwtxn."));
|
||||||
|
if (si == 0) { compilerfaildiag = strings.dup(out.stderr); }
|
||||||
|
else { assert(same(compilerfaildiag, out.stderr)); };
|
||||||
|
env[failcindex] = "WW_TESTONLY_FAIL_C=";
|
||||||
|
env[linkmodeindex] = "WW_TESTONLY_LINK_MODE=signal";
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||||
|
runcommandenv(root, strings.concat("testonly-link-interrupted-", tags[si]),
|
||||||
|
linkdirav, env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 1);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len != 0
|
||||||
|
&& has(out.stderr, "ww: w6l failed\n")
|
||||||
|
&& same(linkdirbytes, readfile(linkdirout))
|
||||||
|
&& same(linkdirworkbefore, treesnapshot(linkdirwork))
|
||||||
|
&& readfile(ctrace).len != 0 && readfile(atrace).len != 0
|
||||||
|
&& readfile(ltrace).len != 0
|
||||||
|
&& !os.exists(strings.concat(linkdirout, ".new"))
|
||||||
|
&& !directoryhasnew(linkdirwork)
|
||||||
|
&& !directoryhasfragment(linkdirwork, ".wwtxn."));
|
||||||
|
if (si == 0) { linkersignaldiag = strings.dup(out.stderr); }
|
||||||
|
else { assert(same(linkersignaldiag, out.stderr)); };
|
||||||
|
env[linkmodeindex] = "WW_TESTONLY_LINK_MODE=";
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||||
|
runcommandenv(root, strings.concat("testonly-link-invalidated-", tags[si]),
|
||||||
|
linkdirav, env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
let changedbytes: str = strings.dup(readfile(linkdirout));
|
||||||
|
let changedartifacts: str = strings.dup(
|
||||||
|
artifacttreesnapshot(linkdirwork));
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||||
|
&& !same(linkdirbytes, changedbytes)
|
||||||
|
&& readfile(ctrace).len != 0 && readfile(atrace).len != 0
|
||||||
|
&& readfile(ltrace).len != 0);
|
||||||
|
if (si == 0) {
|
||||||
|
clinkdirchanged = strings.dup(changedbytes);
|
||||||
|
clinkdirchangedartifacts = strings.dup(changedartifacts);
|
||||||
|
} else {
|
||||||
|
assert(same(clinkdirchanged, changedbytes)
|
||||||
|
&& same(clinkdirchangedartifacts, changedartifacts));
|
||||||
|
};
|
||||||
|
runcommand(root, strings.concat("testonly-link-invalidated-run-", tags[si]),
|
||||||
|
linkdirrun, time.second, &out);
|
||||||
|
expectexit(&out, 7);
|
||||||
|
rewritefile(dirtargetmain, dirtargetoriginal);
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||||
|
runcommandenv(root, strings.concat("testonly-link-restored-", tags[si]),
|
||||||
|
linkdirav, env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||||
|
&& same(linkdirbytes, readfile(linkdirout))
|
||||||
|
&& same(clinkdirartifacts, artifacttreesnapshot(linkdirwork))
|
||||||
|
&& readfile(ctrace).len != 0 && readfile(atrace).len != 0
|
||||||
|
&& readfile(ltrace).len != 0
|
||||||
|
&& !directoryhasnew(linkdirwork)
|
||||||
|
&& !directoryhasfragment(linkdirwork, ".wwtxn."));
|
||||||
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||||
|
|
||||||
// A valid retained raw source seeds state. Renaming only its requested
|
// A valid retained raw source seeds state. Renaming only its requested
|
||||||
@@ -1819,8 +2129,99 @@ fn cwdwritedata(dir: str, label: str) void = {
|
|||||||
&& same(cwarmartifacts, artifacttreesnapshot(warmwork)));
|
&& same(cwarmartifacts, artifacttreesnapshot(warmwork)));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Test and run keep their existing raw-source semantics, while a visible
|
// Test, test -c, and test -S already route this symlink as a directory.
|
||||||
// non-test raw build and a multi-operand error do not enter this slice.
|
// Temporarily make its otherwise build-excluded test source valid and pin
|
||||||
|
// exact stage parity without changing the negative production-build rows.
|
||||||
|
rewritefile(dirtargettest, dirtargettestselected);
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||||
|
let directorytestwork: str = strings.concat(root, "/directory-test-work-",
|
||||||
|
tags[si]);
|
||||||
|
mkdirall(directorytestwork);
|
||||||
|
let directorytestav: []str = [driver(stages[si]), "test", "-w",
|
||||||
|
directorytestwork, "-I", include, dirlink];
|
||||||
|
runcommandenv(root, strings.concat("testonly-directory-test-", tags[si]),
|
||||||
|
directorytestav, env,
|
||||||
|
(60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stderr.len == 0
|
||||||
|
&& has(out.stdout, "directory_selected ... ok\n")
|
||||||
|
&& readfile(ctrace).len != 0 && readfile(atrace).len != 0
|
||||||
|
&& readfile(ltrace).len != 0);
|
||||||
|
let directorytestrunstate: str = strings.dup(
|
||||||
|
artifacttreesnapshot(directorytestwork));
|
||||||
|
if (si == 0) {
|
||||||
|
directorytestrunout = strings.dup(out.stdout);
|
||||||
|
directorytestrunerr = strings.dup(out.stderr);
|
||||||
|
directorytestrunartifacts = strings.dup(directorytestrunstate);
|
||||||
|
} else {
|
||||||
|
assert(same(directorytestrunout, out.stdout)
|
||||||
|
&& same(directorytestrunerr, out.stderr)
|
||||||
|
&& same(directorytestrunartifacts, directorytestrunstate));
|
||||||
|
};
|
||||||
|
let directorycompiled: str = strings.concat(root,
|
||||||
|
"/directory-test-compiled-", tags[si]);
|
||||||
|
let directorycompilework: str = strings.concat(root,
|
||||||
|
"/directory-test-compile-work-", tags[si]);
|
||||||
|
mkdirall(directorycompilework);
|
||||||
|
let directorycompileav: []str = [driver(stages[si]), "test", "-c", "-w",
|
||||||
|
directorycompilework, "-I", include, "-o", directorycompiled, dirlink];
|
||||||
|
runcommandenv(root, strings.concat("testonly-directory-test-compile-",
|
||||||
|
tags[si]), directorycompileav, env,
|
||||||
|
(60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 0);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||||
|
&& os.exists(directorycompiled));
|
||||||
|
let directorycompiledbytes: str = strings.dup(readfile(directorycompiled));
|
||||||
|
let directorycompiledstate: str = strings.dup(
|
||||||
|
artifacttreesnapshot(directorycompilework));
|
||||||
|
if (si == 0) {
|
||||||
|
directorytestbin = strings.dup(directorycompiledbytes);
|
||||||
|
directorytestcompileartifacts = strings.dup(directorycompiledstate);
|
||||||
|
} else {
|
||||||
|
assert(same(directorytestbin, directorycompiledbytes)
|
||||||
|
&& same(directorytestcompileartifacts, directorycompiledstate));
|
||||||
|
};
|
||||||
|
let directorytestasmwork: str = strings.concat(root,
|
||||||
|
"/directory-test-assembly-work-", tags[si]);
|
||||||
|
let directorytestasmout: str = strings.concat(root,
|
||||||
|
"/directory-test-assembly-output-", tags[si]);
|
||||||
|
mkdirall(directorytestasmwork);
|
||||||
|
let directorytestasmav: []str = [driver(stages[si]), "test", "-S", "-w",
|
||||||
|
directorytestasmwork, "-I", include, "-o", directorytestasmout, dirlink];
|
||||||
|
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||||
|
runcommandenv(root, strings.concat("testonly-directory-test-assembly-",
|
||||||
|
tags[si]), directorytestasmav, env,
|
||||||
|
(60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
expectexit(&out, 2);
|
||||||
|
assert(out.stdout.len == 0
|
||||||
|
&& same(out.stderr, "ww test: -S needs a single test file\n")
|
||||||
|
&& !os.exists(directorytestasmout)
|
||||||
|
&& directoryisempty(directorytestasmwork)
|
||||||
|
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
|
||||||
|
&& readfile(ltrace).len == 0);
|
||||||
|
if (si == 0) { directorytestasm = strings.dup(out.stderr); }
|
||||||
|
else { assert(same(directorytestasm, out.stderr)); };
|
||||||
|
|
||||||
|
// Run was not changed by the build-only dispatcher fix. Cstage follows
|
||||||
|
// the selected directory and executes it; WWstage retains its exact
|
||||||
|
// pre-existing raw-source failure for the same requested symlink.
|
||||||
|
let directoryrunav: []str = [driver(stages[si]), "run", "-I", include,
|
||||||
|
dirlink];
|
||||||
|
runcommandenv(root, strings.concat("testonly-directory-run-", tags[si]),
|
||||||
|
directoryrunav, env,
|
||||||
|
(60i64 * (time.second: i64)): time.duration, &out);
|
||||||
|
if (si == 0) {
|
||||||
|
expectexit(&out, 5);
|
||||||
|
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||||
|
} else {
|
||||||
|
expectexit(&out, 1);
|
||||||
|
assert(out.stdout.len == 0
|
||||||
|
&& same(out.stderr, "ww: cannot read source\n"));
|
||||||
|
};
|
||||||
|
rewritefile(dirtargettest, dirtargettestexcluded);
|
||||||
|
|
||||||
|
// Raw-source test and run retain their separate existing semantics, while
|
||||||
|
// a visible non-test raw build and multi-operand error stay out of this slice.
|
||||||
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||||
let testwork: str = strings.concat(root, "/route-work-", tags[si]);
|
let testwork: str = strings.concat(root, "/route-work-", tags[si]);
|
||||||
mkdirall(testwork);
|
mkdirall(testwork);
|
||||||
@@ -1891,9 +2292,76 @@ fn cwdwritedata(dir: str, label: str) void = {
|
|||||||
assert(same(commentbad[0], commentbad[1]));
|
assert(same(commentbad[0], commentbad[1]));
|
||||||
assert(same(multibad[0], multibad[1]));
|
assert(same(multibad[0], multibad[1]));
|
||||||
|
|
||||||
|
// Simultaneous selected-directory requests use isolated work and output
|
||||||
|
// transactions while retaining exact cross-stage product and artifact bytes.
|
||||||
|
let ti: i32 = 0;
|
||||||
|
for (ti < stages.len) {
|
||||||
|
rewritefile(tracecs[ti], "");
|
||||||
|
rewritefile(traceas[ti], "");
|
||||||
|
rewritefile(tracels[ti], "");
|
||||||
|
ti += 1;
|
||||||
|
};
|
||||||
|
let pdcwork: str = strings.concat(root, "/parallel-directory-c-work");
|
||||||
|
let pdwwork: str = strings.concat(root, "/parallel-directory-ww-work");
|
||||||
|
let pdcout: str = strings.concat(root, "/parallel-directory-c-output");
|
||||||
|
let pdwout: str = strings.concat(root, "/parallel-directory-ww-output");
|
||||||
|
mkdirall(pdcwork); mkdirall(pdwwork);
|
||||||
|
let pdcav: []str = [driver("ww"), "build", "-w", pdcwork, "-I", include,
|
||||||
|
"-o", pdcout, dirlink];
|
||||||
|
let pdwav: []str = [driver("ww_ww"), "build", "-w", pdwwork, "-I",
|
||||||
|
include, "-o", pdwout, dirlink];
|
||||||
|
let pdc: exec.command;
|
||||||
|
pdc.path = pdcav[0]; pdc.argv = pdcav; pdc.env = environments[0];
|
||||||
|
pdc.dir = repo();
|
||||||
|
pdc.stdoutpath = strings.concat(root, "/parallel-directory-c.stdout");
|
||||||
|
pdc.stderrpath = strings.concat(root, "/parallel-directory-c.stderr");
|
||||||
|
pdc.deadline = time.add(time.now(time.clock.monotonic),
|
||||||
|
(60i64 * (time.second: i64)): time.duration);
|
||||||
|
pdc.grace = (100i64 * (time.millisecond: i64)): time.duration;
|
||||||
|
let pdw: exec.command;
|
||||||
|
pdw.path = pdwav[0]; pdw.argv = pdwav; pdw.env = environments[1];
|
||||||
|
pdw.dir = repo();
|
||||||
|
pdw.stdoutpath = strings.concat(root, "/parallel-directory-ww.stdout");
|
||||||
|
pdw.stderrpath = strings.concat(root, "/parallel-directory-ww.stderr");
|
||||||
|
pdw.deadline = time.add(time.now(time.clock.monotonic),
|
||||||
|
(60i64 * (time.second: i64)): time.duration);
|
||||||
|
pdw.grace = (100i64 * (time.millisecond: i64)): time.duration;
|
||||||
|
let pdcp: exec.process;
|
||||||
|
let pdwp: exec.process;
|
||||||
|
exec.start(&pdcp, &pdc); exec.start(&pdwp, &pdw);
|
||||||
|
let pdcdone: bool = false;
|
||||||
|
let pdwdone: bool = false;
|
||||||
|
for (!pdcdone || !pdwdone) {
|
||||||
|
if (!pdcdone) { pdcdone = exec.poll(&pdcp); };
|
||||||
|
if (!pdwdone) { pdwdone = exec.poll(&pdwp); };
|
||||||
|
if (!pdcdone || !pdwdone) {
|
||||||
|
time.sleep(time.millisecond, time.clock.monotonic);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
assert(pdcp.result.errno == 0 && pdcp.result.cleanuperrno == 0
|
||||||
|
&& pdcp.result.termination == exec.termination.EXIT
|
||||||
|
&& pdcp.result.code == 0 && pdwp.result.errno == 0
|
||||||
|
&& pdwp.result.cleanuperrno == 0
|
||||||
|
&& pdwp.result.termination == exec.termination.EXIT
|
||||||
|
&& pdwp.result.code == 0);
|
||||||
|
assert(readfile(pdc.stdoutpath).len == 0
|
||||||
|
&& readfile(pdc.stderrpath).len == 0
|
||||||
|
&& readfile(pdw.stdoutpath).len == 0
|
||||||
|
&& readfile(pdw.stderrpath).len == 0
|
||||||
|
&& same(clinkdirbin, readfile(pdcout))
|
||||||
|
&& same(clinkdirbin, readfile(pdwout))
|
||||||
|
&& same(clinkdirartifacts, artifacttreesnapshot(pdcwork))
|
||||||
|
&& same(clinkdirartifacts, artifacttreesnapshot(pdwwork))
|
||||||
|
&& readfile(tracecs[0]).len != 0 && readfile(traceas[0]).len != 0
|
||||||
|
&& readfile(tracels[0]).len != 0 && readfile(tracecs[1]).len != 0
|
||||||
|
&& readfile(traceas[1]).len != 0 && readfile(tracels[1]).len != 0
|
||||||
|
&& !directoryhasnew(pdcwork) && !directoryhasnew(pdwwork)
|
||||||
|
&& !directoryhasfragment(pdcwork, ".wwtxn.")
|
||||||
|
&& !directoryhasfragment(pdwwork, ".wwtxn."));
|
||||||
|
|
||||||
// Two simultaneous omitted requests have no work/output collision and no
|
// Two simultaneous omitted requests have no work/output collision and no
|
||||||
// producer invocation in either stage.
|
// producer invocation in either stage.
|
||||||
let ti: i32 = 0;
|
ti = 0;
|
||||||
for (ti < stages.len) {
|
for (ti < stages.len) {
|
||||||
rewritefile(tracecs[ti], "");
|
rewritefile(tracecs[ti], "");
|
||||||
rewritefile(traceas[ti], "");
|
rewritefile(traceas[ti], "");
|
||||||
|
|||||||
Reference in New Issue
Block a user