test: prove case-fold collision preflight

This commit is contained in:
2026-08-15 16:11:01 +09:00
parent 265be3f796
commit 6974bec878
4 changed files with 1037 additions and 13 deletions

View File

@@ -311,11 +311,21 @@ and matches its final single `linux` token, exactly as Go does.
Selection is additive, not replacement-based: every matching file belongs to
the package. The production variant then excludes `*_test.ww`; internal and
external test classification uses only the already platform-selected test
files. An excluded file creates no source occurrence, import, dependency edge,
package/action/variant identity, compiler input, export, archive member, link
input, artifact, status, or persistence dependency. Adding or editing one is a
producer no-op. Adding, removing, or editing a selected file changes the owning
unit normally.
files. After those decisions, distinct selected basenames in one canonical
directory MUST NOT be equal under Go 1.26.5's Unicode simple-fold comparison.
The check spans the production, internal-test, and external-test selections of
one `ww test` product without merging those source units. An ordinary
`ww build` sees production names only. Exact basename reuse by another action
view of that directory is not a collision.
An excluded file creates no source occurrence, collision, import, dependency
edge, package/action/variant identity, compiler input, export, archive member,
link input, artifact, status, or persistence dependency. Adding or editing one
is a producer no-op. Adding, removing, or editing a selected noncolliding file
changes the owning unit normally. This applicability is deliberately narrower
than Go's `Package.AllFiles`: WW omits wrong-platform and `*_test.ww` names
from an ordinary build because those files are not loaded in WW's
fixed-target, manifest-free source model.
WW implements no source-level build expressions, user tags, target descriptor,
`UseAllFiles` escape, `+tag` replacement scheme, or manifest-defined selector.
@@ -5588,6 +5598,154 @@ source-level build expressions, quoted/grouped/dot imports, and targets other
than the separately specified fixed `linux/amd64` filename selection. None is
used to define canonical package or directory-product identity.
### 11.23 Implemented case-fold collision preflight
This slice pins the collision semantics to Go 1.26.5, tag commit
`c19862e5f8415b4f24b189d065ed739517c548ba`. The official command loader uses
an exact import cache at `src/cmd/go/internal/load/pkg.go:633-636,757-775`,
performs contextual and vendor resolution before cache lookup at
`:840-911,974-1005`, owns one command-global folded import table at `:1725`,
and rejects a second fold-equivalent import spelling at `:1950-1959`. Its
selected-name collision is over `Package.AllFiles` (`:149-194`) and is applied
at `:1991-2000`. `src/cmd/go/internal/str/str.go:32-89` defines the pinned
`ToFold` and `FoldDup` algorithms; the direct, transitive, and filename
expectations are in
`src/cmd/go/testdata/script/list_case_collision.txt:1-41`. Filename discovery
and package/test classification are ordered by
`src/go/build/build.go:859-914,1005-1036,1076-1082,1450-1469`.
Before this slice, a Linux case-sensitive filesystem let distinct directories
such as `domain.Foo` and `domain.foo`, expanded vendor identities, and selected
files such as `File.ww` and `file.ww` build as unrelated packages or sources.
Both Cstage and WWstage did so byte-identically. A symlink making the two import
spellings reach one directory happened to trip the older exact
directory/identity check, but that host-physical consequence was neither the Go
diagnostic nor the required package-graph rule.
#### Exact identity and request-only folded keys
Canonical package identity remains the exact, case-sensitive effective dotted
identity. It is still the key used by action interning, source import maps,
`.wwi` ownership, mangled symbols, storage digests, archives, diagnostics, and
link closure. Default bindings, explicit aliases, blank imports, declared
package names, path leaves, artifacts, and physical directories do not replace
it. Exact repeated occurrences therefore continue to form one canonical edge
and one package action.
The loader graph additionally owns a checked, dynamically grown, request-only
table from a simple-fold key to the first exact package representative. Every
ordinary effective identity registers after contextual local/vendor expansion
and before exact action reuse or physical-directory conflict checks. Exact
reuse succeeds. A distinct spelling with the same key rejects as
`ww: case-insensitive import collision: "A" and "a"`; WW byte-sorts the two
exact spellings so root, request, import, and discovery order cannot select a
different diagnostic. It never stores, interns, looks up, or publishes the
folded spelling as package identity.
WW dotted import components are ASCII by construction: the C lexer/parser
accept them through `cmd/wcc/lex.c:53-63` and
`cmd/wcc/parse.c:1318-1353`, and the self-hosted syntax path mirrors that in
`lib/ww/syntax/lex.ww` and `lib/ww/syntax/decl.ww`. Context-derived identities
are revalidated, and arbitrary local filesystem bytes are escaped into ASCII.
The general Unicode fold routine is nevertheless shared with filename
preflight; for package identities its observable domain reduces exactly to
ASCII case folding.
Vendor imports register the fully expanded canonical identity, not the short
source spelling. Thus `lib.Foo` and `lib.foo` reached below one vendor owner
collide as, for example, `domain.app.vendor.lib.Foo` and
`domain.app.vendor.lib.foo`. Reaching one physical directory through two such
identities changes neither ownership nor the diagnostic. The physical
directory remains a resolution fact, never a substitute identity.
Production, same-package test (`ptest`), external test (`pxtest`), and
product-scoped recompiled actions share their one exact ordinary package
representative and do not re-register as different packages. External
compiler identity may still carry `_test`; that action path is not the folded
package key. Copy-on-write clones copy exact identity and do not register.
Generated main has no ordinary import identity and does not register. Ordinary
toolchain package `test` registers like any real package; only the reserved
synthetic `__wwtest` role stays outside the ordinary representative table.
#### Selected source basenames
Directory enumeration first excludes leading-dot, leading-underscore,
wrong-platform, and variant-ineligible files, validates the selected regular
sources, and byte-sorts their names. Before delegation, the coordinator performs
its required package-clause classification and parses selected production files
to reject `@test` declarations outside `*_test.ww`. The delegated driver then
registers each selected basename in a second request-only table scoped by
canonical physical directory. Repeated views of the exact same selected
basename across `p`, `ptest`, or `pxtest` are accepted. Distinct fold-equivalent
basenames reject as
`ww: case-insensitive file name collision: "File.ww" and "file.ww"` before the
driver's graph-import scan or any producer. The preflight is not an earlier
replacement for the coordinator-owned source validation parse.
One `ww test` request shares that directory scope across production,
same-package test, external test, same-only, external-only, and mixed test-only
actions while preserving their separate compilation units. This catches a
collision crossing classifications, such as production `X_TEST.ww` and test
`x_test.ww`. An ordinary `ww build` excludes `*_test.ww` before registration.
Hidden, underscore-prefixed, and wrong-platform files never register and
therefore create no collision or invalidation.
This last ordering is WW's explicit applicability boundary rather than a claim
that every upstream `AllFiles` member is selected here. Go includes test files
and some ignored Go files in `AllFiles`, so its ordinary build can diagnose a
broader set. WW intentionally follows its existing fixed-target source
eligibility and build/test isolation: files it does not load have no graph or
persistence effect.
Filesystem basenames are arbitrary non-NUL bytes, so their fold keys reproduce
the pinned Unicode 15.0 `unicode.SimpleFold` minimum-cycle behavior without
locale or normalization. Each malformed UTF-8 byte contributes one U+FFFD to
the temporary key, as Go string ranging does; diagnostics preserve the exact
original byte and quote it as `\xNN`. Printable Unicode remains UTF-8, other
nonprinting runes use Go-style `\u` or `\U` escapes, and composed/decomposed
Unicode spellings are not normalized.
#### Tool, artifact, transaction, and persistence ownership
The coordinator owns initial eligibility and production/test classification;
the delegated driver loader owns both fold checks. The language parser still
owns the exact import occurrence and qualifier. The driver owns per-site self,
internal, vendor, and imported-main structural legality, while the compiler
checker owns file-local binding, use, and visibility. The export writer/reader
owns exact canonical `.wwi` data. Compiler, assembler, archiver, and linker
protocols did not change. Successful neighboring units, `.wwi`, assembly,
objects, archives, generated mains, binaries, and exact tool argv therefore
remain byte-identical in Cstage and WWstage.
All root and reachable dependency loading, fold registration, and final exact
identity binding finish before scratch acquisition, support or generated-main
producer work, compilation, assembly, archive construction, or linking. A
collision invokes none of those tools and creates no unit, `.wwi`, assembly,
object, archive, main, binary, result, status, voucher, stamp, or `.new` stage.
Request-wide publication remains transactional: committed sibling and
dependency bytes survive a newly introduced collision, and removing the
colliding source restores precise warm reuse.
The fold tables live only for one command and are freed at graph teardown.
Entries become live only after every owned string and vector allocation
succeeds, so allocation failure cannot publish a partial table or make cleanup
traverse uninitialized entries. Successful action/unit/storage content did not
change; build workdir format stays `18`, test workdir format stays `19`, and
semantic storage stays `3`. A format bump would only discard valid exact-key
artifacts and is therefore not used.
Native proof extends `package_graph_diagnostics_are_stable`,
`platform_filename_source_selection`, and
`vendor_directory_import_resolution`, with command-global allocation failure
retained in `allocation_failure_is_command_global`. The matrix covers direct,
transitive, reversed, recursive, same-directory, and vendor-expanded imports;
exact-repeat acceptance; reversed vendor import and product order;
production/internal/external/test-only filenames; reversed creation and
direct/recursive collision-diagnostic parity; cross-classification, Unicode,
invalid UTF-8, ignored files, and absence of normalization; zero-tool
rejection; multi-product publication isolation; cold/warm add-remove reuse;
exact artifact preservation; and Cstage/WWstage diagnostic and byte identity.
## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins.

View File

@@ -269,8 +269,14 @@ ImportPath = ident { "." ident } .
are ignored. Eligible names are byte-sorted before source validation.
Production excludes selected `*_test.ww`; test variants classify only those
selected test files. An excluded file contributes no declarations, imports,
package edge, action, export, artifact, initialization, test, or persistent
invalidation.
filename collision, package edge, action, export, artifact, initialization,
test, or persistent invalidation. After eligibility, two distinct selected
basenames in one canonical directory that are equal under Go 1.26.5 Unicode
simple folding are rejected after the coordinator's required package-clause
classification and production `@test` validation parses, but before the
delegated graph-import scan or tools. An ordinary build compares production
names only; one test product compares its production, same-package test, and
external-test selections without merging their units.
- `import acme.codec;` loads the canonical package `acme.codec`. If that
package declares `package wire;`, the importing file sees its exported names
as `wire.Name`; `codec.Name` is not an additional binding. An explicit alias
@@ -294,6 +300,12 @@ ImportPath = ident { "." ident } .
edge. Test-only occurrences never enter ordinary production. Self-import is
rejected, except that toolchain-owned external-test self wiring is rebound to
the effective augmented package action after ordinary per-site validation.
- Canonical import identity is exact and case-sensitive. After contextual local
or vendor expansion, two distinct effective identities that are equal under
Go 1.26.5 simple folding are a request-wide structural error. Folding is only
a temporary collision key: it never changes lookup, action identity, `.wwi`
ownership, symbols, artifacts, storage, or diagnostics. Repeated occurrences
of the same exact identity remain valid and deduplicate normally.
- An executable package is one declared `package main` and containing a
`fn main`; path and directory spelling do not classify commands. An ordinary
import of a package declared `main` is rejected, except for the toolchain's
@@ -618,6 +630,13 @@ under test binds to the augmented white-box action when it exists; affected
transitive importers are copied and rewired so ordinary and augmented package
state do not coexist in the linked closure.
Those variants are action distinctions over exact package representatives, not
new ordinary package identities for case-fold comparison. Production,
same-package test, external test, and recompiled copies of one exact canonical
package therefore do not collide with each other. Their selected source units
remain separate, while the directory-owned filename preflight spans the
production/internal/external selections applicable to that test product.
Before a test function runs, the one generated product initializes its exact
effective graph dependency-first and once per canonical action. Imports,
runtime lets, and init declarations found only in `*_test.ww` never enter an

View File

@@ -242,6 +242,22 @@ directory entry order. A recursive pattern omits a directory with no eligible
source, while an explicit wrong-target-only build rejects it as having no WW
package source. There is no source-level build-expression or user-tag mode.
After that eligibility boundary and the coordinator's required package-clause
classification and production `@test` validation parses, the delegated loader
performs selected-basename Go 1.26.5 simple-fold preflight before its graph
import scan. During graph loading, effective canonical imports register only
after contextual local/vendor expansion; exact identity reuse is accepted, but
two distinct fold-equivalent identities reject the entire shared plan.
Selected basenames register in one canonical-directory scope. A production
build registers production names only. A test plan shares the filename scope
across production, internal-test, and external-test actions, including
same-only, external-only, and mixed directories, without combining their source
units. Hidden, underscore-prefixed, wrong-platform, and otherwise ineligible
files never register. Package variants, product-scoped recompiled copies,
support, and generated main remain action wiring over exact identities and do
not manufacture package collisions. The fold preflight does not replace or
precede those coordinator-owned source-validation parses.
Separate compilation is the only driver build path; no compatibility mode
switch remains.
@@ -294,6 +310,24 @@ have the same `ww: out of memory` diagnostic, invoke no compiler, assembler, or
linker, and leave an empty caller work directory with no output, status, or
staging path.
The same observer now owns selected-basename folding: ASCII production,
internal, external, same-only, external-only, mixed, and cross-classification
collisions; printable Unicode Kelvin folding; malformed UTF-8 replacement-rune
keys with exact `\xNN` diagnostics; accepted composed/decomposed neighbors;
ordinary-build isolation from test collisions; ignored folded pairs; reversed
creation order; and direct/recursive collision-diagnostic parity. It proves zero
compiler/assembler/linker calls and an empty workdir, including no
test-support/main artifact, on cold rejection. A committed warm package then
gains and loses a colliding file; the failed middle request preserves every
unit, `.wwi`, assembly, object, archive, binary, tool record, and stamp, and
removal restores exact reuse.
`package_graph_diagnostics_are_stable` provides the corresponding direct,
transitive, reversed-root, recursive, same-directory, exact-repeat, and warm
import proof. `vendor_directory_import_resolution` proves folding uses the
expanded vendor identity under reversed imports and product descriptors, even
when both spellings reach one physical directory, and that multi-product
rejection publishes no output, status, tool state, or staging residue.
`test/sep/sepinit_test.ww` is the single focused package-initialization owner.
It generates all source trees temporarily and runs independent cold/persistent
Cstage and WWstage legs. Its matrix covers blank-only reachability; dependency,
@@ -353,6 +387,13 @@ real directory test products always run, while no-selected-test directories do
not create a process. The byte-identity and bootstrap gates keep building on
fresh scratch. `make clean` reclaims every workdir under `out/`.
Fold keys and first-spelling tables are request-only loader state and are never
stored in a workdir. They do not alter successful unit bytes, action/storage
keys, or tool records, so build format 18, test format 19, and semantic storage
format 3 remain unchanged. Structural fold rejection occurs before staging and
cannot mutate a committed generation; a later request with the collision
removed evaluates the original exact-key reuse contract.
On a package or tree target, `ww build/test -w DIR` forwards that exact caller
path as the one command-global semantic-action store; neither pattern spelling
nor package-group name derives a persistent subdirectory. The delegated driver

View File

@@ -481,6 +481,36 @@ fn directoryhasnew(path: str) bool = {
return false;
};
fn directoryisempty(path: str) bool = {
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
assert(fd >= 0);
let buf: []u8 = alloc([], 16384u64)!;
buf.len = 16384;
let n: i64 = os.getdents64(fd, buf.ptr, buf.len: u64);
for (n > 0i64) {
let off: i32 = 0;
for (off < n: i32) {
let reclen: i32 = (buf[off + 16]: i32)
+ ((buf[off + 17]: i32) * 256);
assert(reclen >= 20 && off + reclen <= n: i32);
let len: i32 = 0;
for (buf[off + 19 + len] != 0u8) { len += 1; };
let dot: bool = len == 1 && buf[off + 19] == '.';
let dotdot: bool = len == 2 && buf[off + 19] == '.'
&& buf[off + 20] == '.';
if (!dot && !dotdot) {
assert(os.close(fd) == 0);
return false;
};
off += reclen;
};
n = os.getdents64(fd, buf.ptr, buf.len: u64);
};
assert(n == 0i64);
assert(os.close(fd) == 0);
return true;
};
fn byteshasat(s: str, off: i32, want: str) bool = {
if (off < 0 || off + want.len > s.len) { return false; };
let i: i32 = 0;
@@ -5749,6 +5779,23 @@ fn hexbytes(value: str) str = {
let identityactual: str = strings.concat(root, "/identityactual");
let identityalias: str = strings.concat(root, "/identityalias");
let reserved: str = strings.concat(root, "/reservedprobe");
let foldsource: str = strings.concat(root, "/foldsource");
let foldupper: str = strings.concat(foldsource, "/domain/Foo");
let foldlower: str = strings.concat(foldsource, "/domain/foo");
let folddirect: str = strings.concat(foldsource, "/cmd/direct");
let foldbackward: str = strings.concat(foldsource, "/cmd/backward");
let foldindirect: str = strings.concat(foldsource, "/cmd/indirect");
let foldleft: str = strings.concat(foldsource, "/parent/left");
let foldright: str = strings.concat(foldsource, "/parent/right");
let foldrepeat: str = strings.concat(foldsource, "/cmd/repeat");
let foldaliasroot: str = strings.concat(foldsource, "/aliasroot");
let foldaliasactual: str = strings.concat(foldsource, "/alias/Foo");
let foldalias: str = strings.concat(foldsource, "/alias/foo");
let foldrootleft: str = strings.concat(foldsource, "/roots/left");
let foldrootright: str = strings.concat(foldsource, "/roots/right");
let foldrecursive: str = strings.concat(root, "/foldrecursive");
let foldrecursiveupper: str = strings.concat(foldrecursive, "/domain/Foo");
let foldrecursivelower: str = strings.concat(foldrecursive, "/domain/foo");
assert(os.mkdir(missing, 448i32) == 0);
assert(os.mkdir(cycle, 448i32) == 0);
assert(os.mkdir(left, 448i32) == 0);
@@ -5758,6 +5805,11 @@ fn hexbytes(value: str) str = {
assert(os.mkdir(identityroot, 448i32) == 0);
assert(os.mkdir(identityactual, 448i32) == 0);
assert(os.mkdir(reserved, 448i32) == 0);
mkdirall(foldupper); mkdirall(foldlower); mkdirall(folddirect);
mkdirall(foldbackward); mkdirall(foldindirect); mkdirall(foldleft);
mkdirall(foldright); mkdirall(foldrepeat); mkdirall(foldaliasroot);
mkdirall(foldaliasactual); mkdirall(foldrootleft); mkdirall(foldrootright);
mkdirall(foldrecursiveupper); mkdirall(foldrecursivelower);
writefile(strings.concat(missing, "/missing.ww"),
"package missing;\nfn local() void = { };\n");
writefile(strings.concat(missing, "/missing_test.ww"), strings.concat(
@@ -5798,6 +5850,49 @@ fn hexbytes(value: str) str = {
writefile(strings.concat(reserved, "/reservedprobe.ww"), strings.concat(
"package reservedprobe;\nimport __wwlocal.fake;\n",
"export fn value() i32 = { return 1; };\n"));
writefile(strings.concat(foldupper, "/pkg.ww"),
"package upperdecl;\nexport fn value() i32 = { return 1; };\n");
writefile(strings.concat(foldlower, "/pkg.ww"),
"package lowerdecl;\nexport fn value() i32 = { return 2; };\n");
// File creation and import order deliberately disagree with byte order.
writefile(strings.concat(folddirect, "/z.ww"), strings.concat(
"package main;\nimport Lower domain.foo;\n",
"fn main() i32 = { return 0; };\n"));
writefile(strings.concat(folddirect, "/a.ww"),
"package main;\nimport Upper domain.Foo;\n");
writefile(strings.concat(foldbackward, "/a.ww"),
"package main;\nimport Lower domain.foo;\n");
writefile(strings.concat(foldbackward, "/z.ww"), strings.concat(
"package main;\nimport Upper domain.Foo;\n",
"fn main() i32 = { return 0; };\n"));
writefile(strings.concat(foldleft, "/pkg.ww"), strings.concat(
"package left;\nimport _ domain.Foo;\n",
"export fn value() i32 = { return 1; };\n"));
writefile(strings.concat(foldright, "/pkg.ww"), strings.concat(
"package right;\nimport _ domain.foo;\n",
"export fn value() i32 = { return 2; };\n"));
writefile(strings.concat(foldindirect, "/main.ww"), strings.concat(
"package main;\nimport _ parent.left;\nimport _ parent.right;\n",
"fn main() i32 = { return 0; };\n"));
writefile(strings.concat(foldrepeat, "/a.ww"),
"package main;\nimport _ domain.Foo;\n");
writefile(strings.concat(foldrepeat, "/z.ww"), strings.concat(
"package main;\nimport _ domain.Foo;\n",
"fn main() i32 = { return 0; };\n"));
writefile(strings.concat(foldaliasactual, "/pkg.ww"),
"package aliasdecl;\nexport fn value() i32 = { return 1; };\n");
assert(os.symlink(foldaliasactual, foldalias) == 0);
writefile(strings.concat(foldaliasroot, "/main.ww"), strings.concat(
"package main;\nimport _ alias.Foo;\nimport _ alias.foo;\n",
"fn main() i32 = { return 0; };\n"));
writefile(strings.concat(foldrootleft, "/pkg.ww"),
"package leftroot;\nimport _ domain.Foo;\n");
writefile(strings.concat(foldrootright, "/pkg.ww"),
"package rightroot;\nimport _ domain.foo;\n");
writefile(strings.concat(foldrecursiveupper, "/pkg.ww"),
"package upper;\nexport fn value() i32 = { return 1; };\n");
writefile(strings.concat(foldrecursivelower, "/pkg.ww"),
"package lower;\nexport fn value() i32 = { return 2; };\n");
rejectpackagestable(root, "missing", missing,
"cannot find package nowhere");
rejectpackagestable(root, "cycle", cycle, "ww: dependency cycle:");
@@ -5808,32 +5903,46 @@ fn hexbytes(value: str) str = {
let stages: []str = ["ww", "ww_ww"];
let tooltrace: str = strings.concat(root, "/identity-tool.trace");
let compilerwrapper: str = strings.concat(root, "/identity-w6c.sh");
let assemblerwrapper: str = strings.concat(root, "/identity-w6a.sh");
let linkerwrapper: str = strings.concat(root, "/identity-w6l.sh");
writefile(tooltrace, "");
writeexecutable(compilerwrapper, strings.concat(
"#!/bin/sh\nprintf 'compile\\n' >> \"$WW_IDENTITY_TRACE\"\n",
"#!/bin/sh\nprintf 'compile' >> \"$WW_IDENTITY_TRACE\"\n",
"for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ",
"\"$WW_IDENTITY_TRACE\"; done\nprintf '\\n' >> ",
"\"$WW_IDENTITY_TRACE\"\n",
"exec \"$WW_IDENTITY_W6C\" \"$@\"\n"));
writeexecutable(assemblerwrapper, strings.concat(
"#!/bin/sh\nprintf 'assemble' >> \"$WW_IDENTITY_TRACE\"\n",
"for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ",
"\"$WW_IDENTITY_TRACE\"; done\nprintf '\\n' >> ",
"\"$WW_IDENTITY_TRACE\"\n",
"exec \"$WW_IDENTITY_W6A\" \"$@\"\n"));
writeexecutable(linkerwrapper, strings.concat(
"#!/bin/sh\nprintf 'link\\n' >> \"$WW_IDENTITY_TRACE\"\n",
"#!/bin/sh\nprintf 'link' >> \"$WW_IDENTITY_TRACE\"\n",
"for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ",
"\"$WW_IDENTITY_TRACE\"; done\nprintf '\\n' >> ",
"\"$WW_IDENTITY_TRACE\"\n",
"exec \"$WW_IDENTITY_W6L\" \"$@\"\n"));
let baseenv: []str = os.getenvs();
let traceenv: []str = alloc([], (baseenv.len + 5): u64)!;
let traceenv: []str = alloc([], (baseenv.len + 4): u64)!;
let ei: i32 = 0;
for (ei < baseenv.len) {
if (!strings.hasprefix(baseenv[ei], "WW_W6C=")
&& !strings.hasprefix(baseenv[ei], "WW_W6A=")
&& !strings.hasprefix(baseenv[ei], "WW_W6L=")
&& !strings.hasprefix(baseenv[ei], "WW_IDENTITY_TRACE=")
&& !strings.hasprefix(baseenv[ei], "WW_IDENTITY_W6C=")
&& !strings.hasprefix(baseenv[ei], "WW_IDENTITY_W6A=")
&& !strings.hasprefix(baseenv[ei], "WW_IDENTITY_W6L=")) {
append(traceenv, baseenv[ei]);
};
ei += 1;
};
append(traceenv, strings.concat("WW_W6C=", compilerwrapper));
append(traceenv, strings.concat("WW_W6A=", assemblerwrapper));
append(traceenv, strings.concat("WW_W6L=", linkerwrapper));
append(traceenv, strings.concat("WW_IDENTITY_TRACE=", tooltrace));
append(traceenv, strings.concat("WW_IDENTITY_W6C=", driver("w6c")));
append(traceenv, strings.concat("WW_IDENTITY_W6L=", driver("w6l")));
let out: commandout;
let i: i32 = 0;
let identitydiagnostics: []str = ["", ""];
@@ -5857,6 +5966,187 @@ fn hexbytes(value: str) str = {
// compiler or linker invocation. The converse is covered by the shared
// multi-context resolution test above.
assert(readfile(tooltrace).len == 0);
let foldexpected: str =
"ww: case-insensitive import collision: \"domain.Foo\" and \"domain.foo\"\n";
let aliasexpected: str =
"ww: case-insensitive import collision: \"alias.Foo\" and \"alias.foo\"\n";
let foldtargets: []str = [folddirect, foldbackward, foldindirect,
foldaliasroot];
let foldlabels: []str = ["direct", "backward", "indirect", "one-dir"];
let foldwant: []str = [foldexpected, foldexpected, foldexpected,
aliasexpected];
let compilers: []str = ["w6c", "w6c_ww"];
let assemblers: []str = ["w6a", "w6a_ww"];
let linkers: []str = ["w6l", "w6l_ww"];
let foldrefs: []str = ["", "", "", "", "", "", ""];
let repeatref: str = "";
i = 0;
for (i < stages.len) {
let foldenv: []str = alloc([], (traceenv.len + 3): u64)!;
let tei: i32 = 0;
for (tei < traceenv.len) {
append(foldenv, traceenv[tei]); tei += 1;
};
append(foldenv, strings.concat("WW_IDENTITY_W6C=",
driver(compilers[i])));
append(foldenv, strings.concat("WW_IDENTITY_W6A=",
driver(assemblers[i])));
append(foldenv, strings.concat("WW_IDENTITY_W6L=",
driver(linkers[i])));
let fi: i32 = 0;
for (fi < foldtargets.len) {
rewritefile(tooltrace, "");
let work: str = strings.concat(root, "/fold-", foldlabels[fi],
"-", stages[i], "-work");
assert(os.mkdir(work, 448i32) == 0);
let output: str = strings.concat(root, "/fold-", foldlabels[fi],
"-", stages[i]);
let av: []str = [driver(stages[i]), "build", "-w", work,
"-I", foldsource, "-o", output, foldtargets[fi]];
runcommandenv(root, strings.concat("case-fold-", foldlabels[fi],
"-", stages[i]), av, foldenv,
(60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(out.stdout.len == 0 && same(out.stderr, foldwant[fi]));
assert(readfile(tooltrace).len == 0);
assert(!os.exists(output));
assert(!os.exists(strings.concat(work, "/.wwtool.stamp")));
assert(directoryisempty(work));
if (i == 0) { foldrefs[fi] = strings.dup(out.stderr); }
else { assert(same(foldrefs[fi], out.stderr)); };
fi += 1;
};
// The coordinator canonicalizes product order, so reversing roots cannot
// choose a different representative or diagnostic.
let rootfirst: []str = [foldrootleft, foldrootright];
let rootsecond: []str = [foldrootright, foldrootleft];
let oi: i32 = 0;
for (oi < rootfirst.len) {
rewritefile(tooltrace, "");
let work: str = strings.concat(root, "/fold-roots-", stages[i],
"-", foldlabels[oi], "-work");
assert(os.mkdir(work, 448i32) == 0);
let av: []str = [driver(stages[i]), "build", "-w", work,
"-I", foldsource, rootfirst[oi], rootsecond[oi]];
runcommandenv(root, strings.concat("case-fold-roots-", stages[i],
"-", foldlabels[oi]), av, foldenv,
(60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(out.stdout.len == 0 && has(out.stderr, foldexpected));
assert(readfile(tooltrace).len == 0);
assert(!os.exists(strings.concat(work, "/.wwtool.stamp")));
assert(directoryisempty(work));
let refindex: i32 = foldtargets.len + oi;
if (i == 0) { foldrefs[refindex] = strings.dup(out.stderr); }
else { assert(same(foldrefs[refindex], out.stderr)); };
oi += 1;
};
assert(same(foldrefs[4], foldrefs[5]));
rewritefile(tooltrace, "");
let recursivework: str = strings.concat(root, "/fold-recursive-",
stages[i], "-work");
assert(os.mkdir(recursivework, 448i32) == 0);
let recursiveav: []str = [driver(stages[i]), "build", "-w",
recursivework, "-I", foldrecursive,
strings.concat(foldrecursive, "/...")];
runcommandenv(root, strings.concat("case-fold-recursive-", stages[i]),
recursiveav, foldenv,
(60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(out.stdout.len == 0 && has(out.stderr, foldexpected));
assert(readfile(tooltrace).len == 0);
assert(!os.exists(strings.concat(recursivework, "/.wwtool.stamp")));
assert(directoryisempty(recursivework));
if (i == 0) { foldrefs[6] = strings.dup(out.stderr); }
else { assert(same(foldrefs[6], out.stderr)); };
// Exact repeated occurrences retain one canonical action and accepted
// compiler input, rather than being merged by the folded key.
rewritefile(tooltrace, "");
let repeatwork: str = strings.concat(root, "/fold-repeat-", stages[i],
"-work");
let repeatout: str = strings.concat(root, "/fold-repeat-", stages[i]);
assert(os.mkdir(repeatwork, 448i32) == 0);
let repeatav: []str = [driver(stages[i]), "build", "-w", repeatwork,
"-I", foldsource, "-o", repeatout, foldrepeat];
runcommandenv(root, strings.concat("case-fold-repeat-", stages[i]),
repeatav, foldenv,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0);
let repeattrace: str = readfile(tooltrace);
assert(occurrences(repeattrace,
"/domain.Foo.unit.new>") == 1);
let repeatline: str = linecontaining(repeattrace,
"/cmd.repeat.unit.new>");
assert(occurrences(repeatline, "<--import><domain.Foo>") == 1);
assert(occurrences(repeatline, "/domain.Foo.wwi.new>") == 1);
assert(os.exists(strings.concat(repeatwork, "/.wwtool.stamp")));
let repeatpaths: []str = ["/.wwtool.ww", "/.wwtool.w6c",
"/.wwtool.w6a", "/.wwtool.stamp", "/cmd.repeat.unit.ww",
"/cmd.repeat.wwi", "/cmd.repeat.s", "/cmd.repeat.o",
"/cmd.repeat.a", "/cmd.repeat.init.unit.ww",
"/cmd.repeat.init.s", "/cmd.repeat.init.o",
"/domain.Foo.unit.ww", "/domain.Foo.wwi", "/domain.Foo.s",
"/domain.Foo.o", "/domain.Foo.a"];
let repeatbytes: []str = alloc([], repeatpaths.len: u64)!;
let rsi: i32 = 0;
for (rsi < repeatpaths.len) {
append(repeatbytes, strings.dup(readfile(strings.concat(repeatwork,
repeatpaths[rsi]))));
rsi += 1;
};
let repeatoutput: str = strings.dup(readfile(repeatout));
if (i == 0) { repeatref = strings.dup(repeatoutput); }
else { assert(same(repeatref, repeatoutput)); };
let repeatcollision: str = strings.concat(foldrepeat, "/m.ww");
writefile(repeatcollision,
"package main;\nimport _ domain.foo;\n");
rewritefile(tooltrace, "");
runcommandenv(root, strings.concat("case-fold-repeat-added-", stages[i]),
repeatav, foldenv,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(same(out.stderr, foldexpected));
assert(readfile(tooltrace).len == 0);
rsi = 0;
for (rsi < repeatpaths.len) {
assert(same(repeatbytes[rsi], readfile(strings.concat(repeatwork,
repeatpaths[rsi]))));
rsi += 1;
};
assert(!directoryhasnew(repeatwork));
assert(same(repeatoutput, readfile(repeatout)));
assert(!os.exists(strings.concat(repeatout, ".new")));
assert(!os.exists(strings.concat(repeatout, ".sepwork")));
assert(!os.exists(strings.concat(repeatwork, "/domain.foo.unit.ww")));
assert(!os.exists(strings.concat(repeatwork, "/domain.foo.wwi")));
assert(!os.exists(strings.concat(repeatwork, "/domain.foo.a")));
assert(os.remove(repeatcollision) == 0);
rewritefile(tooltrace, "");
runcommandenv(root, strings.concat("case-fold-repeat-removed-", stages[i]),
repeatav, foldenv,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(occurrences(readfile(tooltrace), "compile") == 0);
assert(occurrences(readfile(tooltrace), "assemble") == 0);
assert(occurrences(readfile(tooltrace), "link") == 1);
rsi = 0;
for (rsi < repeatpaths.len) {
assert(same(repeatbytes[rsi], readfile(strings.concat(repeatwork,
repeatpaths[rsi]))));
rsi += 1;
};
assert(!directoryhasnew(repeatwork));
assert(same(repeatoutput, readfile(repeatout)));
assert(!os.exists(strings.concat(repeatout, ".new")));
assert(!os.exists(strings.concat(repeatout, ".sepwork")));
i += 1;
};
rewritefile(tooltrace, "");
let reserveddiagnostics: []str = ["", ""];
i = 0;
for (i < stages.len) {
@@ -5889,9 +6179,25 @@ fn hexbytes(value: str) str = {
let vendored: str = strings.concat(app, "/vendor/vendored");
let order: str = strings.concat(root, "/order");
let tools: str = strings.concat(root, "/tools");
let foldfiles: str = strings.concat(root, "/foldfiles");
let foldunicode: str = strings.concat(foldfiles, "/unicode");
let foldinvalid: str = strings.concat(foldfiles, "/invalid");
let foldnonormal: str = strings.concat(foldfiles, "/nonormal");
let foldinternal: str = strings.concat(foldfiles, "/internal");
let foldexternal: str = strings.concat(foldfiles, "/external");
let foldsameonly: str = strings.concat(foldfiles, "/sameonly");
let foldexternalonly: str = strings.concat(foldfiles, "/externalonly");
let foldmixed: str = strings.concat(foldfiles, "/mixed");
let foldcross: str = strings.concat(foldfiles, "/cross");
let foldcreationroot: str = strings.concat(root, "/fold-creation-order");
let foldcreation: str = strings.concat(foldcreationroot, "/prod");
mkdirall(app); mkdirall(good); mkdirall(pkg); mkdirall(testgood);
mkdirall(wrongonly); mkdirall(notests); mkdirall(internal);
mkdirall(command); mkdirall(vendored); mkdirall(order); mkdirall(tools);
mkdirall(foldunicode); mkdirall(foldinvalid); mkdirall(foldnonormal);
mkdirall(foldinternal); mkdirall(foldexternal); mkdirall(foldsameonly);
mkdirall(foldexternalonly); mkdirall(foldmixed); mkdirall(foldcross);
mkdirall(foldcreation);
let good5: str = strings.concat("package good;\n",
"export fn value() i32 = { return 5; };\n");
@@ -5949,6 +6255,12 @@ fn hexbytes(value: str) str = {
ignoredinternal);
writefile(strings.concat(app, "/badmain_linux_arm64.ww"), ignoredmain);
writefile(strings.concat(app, "/badvendor_windows.ww"), ignoredvendor);
writefile(strings.concat(app, "/.Fold.ww"), ignoredparse);
writefile(strings.concat(app, "/.fold.ww"), ignoredparseedit);
writefile(strings.concat(app, "/_Fold.ww"), ignoredparse);
writefile(strings.concat(app, "/_fold.ww"), ignoredparseedit);
writefile(strings.concat(app, "/Fold_windows.ww"), ignoredparse);
writefile(strings.concat(app, "/fold_windows.ww"), ignoredparseedit);
writefile(strings.concat(internal, "/secret.ww"), strings.concat(
"package secret;\n",
"export fn value() i32 = { return 19; };\n"));
@@ -6004,6 +6316,76 @@ fn hexbytes(value: str) str = {
writefile(strings.concat(notests, "/base.ww"),
"package notests;\nexport fn value() i32 = { return 1; };\n");
writefile(strings.concat(notests, "/only_windows_test.ww"), ignoredparse);
writefile(strings.concat(pkg, "/.Fold_test.ww"), ignoredparse);
writefile(strings.concat(pkg, "/.fold_test.ww"), ignoredparseedit);
writefile(strings.concat(pkg, "/_Fold_test.ww"), ignoredparse);
writefile(strings.concat(pkg, "/_fold_test.ww"), ignoredparseedit);
writefile(strings.concat(pkg, "/Fold_windows_test.ww"), ignoredparse);
writefile(strings.concat(pkg, "/fold_windows_test.ww"), ignoredparseedit);
writefile(strings.concat(foldunicode, "/K.ww"),
"package main;\nfn latin() i32 = { return 1; };\n");
writefile(strings.concat(foldunicode, "/.ww"),
"package main;\nfn kelvin() i32 = { return 2; };\n");
writefile(strings.concat(foldunicode, "/main.ww"),
"package main;\nfn main() i32 = { return 0; };\n");
let invalidupperbytes: []u8 = alloc([], 6u64)!;
append(invalidupperbytes, 'A'); append(invalidupperbytes, '-');
append(invalidupperbytes, 255u8); append(invalidupperbytes, '.');
append(invalidupperbytes, 'w'); append(invalidupperbytes, 'w');
let invalidlowerbytes: []u8 = alloc([], 6u64)!;
append(invalidlowerbytes, 'a'); append(invalidlowerbytes, '-');
append(invalidlowerbytes, 254u8); append(invalidlowerbytes, '.');
append(invalidlowerbytes, 'w'); append(invalidlowerbytes, 'w');
writefile(strings.concat(foldinvalid, "/",
strings.frombytes(invalidupperbytes)),
"package main;\nfn invalidupper() i32 = { return 1; };\n");
writefile(strings.concat(foldinvalid, "/",
strings.frombytes(invalidlowerbytes)),
"package main;\nfn invalidlower() i32 = { return 2; };\n");
writefile(strings.concat(foldinvalid, "/main.ww"),
"package main;\nfn main() i32 = { return 0; };\n");
writefile(strings.concat(foldnonormal, "/É.ww"),
"package main;\nfn composed() i32 = { return 1; };\n");
writefile(strings.concat(foldnonormal, "/É.ww"),
"package main;\nfn decomposed() i32 = { return 2; };\n");
writefile(strings.concat(foldnonormal, "/main.ww"),
"package main;\nfn main() i32 = { return 0; };\n");
writefile(strings.concat(foldinternal, "/base.ww"),
"package sample;\nexport fn value() i32 = { return 1; };\n");
writefile(strings.concat(foldinternal, "/File_test.ww"),
"package sample;\n@test fn upper() void = { assert(true); };\n");
writefile(strings.concat(foldinternal, "/file_test.ww"),
"package sample;\n@test fn lower() void = { assert(true); };\n");
writefile(strings.concat(foldexternal, "/base.ww"),
"package sample;\nexport fn value() i32 = { return 1; };\n");
writefile(strings.concat(foldexternal, "/File_test.ww"),
"package sample_test;\n@test fn upper() void = { assert(true); };\n");
writefile(strings.concat(foldexternal, "/file_test.ww"),
"package sample_test;\n@test fn lower() void = { assert(true); };\n");
writefile(strings.concat(foldsameonly, "/File_test.ww"),
"package sample;\n@test fn upper() void = { assert(true); };\n");
writefile(strings.concat(foldsameonly, "/file_test.ww"),
"package sample;\n@test fn lower() void = { assert(true); };\n");
writefile(strings.concat(foldexternalonly, "/File_test.ww"),
"package sample_test;\n@test fn upper() void = { assert(true); };\n");
writefile(strings.concat(foldexternalonly, "/file_test.ww"),
"package sample_test;\n@test fn lower() void = { assert(true); };\n");
writefile(strings.concat(foldmixed, "/File_test.ww"),
"package sample;\n@test fn upper() void = { assert(true); };\n");
writefile(strings.concat(foldmixed, "/file_test.ww"),
"package sample_test;\n@test fn lower() void = { assert(true); };\n");
writefile(strings.concat(foldcross, "/X_TEST.ww"),
"package cross;\nexport fn value() i32 = { return 1; };\n");
writefile(strings.concat(foldcross, "/x_test.ww"),
"package cross;\n@test fn lower() void = { assert(value() == 1); };\n");
// The lower spelling is created first to prove getdents creation order is
// irrelevant to both direct and recursive collision diagnostics.
writefile(strings.concat(foldcreation, "/file.ww"),
"package main;\nfn lower() i32 = { return 1; };\n");
writefile(strings.concat(foldcreation, "/File.ww"), strings.concat(
"package main;\nfn upper() i32 = { return 2; };\n",
"fn main() i32 = { return 0; };\n"));
// Creation order is deliberately the inverse of byte order.
writefile(strings.concat(order, "/z_linux.ww"),
"package order;\n@test fn z() void = { };\n");
@@ -6061,6 +6443,12 @@ fn hexbytes(value: str) str = {
let faildiagref: str = "";
let recursiveoutref: str = "";
let recursiveerrref: str = "";
let foldfilerefs: []str = ["", "", "", "", "", "", "", ""];
let crossref: str = "";
let nonormalref: str = "";
let nonormalunitref: str = "";
let foldcreationref: str = "";
let foldcreationrecursiveref: str = "";
let baseenv: []str = os.getenvs();
let si: i32 = 0;
for (si < stages.len) {
@@ -6513,6 +6901,267 @@ fn hexbytes(value: str) str = {
assert(same(recursiveoutref, out.stdout));
assert(same(recursiveerrref, out.stderr));
};
// An upper-case TEST suffix is production source, while its lower-case
// twin is test source. Ordinary build stays isolated; test selects both
// categories into one directory-owned filename-collision scope.
let crosswork: str = strings.concat(root, "/", labels[si],
"-fold-cross-build-work");
let crossout: str = strings.concat(root, "/", labels[si],
"-fold-cross.a");
assert(os.mkdir(crosswork, 448i32) == 0);
rewritefile(compilertrace, ""); rewritefile(assemblertrace, "");
rewritefile(linkertrace, "");
let crossbuildav: []str = [driver(stages[si]), "build", "-w",
crosswork, "-I", foldfiles, "-o", crossout, foldcross];
runcommandenv(root, strings.concat("fold-cross-build-", labels[si]),
crossbuildav, env,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0);
assert(!has(readfile(strings.concat(crosswork, "/cross.unit.ww")),
"@test fn lower"));
if (si == 0) { crossref = strings.dup(readfile(crossout)); }
else { assert(same(crossref, readfile(crossout))); };
let foldtargets: []str = [foldunicode, foldinvalid, foldinternal,
foldexternal, foldsameonly, foldexternalonly, foldmixed, foldcross];
let foldmodes: []str = ["build", "build", "test", "test", "test",
"test", "test", "test"];
let foldpackages: []str = ["", "", "sample", "sample", "sample",
"sample", "sample", "cross"];
let foldlabels: []str = ["unicode", "invalid", "internal", "external",
"same-only", "external-only", "mixed", "cross"];
let foldwants: []str = [
"ww: case-insensitive file name collision: \"K.ww\" and \".ww\"\n",
"ww: case-insensitive file name collision: \"A-\\xff.ww\" and \"a-\\xfe.ww\"\n",
"ww: case-insensitive file name collision: \"File_test.ww\" and \"file_test.ww\"\n",
"ww: case-insensitive file name collision: \"File_test.ww\" and \"file_test.ww\"\n",
"ww: case-insensitive file name collision: \"File_test.ww\" and \"file_test.ww\"\n",
"ww: case-insensitive file name collision: \"File_test.ww\" and \"file_test.ww\"\n",
"ww: case-insensitive file name collision: \"File_test.ww\" and \"file_test.ww\"\n",
"ww: case-insensitive file name collision: \"X_TEST.ww\" and \"x_test.ww\"\n",
];
let fi: i32 = 0;
for (fi < foldtargets.len) {
let foldwork: str = strings.concat(root, "/", labels[si],
"-fold-", foldlabels[fi], "-work");
let foldoutput: str = strings.concat(root, "/", labels[si],
"-fold-", foldlabels[fi]);
assert(os.mkdir(foldwork, 448i32) == 0);
rewritefile(compilertrace, ""); rewritefile(assemblertrace, "");
rewritefile(linkertrace, "");
let foldbuildav: []str = [driver(stages[si]), "build", "-w",
foldwork, "-I", foldfiles, "-o", foldoutput, foldtargets[fi]];
let foldtestav: []str = [driver(stages[si]), "test", "-w",
foldwork, "-I", foldfiles, foldtargets[fi]];
let foldav: []str = foldtestav;
if (same(foldmodes[fi], "build")) { foldav = foldbuildav; };
runcommandenv(root, strings.concat("filename-fold-", labels[si],
"-", foldlabels[fi]), foldav, env,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
let foldfullwant: str = foldwants[fi];
if (same(foldmodes[fi], "test")) {
foldfullwant = strings.concat(foldwants[fi], "FAIL ",
foldtargets[fi], " [", foldpackages[fi],
"] (build exit 1)\n");
};
assert(out.stdout.len == 0 && same(out.stderr, foldfullwant));
assert(readfile(compilertrace).len == 0);
assert(readfile(assemblertrace).len == 0);
assert(readfile(linkertrace).len == 0);
assert(!os.exists(foldoutput));
assert(!os.exists(strings.concat(foldoutput, ".new")));
assert(!os.exists(strings.concat(foldoutput, ".sepwork")));
assert(!os.exists(strings.concat(foldwork, "/.wwtool.stamp")));
assert(directoryisempty(foldwork));
if (si == 0) { foldfilerefs[fi] = strings.dup(out.stderr); }
else { assert(same(foldfilerefs[fi], out.stderr)); };
fi += 1;
};
let creationmodes: []str = ["direct", "recursive"];
let creationfull: []str = ["", ""];
let creationwant: str =
"ww: case-insensitive file name collision: \"File.ww\" and \"file.ww\"\n";
let ci: i32 = 0;
for (ci < creationmodes.len) {
let creationwork: str = strings.concat(root, "/", labels[si],
"-fold-creation-", creationmodes[ci], "-work");
let creationout: str = strings.concat(root, "/", labels[si],
"-fold-creation-", creationmodes[ci]);
assert(os.mkdir(creationwork, 448i32) == 0);
rewritefile(compilertrace, ""); rewritefile(assemblertrace, "");
rewritefile(linkertrace, "");
let directav: []str = [driver(stages[si]), "build", "-w",
creationwork, "-I", foldcreationroot, "-o", creationout,
foldcreation];
if (ci != 0) { assert(os.mkdir(creationout, 448i32) == 0); };
let recursiveav: []str = [driver(stages[si]), "build", "-w",
creationwork, "-I", foldcreationroot, "-o",
strings.concat(creationout, "/"),
strings.concat(foldcreationroot, "/...")];
let creationav: []str = directav;
if (ci != 0) { creationav = recursiveav; };
runcommandenv(root, strings.concat("filename-fold-creation-",
labels[si], "-", creationmodes[ci]), creationav, env,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(out.stdout.len == 0);
let creationfullwant: str = creationwant;
if (ci != 0) {
creationfullwant = strings.concat(creationwant, "FAIL ",
foldcreation, " [main] (build exit 1)\n");
};
assert(same(out.stderr, creationfullwant));
assert(readfile(compilertrace).len == 0);
assert(readfile(assemblertrace).len == 0);
assert(readfile(linkertrace).len == 0);
if (ci == 0) { assert(!os.exists(creationout)); }
else { assert(directoryisempty(creationout)); };
assert(!os.exists(strings.concat(creationout, ".new")));
assert(!os.exists(strings.concat(creationout, ".sepwork")));
assert(directoryisempty(creationwork));
creationfull[ci] = strings.dup(out.stderr);
ci += 1;
};
if (si == 0) {
foldcreationref = strings.dup(creationfull[0]);
foldcreationrecursiveref = strings.dup(creationfull[1]);
} else {
assert(same(foldcreationref, creationfull[0]));
assert(same(foldcreationrecursiveref, creationfull[1]));
};
// Canonically equivalent Unicode normalization forms remain distinct.
let nonormalwork: str = strings.concat(root, "/", labels[si],
"-fold-nonormal-work");
let nonormalout: str = strings.concat(root, "/", labels[si],
"-fold-nonormal");
assert(os.mkdir(nonormalwork, 448i32) == 0);
rewritefile(compilertrace, ""); rewritefile(assemblertrace, "");
rewritefile(linkertrace, "");
let nonormalav: []str = [driver(stages[si]), "build", "-w",
nonormalwork, "-I", foldfiles, "-o", nonormalout, foldnonormal];
runcommandenv(root, strings.concat("filename-no-normalization-",
labels[si]), nonormalav, env,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0);
let nonormalunit: str = readfile(strings.concat(nonormalwork,
"/nonormal.unit.ww"));
assert(has(nonormalunit, "fn composed() i32"));
assert(has(nonormalunit, "fn decomposed() i32"));
if (si == 0) { nonormalref = strings.dup(readfile(nonormalout)); }
else { assert(same(nonormalref, readfile(nonormalout))); };
if (si == 0) { nonormalunitref = strings.dup(nonormalunit); }
else { assert(same(nonormalunitref, nonormalunit)); };
// A collision introduced after a committed generation is pre-tool and
// cannot mutate any successful action, stamp, or published output. Its
// removal restores exact warm reuse (only the normal relink remains).
let warmname: str = strings.concat("warm", labels[si]);
let warmdir: str = strings.concat(foldfiles, "/", warmname);
mkdirall(warmdir);
writefile(strings.concat(warmdir, "/File.ww"), strings.concat(
"package main;\nfn upper() i32 = { return 1; };\n"));
writefile(strings.concat(warmdir, "/main.ww"), strings.concat(
"package main;\nfn main() i32 = { return upper() - 1; };\n"));
let warmwork: str = strings.concat(root, "/", labels[si],
"-fold-warm-work");
let warmout: str = strings.concat(root, "/", labels[si],
"-fold-warm");
assert(os.mkdir(warmwork, 448i32) == 0);
let warmav: []str = [driver(stages[si]), "build", "-w", warmwork,
"-I", foldfiles, "-o", warmout, warmdir];
rewritefile(compilertrace, ""); rewritefile(assemblertrace, "");
rewritefile(linkertrace, "");
runcommandenv(root, strings.concat("filename-fold-warm-cold-",
labels[si]), warmav, env,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
let warmsuffixes: []str = [".unit.ww", ".wwi", ".s", ".o", ".a",
".init.unit.ww", ".init.s", ".init.o"];
let warmrefs: []str = ["", "", "", "", "", "", "", ""];
let wi: i32 = 0;
for (wi < warmsuffixes.len) {
warmrefs[wi] = strings.dup(readfile(strings.concat(warmwork, "/",
warmname, warmsuffixes[wi])));
wi += 1;
};
let warmbinref: str = strings.dup(readfile(warmout));
let warmstampref: str = strings.dup(readfile(strings.concat(warmwork,
"/.wwtool.stamp")));
let warmtoolpaths: []str = ["/.wwtool.ww", "/.wwtool.w6c",
"/.wwtool.w6a"];
let warmtoolrefs: []str = alloc([], warmtoolpaths.len: u64)!;
wi = 0;
for (wi < warmtoolpaths.len) {
append(warmtoolrefs, strings.dup(readfile(strings.concat(warmwork,
warmtoolpaths[wi]))));
wi += 1;
};
let collidingfile: str = strings.concat(warmdir, "/file.ww");
writefile(collidingfile,
"package main;\nfn lower() i32 = { return 2; };\n");
rewritefile(compilertrace, ""); rewritefile(assemblertrace, "");
rewritefile(linkertrace, "");
runcommandenv(root, strings.concat("filename-fold-warm-added-",
labels[si]), warmav, env,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(same(out.stderr,
"ww: case-insensitive file name collision: \"File.ww\" and \"file.ww\"\n"));
assert(readfile(compilertrace).len == 0);
assert(readfile(assemblertrace).len == 0);
assert(readfile(linkertrace).len == 0);
wi = 0;
for (wi < warmsuffixes.len) {
assert(same(warmrefs[wi], readfile(strings.concat(warmwork, "/",
warmname, warmsuffixes[wi]))));
wi += 1;
};
wi = 0;
for (wi < warmtoolpaths.len) {
assert(same(warmtoolrefs[wi], readfile(strings.concat(warmwork,
warmtoolpaths[wi]))));
wi += 1;
};
assert(!directoryhasnew(warmwork));
assert(same(warmbinref, readfile(warmout)));
assert(!os.exists(strings.concat(warmout, ".new")));
assert(!os.exists(strings.concat(warmout, ".sepwork")));
assert(same(warmstampref, readfile(strings.concat(warmwork,
"/.wwtool.stamp"))));
assert(os.remove(collidingfile) == 0);
rewritefile(compilertrace, ""); rewritefile(assemblertrace, "");
rewritefile(linkertrace, "");
runcommandenv(root, strings.concat("filename-fold-warm-removed-",
labels[si]), warmav, env,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(readfile(compilertrace).len == 0);
assert(readfile(assemblertrace).len == 0);
assert(occurrences(readfile(linkertrace), "\n") == 1);
wi = 0;
for (wi < warmsuffixes.len) {
assert(same(warmrefs[wi], readfile(strings.concat(warmwork, "/",
warmname, warmsuffixes[wi]))));
wi += 1;
};
wi = 0;
for (wi < warmtoolpaths.len) {
assert(same(warmtoolrefs[wi], readfile(strings.concat(warmwork,
warmtoolpaths[wi]))));
wi += 1;
};
assert(!directoryhasnew(warmwork));
assert(same(warmbinref, readfile(warmout)));
assert(!os.exists(strings.concat(warmout, ".new")));
assert(!os.exists(strings.concat(warmout, ".sepwork")));
assert(same(warmstampref, readfile(strings.concat(warmwork,
"/.wwtool.stamp"))));
si += 1;
};
clean(root);
@@ -8508,6 +9157,26 @@ fn runtimepath(relative: str) str = {
let cleanroot: str = strings.concat(root, "/clean");
let cleanclient: str = strings.concat(cleanroot, "/domain/app");
let cleanordinary: str = strings.concat(cleanroot, "/lib/math");
let foldvendorupper: str = strings.concat(source,
"/domain/fold/vendor/lib/Foo");
let foldvendorlower: str = strings.concat(source,
"/domain/fold/vendor/lib/foo");
let foldvendorleft: str = strings.concat(source, "/domain/fold/left");
let foldvendorright: str = strings.concat(source, "/domain/fold/right");
let foldvendorclient: str = strings.concat(source, "/domain/fold/client");
let foldvendorreverseclient: str = strings.concat(source,
"/domain/fold/reverseclient");
let foldaliasvendorlib: str = strings.concat(source,
"/domain/foldalias/vendor/lib");
let foldaliasvendorupper: str = strings.concat(foldaliasvendorlib, "/Foo");
let foldaliasvendorlower: str = strings.concat(foldaliasvendorlib, "/foo");
let foldaliasvendorleft: str = strings.concat(source,
"/domain/foldalias/left");
let foldaliasvendorright: str = strings.concat(source,
"/domain/foldalias/right");
let foldaliasvendorclient: str = strings.concat(source,
"/domain/foldalias/client");
let foldaliasvendorvault: str = strings.concat(root, "/fold-vendor-vault");
let dirs: []str = [leaf, nearest, outer, rootvendor, ordinary, client,
one, two, outerclient, rootclient, emptyclient,
@@ -8523,6 +9192,9 @@ fn runtimepath(relative: str) str = {
alpha, beta, strings.concat(alpha, "/vendor/lib"),
strings.concat(beta, "/vendor/lib"), pairvault, boundaryclient,
boundaryordinary, boundaryabove, cleanclient, cleanordinary,
foldvendorupper, foldvendorlower, foldvendorleft, foldvendorright,
foldvendorclient, foldvendorreverseclient, foldaliasvendorlib, foldaliasvendorleft,
foldaliasvendorright, foldaliasvendorclient, foldaliasvendorvault,
runtime, tools];
let di: i32 = 0;
for (di < dirs.len) { mkdirall(dirs[di]); di += 1; };
@@ -8649,6 +9321,35 @@ fn runtimepath(relative: str) str = {
writefile(strings.concat(cleanclient, "/main.ww"), mainbody);
writefile(strings.concat(cleanordinary, "/math.ww"),
"package math;\nexport fn value() i32 = { return 44; };\n");
writefile(strings.concat(foldvendorupper, "/pkg.ww"),
"package upper;\nexport fn value() i32 = { return 1; };\n");
writefile(strings.concat(foldvendorlower, "/pkg.ww"),
"package lower;\nexport fn value() i32 = { return 2; };\n");
writefile(strings.concat(foldvendorleft, "/pkg.ww"), strings.concat(
"package left;\nimport _ lib.Foo;\n",
"export fn value() i32 = { return 1; };\n"));
writefile(strings.concat(foldvendorright, "/pkg.ww"), strings.concat(
"package right;\nimport _ lib.foo;\n",
"export fn value() i32 = { return 2; };\n"));
writefile(strings.concat(foldvendorclient, "/main.ww"), strings.concat(
"package main;\nimport _ domain.fold.left;\n",
"import _ domain.fold.right;\nfn main() i32 = { return 0; };\n"));
writefile(strings.concat(foldvendorreverseclient, "/main.ww"), strings.concat(
"package main;\nimport _ domain.fold.right;\n",
"import _ domain.fold.left;\nfn main() i32 = { return 0; };\n"));
writefile(strings.concat(foldaliasvendorvault, "/pkg.ww"),
"package shared;\nexport fn value() i32 = { return 1; };\n");
assert(os.symlink(foldaliasvendorvault, foldaliasvendorupper) == 0);
assert(os.symlink(foldaliasvendorvault, foldaliasvendorlower) == 0);
writefile(strings.concat(foldaliasvendorleft, "/pkg.ww"), strings.concat(
"package left;\nimport _ lib.Foo;\n",
"export fn value() i32 = { return 1; };\n"));
writefile(strings.concat(foldaliasvendorright, "/pkg.ww"), strings.concat(
"package right;\nimport _ lib.foo;\n",
"export fn value() i32 = { return 2; };\n"));
writefile(strings.concat(foldaliasvendorclient, "/main.ww"), strings.concat(
"package main;\nimport _ domain.foldalias.left;\n",
"import _ domain.foldalias.right;\nfn main() i32 = { return 0; };\n"));
let compilerwrapper: str = strings.concat(tools, "/w6c.sh");
let assemblerwrapper: str = strings.concat(tools, "/w6a.sh");
@@ -8711,6 +9412,8 @@ fn runtimepath(relative: str) str = {
let referencealiasdirectdiag: str = "";
let referenceprefixdiag: str = "";
let candidate_diag_refs: []str = ["", ""];
let foldvendorrefs: []str = ["", "", ""];
let foldvendormultiref: str = "";
let supplementalrefs: []str = ["", "", "", "", "", "", "", "",
""];
let referencevendortestout: str = "";
@@ -8764,9 +9467,112 @@ fn runtimepath(relative: str) str = {
append(env, strings.concat("WW_VENDOR_REAL_LINKER=",
driver(linkers[si])));
let out: commandout;
let foldtargets: []str = [foldvendorclient, foldvendorreverseclient,
foldaliasvendorclient];
let foldlabels: []str = ["distinct", "reverse-imports", "one-dir"];
let foldwants: []str = [
"ww: case-insensitive import collision: \"domain.fold.vendor.lib.Foo\" and \"domain.fold.vendor.lib.foo\"\n",
"ww: case-insensitive import collision: \"domain.fold.vendor.lib.Foo\" and \"domain.fold.vendor.lib.foo\"\n",
"ww: case-insensitive import collision: \"domain.foldalias.vendor.lib.Foo\" and \"domain.foldalias.vendor.lib.foo\"\n",
];
let fi: i32 = 0;
for (fi < foldtargets.len) {
rewritefile(compilertraces[si], "");
rewritefile(assemblertraces[si], "");
rewritefile(linkertraces[si], "");
let foldwork: str = strings.concat(root, "/vendor-fold-",
foldlabels[fi], "-", stages[si], "-work");
let foldout: str = strings.concat(root, "/vendor-fold-",
foldlabels[fi], "-", stages[si]);
assert(os.mkdir(foldwork, 448i32) == 0);
let foldav: []str = [driver(stages[si]), "build", "-w",
foldwork, "-I", source, "-o", foldout, foldtargets[fi]];
runcommandenv(root, strings.concat("vendor-fold-", foldlabels[fi],
"-", stages[si]), foldav, env,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(out.stdout.len == 0 && same(out.stderr, foldwants[fi]));
assert(readfile(compilertraces[si]).len == 0);
assert(readfile(assemblertraces[si]).len == 0);
assert(readfile(linkertraces[si]).len == 0);
assert(!os.exists(foldout));
assert(!os.exists(strings.concat(foldout, ".new")));
assert(!os.exists(strings.concat(foldout, ".sepwork")));
assert(!os.exists(strings.concat(foldwork, "/.wwtool.stamp")));
assert(directoryisempty(foldwork));
if (si == 0) { foldvendorrefs[fi] = strings.dup(out.stderr); }
else { assert(same(foldvendorrefs[fi], out.stderr)); };
fi += 1;
};
assert(same(foldvendorrefs[0], foldvendorrefs[1]));
// A command-global multi-product request is one rejection transaction.
// Reversing its descriptors cannot publish the unrelated product or
// mutate either pre-existing status.
let foldmultiwork: str = strings.concat(root, "/vendor-fold-multi-",
stages[si], "-work");
assert(os.mkdir(foldmultiwork, 448i32) == 0);
let foldallowedout: str = strings.concat(root, "/vendor-fold-allowed-",
stages[si]);
let foldcollisionout: str = strings.concat(root,
"/vendor-fold-collision-", stages[si]);
let foldallowedstatus: str = strings.concat(foldallowedout, ".status");
let foldcollisionstatus: str = strings.concat(foldcollisionout, ".status");
writefile(foldallowedstatus, "sentinel-allowed\n");
writefile(foldcollisionstatus, "sentinel-collision\n");
let foldforward: []str = [driver(stages[si]), "test", "-c", "-w",
foldmultiwork, "-I", source, "--ww-package-build",
"--ww-package-test", "build", "main", "main", "-", "-", client,
foldallowedout, foldallowedstatus,
"--ww-package-test", "build", "main", "main", "-", "-",
foldvendorclient, foldcollisionout, foldcollisionstatus, client];
let foldreverse: []str = [driver(stages[si]), "test", "-c", "-w",
foldmultiwork, "-I", source, "--ww-package-build",
"--ww-package-test", "build", "main", "main", "-", "-",
foldvendorclient, foldcollisionout, foldcollisionstatus,
"--ww-package-test", "build", "main", "main", "-", "-", client,
foldallowedout, foldallowedstatus, client];
let foldrequests: [][]str = [foldforward, foldreverse];
let foldmultidiags: []str = ["", ""];
let foldorderlabels: []str = ["forward", "reverse"];
let foi: i32 = 0;
for (foi < foldrequests.len) {
rewritefile(compilertraces[si], "");
rewritefile(assemblertraces[si], "");
rewritefile(linkertraces[si], "");
runcommandenv(root, strings.concat("vendor-fold-multi-", stages[si],
"-", foldorderlabels[foi]), foldrequests[foi], env,
(180i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(out.stdout.len == 0 && same(out.stderr, foldwants[0]));
foldmultidiags[foi] = strings.dup(out.stderr);
assert(readfile(compilertraces[si]).len == 0);
assert(readfile(assemblertraces[si]).len == 0);
assert(readfile(linkertraces[si]).len == 0);
assert(same(readfile(foldallowedstatus), "sentinel-allowed\n"));
assert(same(readfile(foldcollisionstatus), "sentinel-collision\n"));
assert(!os.exists(strings.concat(foldallowedstatus, ".new")));
assert(!os.exists(strings.concat(foldcollisionstatus, ".new")));
assert(!os.exists(foldallowedout));
assert(!os.exists(strings.concat(foldallowedout, ".new")));
assert(!os.exists(strings.concat(foldallowedout, ".sepwork")));
assert(!os.exists(strings.concat(foldallowedout, ".result")));
assert(!os.exists(strings.concat(foldallowedout, ".result.new")));
assert(!os.exists(foldcollisionout));
assert(!os.exists(strings.concat(foldcollisionout, ".new")));
assert(!os.exists(strings.concat(foldcollisionout, ".sepwork")));
assert(!os.exists(strings.concat(foldcollisionout, ".result")));
assert(!os.exists(strings.concat(foldcollisionout, ".result.new")));
assert(directoryisempty(foldmultiwork));
foi += 1;
};
assert(same(foldmultidiags[0], foldmultidiags[1]));
if (si == 0) { foldvendormultiref = strings.dup(foldmultidiags[0]); }
else { assert(same(foldvendormultiref, foldmultidiags[0])); };
let av: []str = [driver(stages[si]), "build", "-w", works[si],
"-I", source, "-o", bins[si], client];
let out: commandout;
runcommandenv(root, strings.concat("vendor-cold-", stages[si]), av,
env, (120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);