diff --git a/cmd/ww/main.c b/cmd/ww/main.c index 16bb2a79..a78edc1c 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -7241,6 +7241,13 @@ basename_no_ext(const char *path, char *out, size_t outsz) if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; } +static int +source_operand_named(const char *path) +{ + size_t n = strlen(path); + return n >= 3 && strcmp(path + n - 3, ".ww") == 0; +} + /* Go's named-file package still applies matchFile's unconditional basename * exclusion before UseAllFiles, platform suffixes, source parsing, or test-file * classification. Keep this predicate on the public operand spelling: a @@ -7250,8 +7257,7 @@ static int source_operand_ignored(const char *path) { struct stat st; - size_t n = strlen(path); - if (n < 3 || strcmp(path + n - 3, ".ww") != 0) return 0; + if (!source_operand_named(path)) return 0; if (stat(path, &st) != 0 || S_ISDIR(st.st_mode)) return 0; const char *base = strrchr(path, '/'); base = base ? base + 1 : path; @@ -7342,7 +7348,7 @@ resolve_module(const char *name, const char *incs, char *out, size_t outsz, { struct stat st; if (stat(name, &st) == 0) { - if (S_ISREG(st.st_mode)) { + if (S_ISREG(st.st_mode) && source_operand_named(name)) { if (strlen(name) + 1 > outsz) return 0; memcpy(out, name, strlen(name) + 1); *is_dir = 0; @@ -7578,7 +7584,9 @@ do_build(int argc, char **argv) return exec_package_command(argc, argv, src, NULL, NULL, 0, 1); } struct stat requested; - int literal = stat(src, &requested) == 0; + int literal = stat(src, &requested) == 0 + && (S_ISDIR(requested.st_mode) + || source_operand_named(src)); int requested_nondirectory = literal && !S_ISDIR(requested.st_mode); if (source_operand_ignored(src)) { source_operand_no_sources(src); @@ -7705,10 +7713,12 @@ do_run(int argc, char **argv) if (next < 0) { free(incs); return 2; } if (src == NULL) src = "."; struct stat requested; - int literal = stat(src, &requested) == 0; + int literal = stat(src, &requested) == 0 + && (S_ISDIR(requested.st_mode) + || source_operand_named(src)); if (literal && S_ISDIR(requested.st_mode)) { size_t n = strlen(src); - if (n >= 3 && strcmp(src + n - 3, ".ww") == 0) { + if (source_operand_named(src)) { if (n >= 8 && strcmp(src + n - 8, "_test.ww") == 0) fprintf(stderr, "ww: cannot run *_test.ww files (%s)\n", src); @@ -8188,10 +8198,14 @@ do_test(int argc, char **argv) } if (pattern != NULL) { struct stat first; - if (stat(target, &first) != 0 || !S_ISREG(first.st_mode)) { + int first_found = stat(target, &first) == 0; + int first_logical = !first_found + || (!S_ISDIR(first.st_mode) && !source_operand_named(target)); + if (!first_found || !S_ISREG(first.st_mode) + || !source_operand_named(target)) { char first_resolved[PATH_MAX]; int first_is_dir = 0; - if (stat(target, &first) == 0 + if (!first_logical || !resolve_module(target, incs, first_resolved, sizeof first_resolved, &first_is_dir) || first_is_dir) { @@ -8229,7 +8243,8 @@ do_test(int argc, char **argv) return exec_package_command(argc, argv, src, NULL, NULL, 0, 0); } struct stat st; - if (stat(target, &st) != 0) { + if (stat(target, &st) != 0 + || (!S_ISDIR(st.st_mode) && !source_operand_named(target))) { /* not a literal path — try module resolution and run as * a single test program. */ char resolved[PATH_MAX]; @@ -8349,7 +8364,7 @@ do_test(int argc, char **argv) if (test_failed) fputs("FAIL\n", stdout); return rc; } - if (S_ISREG(st.st_mode)) { + if (S_ISREG(st.st_mode) && source_operand_named(target)) { if (nproducts != 0) { fprintf(stderr, "ww test: package-test variant needs one directory\n"); diff --git a/docs/build-system.md b/docs/build-system.md index ff187f7f..24ccbf35 100644 --- a/docs/build-system.md +++ b/docs/build-system.md @@ -10511,6 +10511,150 @@ 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. +### 11.54 Implemented wrong-suffix physical-source exclusion + +One public operand is a local named source only when its exact requested +spelling ends `.ww` and the command's existing file-kind rule admits it. An +existing non-directory object with any other suffix does not become source and +does not preempt the same operand's ordinary dotted lookup. Thus a physical +`foo.bar` is ignored as a source while request `foo.bar` continues to +`foo/bar.ww` or `foo/bar/`. An existing directory, including a symlink whose +target is a directory, remains a stat-first directory package regardless of +suffix. + +#### Pinned authority and applicability + +- **behavior directly implemented or asserted by pinned Go** — official Go + 1.26.5 commit `c19862e5f8415b4f24b189d065ed739517c548ba` enters named-file + mode in `cmd/go/internal/load/pkg.go:2887–2932`, especially 2903–2918, + only when a requested spelling ends `.go`, `Stat` succeeds, and the result + is not a directory. `GoFilesPackage` independently rejects every non-`.go` + member at `pkg.go:3244–3318` before constructing its synthetic package. + Build and test call that loader at + `cmd/go/internal/work/build.go:459–477` and + `cmd/go/internal/test/test.go:684–719`. Run independently consumes only + leading `.go` spellings at `cmd/go/internal/run/run.go:73–145`, especially + 96–123. +- **behavior directly implemented or asserted by pinned Go** — official + `cmd/go/testdata/script/list_test_non_go_files.txt:1–13` directly tests a + mixed named-file list: after a `.go` member selects named-file mode, + `GoFilesPackage` rejects the non-`.go` member. Official `run_hello.txt:1–10` + and `run_set_executable_name.txt:4–17` anchor ordinary named-file and package + run fronts. None directly tests one existing wrong-suffix object colliding + with a package request, and the official tree contains no such singular + build/run/test script. +- **behavior derived from the pinned implementation** — the singular + collision result follows from the pinned suffix-before-`Stat` build/test + gate and run's suffix-only scan. WW's honest local adaptation applies the + same positive spelling decision to `.ww` named sources before its existing + dotted search. It requires no module, manifest, registry, network lookup, + generalized import syntax, cache, database, CAS, lock, or source expression. + +#### Ownership, selection, and identity + +- **directly measured WW behavior** — before this change, both stages adopted + an existing physical `foo.bar` as a raw source. Build and run therefore used + its package, imports, main, and runtime status instead of `foo/bar.ww`; + raw, compile-only, and assembly-only test used its test descriptors and + retained its semantic/public bytes. Removing only `foo.bar` selected the + logical provider and changed all of those observations. +- **behavior derived from the pinned implementation** — the true shared + owners are `cmd/ww/main.c::resolve_module` and + `selfhost/cmd/ww/main.ww::resolvemodule`. Their direct non-directory adoption + now requires the exact `.ww` requested spelling. The mirrored spelling + predicate also owns build/run requested-literal bookkeeping and raw test's + second-positional classifier plus main stat/resolution branch. These command + fronts distinguish direct directory, eligible direct source, and logical + resolution without moving the rule into the compiler, enumerator, graph, + coordinator, producer, or runtime. +- **directly measured WW behavior** — a resolved logical single-file provider + retains the established command-line-file root family `__root.*`; selection + by a dotted request does not invent a dotted storage identity. A resolved + logical directory retains its dotted package/import/action family such as + `foo.bar.*`. The ignored physical pathname, object kind, containing + directory, bytes, mode, and timestamp create no package member, qualifier, + graph node or edge, action, symbol, `.wwi`, initializer, artifact, + publication destination, or persistence key. The logical requested spelling + remains canonical request identity where the existing directory route uses + it. +- **directly measured WW behavior** — requested suffix, not a symlink target's + basename, owns the positive gate. A wrong-suffix symlink to a regular or + non-directory special object is ignored as source; a wrong-suffix symlink to + a directory follows ordinary directory routing. A visible `.ww` symlink to a + regular source remains eligible. Each stage retains its prior visible `.ww` + special-file kind handling; this slice does not make FIFO/device loading a + shared new contract. + +#### Build, test, package, and import effects + +- **directly measured WW behavior** — with a logical provider, build produces + the same source set, import closure, initializer graph, producer calls, + runtime result, public output, and persistent artifacts whether the + wrong-suffix object is absent or present. Run executes that same provider. + Raw/running test, the historical second-positional test-name filter, + `test -c`, and `test -S` select the same logical test package, descriptors, + support closure, binary, and assembly. Cstage and WWstage outputs and every + comparable semantic artifact are byte-identical. +- **directly measured WW behavior** — the ignored object's package clause, + imports, malformed bytes, checker failures, abort/nonzero behavior, and + timestamps are not source input and cannot displace logical-provider + diagnostics. Package membership and import edges are exactly those of the + provider. A logical directory retains its dotted identity and a logical file + retains `__root`; physical collision state supplies neither. +- **directly measured WW behavior** — when no logical provider exists, a + collision matches the absent-physical control. An ordinary build or run + emits its existing `cannot find module` diagnostic; raw test emits its + existing `cannot find` diagnostic plus `FAIL` only when running; `-c` and + `-S` omit that marker. A second positional deliberately retains the historic + package-coordinator route and its exact canonicalization or usage result, + rather than being silently redefined as a direct cannot-find path. Existing + flag, output, tree, package-option, hidden-source, named `_test.ww`, and + `.ww` run-directory precedence remains unchanged. +- **behavior derived from the pinned implementation** — all four permanent + axes meet at this one source-eligibility decision. Build no longer constructs + or publishes the wrong action; test no longer constructs or runs the wrong + test package; package membership is not stolen by an ineligible physical + filename; and import binding/initialization comes only from the logical + provider. No axis receives a compatibility bypass or new identity model. + +#### Lifecycle, parity, formats, and scope + +- **directly measured WW behavior** — changing the collision among absent and + stat-successful non-directory states does not invalidate semantic actions or + alter semantic bytes or producer inputs. An unchanged warm command still + performs the established final link and success publication, producing the + same public bytes while its inode and mtime may change. Replacing the + collision with a directory, or retargeting a symlink to a directory, leaves + this case and follows ordinary stat-first directory behavior; no new atomic + snapshot promise is made for a concurrent kind change. +- **directly measured WW behavior** — logical producer failure preserves the + prior public and semantic generation. A retained running-test runtime failure + occurs after the complete logical build generation commits: deferred public + installation is skipped, so prior retained public bytes survive while the + newly built semantic generation remains committed and reusable. Restoring the + prior source requires a later successful rebuild and commit, not rollback of + the runtime-failing generation. Normal completion and controlled failures + remove request-owned scratch and transaction fragments. The spelling gate is + request-local and allocates no state before logical action or coordinator + start; concurrent requests use separate destinations and the existing + logical-action synchronization. +- **directly measured WW behavior** — external signal interruption after an + action starts is unchanged. In particular, the verified direct-driver fixed + `.new` leakage and later persistent-request poisoning remain open. This + source classifier neither prevents nor recovers that residue and makes no + signal-cleanup claim. +- **behavior derived from the pinned implementation** — the rule does not + complete the remaining suffix-first run front, multiple named sources, + finite `.ww` FIFO capture, shared test-process state/failure topology, + Go-compatible `-run` regular expressions, or `package documentation` + suppression. Existing `.ww` directory slices, hidden-source exclusion, + named `_test.ww` build omission, recursive/multiple-root coordination, + package syntax, and import syntax remain intact. + +Build workdir format remains `18`, test workdir format remains `19`, and +semantic storage format remains `3`. No schema, action descriptor, cache/result +record, manifest, transaction protocol, or lock changes. + ## 12. Candidate architectures and hard-gate decision Five candidates were developed as coherent systems, not as feature bins. diff --git a/docs/spec.md b/docs/spec.md index e46db098..4b5e3fb5 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -395,6 +395,52 @@ ImportPath = ident { "." ident } . persistence, process, or filesystem owner. Build and all test forms retain their distinct stat-first directory behavior. Build workdir format remains 18, test workdir format remains 19, and semantic storage format remains 3. +- A public operand is eligible for direct named-source adoption only when its + exact requested spelling ends `.ww` and that command's existing file-kind + rule admits it. An existing non-directory object with another suffix is not + source: its bytes, package clause, imports, syntax, test declarations, + runtime behavior, mode, and timestamp are not read as source, and ordinary + dotted resolution continues exactly as though the colliding object were + absent. Thus request `foo.bar` may resolve `foo/bar.ww` or `foo/bar/` even + while a physical non-directory `foo.bar` exists. A wrong-suffix symlink to a + non-directory is the same ignored collision; a symlink to a directory remains + an ordinary stat-first directory request. A visible `.ww` symlink to a + regular source remains eligible, and the established stage-specific handling + of visible `.ww` special files is not broadened by this rule. + + A resolved logical single file retains the established `__root` command-line + package/action/artifact identity. A resolved logical directory retains its + dotted package, import, graph, action, symbol, `.wwi`, initializer, artifact, + publication, and persistence identity. The ignored physical pathname/object + creates no membership, binding, edge, action, key, or alternate identity. + Build and run use only the logical provider's production/import/initializer + closure and runtime. Raw running test, its historical second-positional + test-name filter, `test -c`, and `test -S` use only the provider's test + package, descriptors, support closure, binary, and assembly. + + When no provider exists, collision-present behavior is byte-for-byte the + existing collision-absent behavior. An ordinary single target retains its + direct build/run/test cannot-find result and creates no producer action. A + second positional retains the established package-coordinator route, + diagnostics, status, selection lifecycle, and cleanup; this source gate does + not reinterpret that positional form. Mutation invariance covers only absent + and stat-successful non-directory collision states. A transition to a + directory leaves this rule and follows ordinary directory routing, with no + new atomic-snapshot guarantee for concurrent kind changes. + + Provider compilation failure preserves the prior public and semantic + generation. A retained running-test runtime failure occurs after the complete + logical build generation commits: it preserves prior retained public bytes by + skipping deferred installation, while that semantic generation remains + committed and reusable. Restoring prior source bytes requires a later + successful rebuild and commit, not runtime-failure rollback. Controlled- + failure cleanup otherwise remains unchanged. External signal + interruption after action start is unchanged, including the verified-open + fixed `.new` residue and later persistent-request poisoning. Multiple named + sources, remaining suffix-first run behavior, finite `.ww` FIFOs, test + process topology, and `-run` regular expressions are not completed here. + 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 package declares `package wire;`, the importing file sees its exported names as `wire.Name`; `codec.Name` is not an additional binding. An explicit alias diff --git a/docs/test-system-v2.md b/docs/test-system-v2.md index 84b2ebd0..9b015631 100644 --- a/docs/test-system-v2.md +++ b/docs/test-system-v2.md @@ -302,6 +302,53 @@ multiple named sources, finite FIFOs, and hidden regular sources are not claimed by this existing-directory slice. The general direct-driver `.new` interruption residue also remains open. +The raw source front also applies one positive requested-spelling gate before +physical adoption. An existing non-directory operand whose exact requested +spelling does not end `.ww` is not a raw source and cannot hijack its ordinary +dotted provider. Build, run, raw test, the historical second-positional raw +test filter, `test -c`, and `test -S` therefore resolve and consume the same +logical file or directory they consume when the collision is absent. Logical +files keep the existing `__root` action/artifact family; logical directories +keep dotted package/import/action identity. The ignored object's bytes, +package, imports, tests, runtime status, mode, and timestamp supply no package +membership, edge, producer input, artifact, publication, or persistence key. + +Requested suffix owns the gate through symlinks. Wrong-suffix links to regular +or special non-directories remain logical requests, while a link to a directory +retains stat-first direct-directory routing; a visible `.ww` link to a regular +source remains raw source. Each stage's existing visible `.ww` special-file +handling is outside this slice. Mutation/reuse invariance is limited to absent +or stat-successful non-directory collision states. A transition to a directory +uses the directory owner and receives no new concurrent-kind snapshot promise. + +No-provider rows deliberately separate the ordinary single-target path from +the second-positional compatibility path. The first retains direct cannot-find +diagnostics, running-test `FAIL`, and producer/artifact absence. The second +retains the existing package-coordinator process, canonicalization or usage +diagnostic, status, selection state, and cleanup; the gate does not turn that +route into a direct raw-file error. Logical producer failure preserves prior +public and semantic bytes. A retained running-test runtime failure instead +keeps the complete newly built semantic generation committed and reusable but +skips deferred installation, preserving prior retained public bytes; restoring +the old source requires a later successful rebuild and commit. Normal and +controlled-failure cleanup is unchanged. External driver signals after action +start retain the open fixed +`.new` leakage and later-request poisoning behavior. + +The focused `wrong_suffix_physical_files_do_not_hijack_dotted_requests` +package observer owns this boundary for both driver stages. It compares absent +controls with regular, symlinked-regular, and symlinked-special collisions; +checks build, run, raw/filter test, `-c`, and `-S` streams, statuses, runtime, +public bytes, complete comparable semantic artifacts, `__root` file identity, +and dotted directory identity; separates direct and coordinator no-provider +rows; exercises warm non-directory mutation, producer-failure rollback, +retained-runtime-failure public-byte preservation with committed semantic- +generation reuse and later successful restoration, request-local concurrent +builds, positive `.ww` and direct-directory controls, and normal residue +cleanup. It makes no finite-FIFO or signal-recovery claim. +The rule changes no package coordinator, test harness/process topology, +test-result caching, or build/test/semantic format (18/19/3). + List mode uses that same product process and initialization boundary but starts 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 diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index 3db700b8..90a0a66e 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -8660,13 +8660,17 @@ fn basenameoff(p: *u8, plen: u64) u64 = { return start; }; +fn sourceoperandnamed(path: *u8) bool = { + return cstrendswithlit(path, ".ww"); +}; + // A named Go source still passes through matchFile's leading-dot/underscore // exclusion even when UseAllFiles bypasses platform suffixes and build tags. // Inspect the public operand spelling so a hidden parent is not source identity // and a hidden symlink spelling cannot be resurrected by a non-directory // target's name or file kind. fn sourceoperandignored(path: *u8) bool = { - if (!cstrendswithlit(path, ".ww")) { return false; }; + if (!sourceoperandnamed(path)) { return false; }; let fi: os.filestat; match (os.stat(&fi, pathstr(path))) { case void => { @@ -8741,13 +8745,6 @@ fn buildsearchpath(selfdir: *u8, incs: *u8) *u8 = { fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = { let nlen: u64 = cstrlen(name); - if (cstrendswithlit(name, ".ww")) { - if (os.access(pathstr(name), 0i32) == 0) { - *isdir = 0; - return arenadupcstr(name, nlen); - }; - }; - let fi: os.filestat; let sr: (void | os.oserror) = os.stat(&fi, pathstr(name)); let found: bool = false; @@ -8760,10 +8757,16 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = { }; case let e: os.oserror => void; }; - if (found) { + if (foundisdir != 0) { *isdir = foundisdir; return arenadupcstr(name, nlen); }; + if (sourceoperandnamed(name)) { + if (os.access(pathstr(name), 0i32) == 0 || found) { + *isdir = 0; + return arenadupcstr(name, nlen); + }; + }; if (reservedimportpath(name)) { return nil; }; let search: *u8 = buildsearchpath(selfdir, incs); @@ -9043,9 +9046,11 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let requestedstat: os.filestat; match (os.stat(&requestedstat, pathstr(src))) { case void => { - requestedliteral = true; let typ: u32 = (requestedstat.mode: u32) & 61440u32; - requestednondirectory = typ != os.mode.DIR: u32; + requestedliteral = typ == os.mode.DIR: u32 + || sourceoperandnamed(src); + requestednondirectory = requestedliteral + && typ != os.mode.DIR: u32; }; case let e: os.oserror => void; }; @@ -9071,7 +9076,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let isdir: i32 = 0; let resolved: *u8 = nil; if (requestedliteral && !requestednondirectory - && cstrendswithlit(src, ".ww")) { + && sourceoperandnamed(src)) { isdir = 1; resolved = src; } else { @@ -9318,12 +9323,16 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let requestedliteral: bool = false; let requestedstat: os.filestat; match (os.stat(&requestedstat, pathstr(src))) { - case void => requestedliteral = true; + case void => { + let typ: u32 = (requestedstat.mode: u32) & 61440u32; + requestedliteral = typ == os.mode.DIR: u32 + || sourceoperandnamed(src); + }; case let e: os.oserror => void; }; if (requestedliteral && ((requestedstat.mode: u32) & 61440u32) == os.mode.DIR: u32 - && cstrendswithlit(src, ".ww")) { + && sourceoperandnamed(src)) { if (cstrendswithlit(src, "_test.ww")) { cerrpath("ww: cannot run *_test.ww files (", src, ")\n"); } else { @@ -9987,22 +9996,27 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let first: os.filestat; let firstregular: bool = false; let firstfound: bool = false; + let firstisdirphysical: bool = false; match (os.stat(&first, pathstr(target))) { case void => { firstfound = true; + firstisdirphysical = ((first.mode: u32) & 61440u32) + == os.mode.DIR: u32; firstregular = (((first.mode: u32) & 61440u32) - == (os.mode.REG: u32)); + == (os.mode.REG: u32)) && sourceoperandnamed(target); }; case let e: os.oserror => void; }; if (!firstregular) { let firstresolved: *u8 = nil; let firstisdir: i32 = 0; - if (!firstfound) { + let firstlogical: bool = !firstfound + || (!firstisdirphysical && !sourceoperandnamed(target)); + if (firstlogical) { firstresolved = resolvemodule(selfdir, target, incs.ptr, &firstisdir); }; - if (firstfound || firstresolved == nil || firstisdir != 0) { + if (!firstlogical || firstresolved == nil || firstisdir != 0) { let replacement: *u8 = nil; let identity: *u8 = requestidentity; if (firstisdir != 0) { @@ -10049,7 +10063,9 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { case void => { let t: u32 = (fi.mode: u32) & 61440u32; if (t == os.mode.DIR: u32) { isdir = 1; found = true; } - else { if (t == os.mode.REG: u32) { found = true; }; }; + else { if (t == os.mode.REG: u32 && sourceoperandnamed(target)) { + found = true; + }; }; }; case let e: os.oserror => void; }; diff --git a/test/package/package_test.ww b/test/package/package_test.ww index 5ff62011..769ab528 100644 --- a/test/package/package_test.ww +++ b/test/package/package_test.ww @@ -18,6 +18,1035 @@ type commandout = struct { stderr: str, }; +fn wrongsuffixrecord(out: *commandout, code: i32, records: *[]str) void = { + expectexit(out, code); + append(*records, strings.dup(out.stdout)); + append(*records, strings.dup(out.stderr)); +}; + +fn wrongsuffixsame(a: []str, b: []str) void = { + assert(a.len == b.len); + let i: i32 = 0; + for (i < a.len) { + assert(same(a[i], b[i])); + i += 1; + }; +}; + +fn wrongsuffixstageparity(a: []str, b: []str) void = { + assert(a.len == b.len); + let i: i32 = 0; + for (i < a.len) { + if (i != 3 && i != 10 && i != 13 && i != 17 && i != 20) { + assert(same(a[i], b[i])); + }; + i += 1; + }; +}; + +fn wrongsuffixartifactparity(a: str, b: str, names: []str) void = { + let i: i32 = 0; + for (i < names.len) { + assert(same(readfile(strings.concat(a, "/", names[i])), + readfile(strings.concat(b, "/", names[i])))); + i += 1; + }; +}; + +fn wrongsuffixprovenance(path: str) bool = { + return same(path, ".wwtool.ww") || same(path, ".wwtool.w6c") + || same(path, ".wwtool.w6a"); +}; + +// Producer paths are execution provenance rather than semantic action state; +// the stamp and every other relative entry remain part of parity. +fn wrongsuffixsemantictreesnapshot(root: str) str = { + let paths: []str = alloc([], 16u64)!; + append(paths, ""); + treepaths(root, "", &paths); + let i: i32 = 1; + for (i < paths.len) { + let j: i32 = i; + for (j > 0 && strings.compare(paths[j - 1], paths[j]) > 0) { + let swap: str = paths[j - 1]; + paths[j - 1] = paths[j]; + paths[j] = swap; + j -= 1; + }; + i += 1; + }; + let out: []u8 = alloc([], 4096u64)!; + i = 0; + for (i < paths.len) { + if (wrongsuffixprovenance(paths[i])) { i += 1; continue; }; + let full: str = root; + if (paths[i].len != 0) { + full = strings.concat(root, "/", paths[i]); + }; + let fi: os.filestat; + match (os.lstat(&fi, full)) { + case void => void; + case let e: os.oserror => abort("wrong suffix snapshot lstat failed"); + }; + snapshotbytes(&out, paths[i]); + snapshotu64(&out, fi.mode: u32: u64); + let kind: u32 = (fi.mode: u32) & 61440u32; + if (kind == os.mode.REG: u32) { + snapshotbytes(&out, readfile(full)); + } else { if (kind == os.mode.LINK: u32) { + let target: [4096]u8; + let got: i64 = os.readlink(full, &target[0], 4096u64); + assert(got >= 0i64 && got <= 4096i64); + let value: str; + value.ptr = &target[0]; + value.len = got: i32; + snapshotbytes(&out, value); + } else { + snapshotu64(&out, 0u64); + }; }; + i += 1; + }; + return strings.frombytes(out); +}; + +fn wrongsuffixsemanticparity(a: str, b: str) void = { + assert(same(wrongsuffixsemantictreesnapshot(a), + wrongsuffixsemantictreesnapshot(b))); +}; + +fn wrongsuffixfamilyabsent(work: str, action: str) void = { + let suffixes: []str = [".unit.ww", ".wwi", ".s", ".o", ".a", + ".init.unit.ww", ".init.s", ".init.o"]; + let i: i32 = 0; + for (i < suffixes.len) { + assert(!os.exists(strings.concat(work, "/", action, suffixes[i]))); + i += 1; + }; +}; + +fn wrongsuffixpathfragment(root: str, needle: str) bool = { + let paths: []str = alloc([], 16u64)!; + treepaths(root, "", &paths); + let i: i32 = 0; + for (i < paths.len) { + if (has(paths[i], needle)) { return true; }; + i += 1; + }; + return false; +}; + +fn wrongsuffixlogicalcapture(root: str, label: str, stage: str, + dir: str, buildsource: str, testsource: str, records: *[]str) void = { + let deadline: time.duration = + (120i64 * (time.second: i64)): time.duration; + let marker: str = "WRONG_SUFFIX_BYTES_MUST_NOT_APPEAR"; + let roots: []str = ["/__root.unit.ww", "/__root.wwi", "/__root.s", + "/__root.o", "/__root.a", "/__root.init.unit.ww", + "/__root.init.s", "/__root.init.o"]; + let out: commandout; + + let buildwork: str = strings.concat(root, "/", label, "-build-work"); + let buildout: str = strings.concat(root, "/", label, "-build-output"); + mkdirall(buildwork); + let buildav: []str = [driver(stage), "build", "-w", buildwork, + "-I", dir, "-o", buildout, "foo.bar"]; + runcommanddir(root, strings.concat(label, "-build"), dir, buildav, + deadline, &out); + wrongsuffixrecord(&out, 0, records); + assert(os.exists(buildout) && !has(out.stdout, marker) + && !has(out.stderr, marker)); + append(*records, strings.dup(readfile(buildout))); + let buildtree: str = strings.dup(artifacttreesnapshot(buildwork)); + append(*records, buildtree); + let i: i32 = 0; + for (i < roots.len) { + assert(os.exists(strings.concat(buildwork, roots[i]))); + i += 1; + }; + wrongsuffixfamilyabsent(buildwork, "foo.bar"); + assert(!directoryhasnew(buildwork) + && !directoryhasfragment(buildwork, ".wwtxn.") + && !directoryhasfragment(buildwork, ".install") + && !directoryhasfragment(buildwork, ".sepwork")); + + let builtav: []str = [buildout]; + runcommand(root, strings.concat(label, "-built"), builtav, + time.second, &out); + wrongsuffixrecord(&out, 23, records); + + let runav: []str = [driver(stage), "run", "-I", dir, "foo.bar"]; + runcommanddir(root, strings.concat(label, "-run"), dir, runav, + deadline, &out); + wrongsuffixrecord(&out, 23, records); + assert(!has(out.stdout, marker) && !has(out.stderr, marker)); + rewritefile(strings.concat(dir, "/foo/bar.ww"), testsource); + + let rawwork: str = strings.concat(root, "/", label, "-raw-work"); + mkdirall(rawwork); + let rawav: []str = [driver(stage), "test", "-w", rawwork, + "-I", dir, "foo.bar"]; + runcommanddir(root, strings.concat(label, "-raw"), dir, rawav, + deadline, &out); + wrongsuffixrecord(&out, 0, records); + assert(occurrences(out.stdout, "logical_selected ... ok\n") == 1 + && occurrences(out.stdout, "logical_second ... ok\n") == 1 + && !has(out.stdout, "wrong_physical_selected") + && !has(out.stdout, marker) && !has(out.stderr, marker)); + let rawtree: str = strings.dup(artifacttreesnapshot(rawwork)); + append(*records, rawtree); + wrongsuffixfamilyabsent(rawwork, "foo.bar"); + assert(os.exists(strings.concat(rawwork, "/__root.unit.ww")) + && os.exists(strings.concat(rawwork, "/__root.wwi")) + && os.exists(strings.concat(rawwork, "/__root.a")) + && !directoryhasnew(rawwork) + && !directoryhasfragment(rawwork, ".wwtxn.") + && !directoryhasfragment(rawwork, ".sepwork")); + + let filterwork: str = strings.concat(root, "/", label, "-filter-work"); + mkdirall(filterwork); + let filterav: []str = [driver(stage), "test", "-w", filterwork, + "-I", dir, "foo.bar", "logical_selected"]; + runcommanddir(root, strings.concat(label, "-filter"), dir, filterav, + deadline, &out); + wrongsuffixrecord(&out, 0, records); + assert(occurrences(out.stdout, "logical_selected ... ok\n") == 1 + && !has(out.stdout, "logical_second") + && !has(out.stdout, "wrong_physical_selected") + && !has(out.stdout, marker) && !has(out.stderr, marker)); + let filtertree: str = strings.dup(artifacttreesnapshot(filterwork)); + append(*records, filtertree); + wrongsuffixfamilyabsent(filterwork, "foo.bar"); + assert(os.exists(strings.concat(filterwork, "/__root.unit.ww")) + && !directoryhasnew(filterwork) + && !directoryhasfragment(filterwork, ".wwtxn.")); + + let compilework: str = strings.concat(root, "/", label, + "-compile-work"); + let compileout: str = strings.concat(root, "/", label, + "-compiled.test"); + mkdirall(compilework); + let compileav: []str = [driver(stage), "test", "-c", "-w", + compilework, "-I", dir, "-o", compileout, "foo.bar"]; + runcommanddir(root, strings.concat(label, "-compile"), dir, compileav, + deadline, &out); + wrongsuffixrecord(&out, 0, records); + assert(os.exists(compileout) && !has(out.stdout, marker) + && !has(out.stderr, marker)); + append(*records, strings.dup(readfile(compileout))); + let compiletree: str = strings.dup(artifacttreesnapshot(compilework)); + append(*records, compiletree); + wrongsuffixfamilyabsent(compilework, "foo.bar"); + assert(os.exists(strings.concat(compilework, "/__root.unit.ww")) + && !directoryhasnew(compilework) + && !directoryhasfragment(compilework, ".wwtxn.")); + + let asmwork: str = strings.concat(root, "/", label, "-asm-work"); + let asmout: str = strings.concat(root, "/", label, "-assembly"); + mkdirall(asmwork); + let asmav: []str = [driver(stage), "test", "-S", "-w", asmwork, + "-I", dir, "-o", asmout, "foo.bar"]; + runcommanddir(root, strings.concat(label, "-assembly"), dir, asmav, + deadline, &out); + wrongsuffixrecord(&out, 0, records); + let asmtree: str = strings.dup(artifacttreesnapshot(asmwork)); + append(*records, asmtree); + assert(!os.exists(asmout)); + append(*records, "assembly destination absent"); + wrongsuffixfamilyabsent(asmwork, "foo.bar"); + assert(os.exists(strings.concat(asmwork, "/__root.unit.ww")) + && os.exists(strings.concat(asmwork, "/__root.wwi")) + && os.exists(strings.concat(asmwork, "/__root.s")) + && has(readfile(strings.concat(asmwork, "/__root.unit.ww")), + "logical_selected") + && !has(readfile(strings.concat(asmwork, "/__root.unit.ww")), + "wrong_physical_selected") + && !directoryhasnew(asmwork) + && !directoryhasfragment(asmwork, ".wwtxn.") + && !directoryhasfragment(asmwork, ".sepwork")); + rewritefile(strings.concat(dir, "/foo/bar.ww"), buildsource); +}; + +// A physical non-directory can select a named source only through the public +// requested spelling. Once that spelling is ineligible, every command must +// observe the same logical request and action as if the object were absent. +@test fn wrong_suffix_physical_files_do_not_hijack_dotted_requests() void = { + let root: str = fresh(); + let filedir: str = strings.concat(root, "/file-request"); + let logicalparent: str = strings.concat(filedir, "/foo"); + let dependency: str = strings.concat(filedir, "/proof/marker"); + mkdirall(logicalparent); mkdirall(dependency); + let logicalfile: str = strings.concat(logicalparent, "/bar.ww"); + let logicalvalid: str = strings.concat( + "package main;\nimport proof.marker;\n", + "fn main() i32 = { return marker.value(); };\n"); + let logicaltestvalid: str = strings.concat( + "package main;\nimport proof.marker;\n", + "@test fn logical_selected() void = { ", + "assert(marker.value() == 23); };\n", + "@test fn logical_second() void = { ", + "assert(marker.value() != 99); };\n"); + let logicalinvalid: str = strings.concat( + "package main;\nimport proof.marker;\n", + "fn main() i32 = { return marker.missing(); };\n"); + let logicalruntimefail: str = strings.concat( + "package main;\nimport proof.marker;\n", + "@test fn logical_selected() void = { ", + "assert(marker.value() == 23); abort(); };\n", + "@test fn logical_second() void = { assert(true); };\n"); + let wrongbytes: str = strings.concat( + "WRONG_SUFFIX_BYTES_MUST_NOT_APPEAR\n", + "package main;\nimport never.selected;\n", + "fn main() i32 = { return 99; };\n", + "@test fn wrong_physical_selected() void = { abort(); };\n"); + writefile(logicalfile, logicalvalid); + writefile(strings.concat(dependency, "/main.ww"), + "package marker;\nexport fn value() i32 = { return 23; };\n"); + let collision: str = strings.concat(filedir, "/foo.bar"); + let wrongtarget: str = strings.concat(root, "/wrong-target"); + writefile(wrongtarget, wrongbytes); + let stages: []str = ["ww", "ww_ww"]; + let tags: []str = ["c", "ww"]; + let reference: []str = alloc([], 32u64)!; + let wwreference: []str = alloc([], 32u64)!; + let current: []str = alloc([], 32u64)!; + + wrongsuffixlogicalcapture(root, "file-absent-c", stages[0], filedir, + logicalvalid, logicaltestvalid, &reference); + wrongsuffixlogicalcapture(root, "file-absent-ww", stages[1], filedir, + logicalvalid, logicaltestvalid, ¤t); + let ci: i32 = 0; + for (ci < current.len) { + append(wwreference, strings.dup(current[ci])); ci += 1; + }; + wrongsuffixstageparity(reference, wwreference); + let buildartifacts: []str = ["__root.unit.ww", "__root.wwi", "__root.s", + "__root.o", "__root.a", "__root.init.unit.ww", "__root.init.s", + "__root.init.o", "proof.marker.unit.ww", "proof.marker.wwi", + "proof.marker.s", "proof.marker.o", "proof.marker.a"]; + let testartifacts: []str = ["__root.unit.ww", "__root.wwi", "__root.s", + "__root.o", "__root.a", "__root.init.unit.ww", "__root.init.s", + "__root.init.o", "proof.marker.unit.ww", "proof.marker.wwi", + "proof.marker.s", "proof.marker.o", "proof.marker.a", "test.unit.ww", + "test.wwi", "test.s", "test.o", "test.a"]; + let asmartifacts: []str = ["__root.unit.ww", "__root.wwi", "__root.s", + "__root.init.unit.ww", "__root.init.s", "proof.marker.unit.ww", + "proof.marker.wwi", "proof.marker.s", "test.unit.ww", "test.wwi", + "test.s"]; + wrongsuffixartifactparity(strings.concat(root, "/file-absent-c-build-work"), + strings.concat(root, "/file-absent-ww-build-work"), buildartifacts); + wrongsuffixartifactparity(strings.concat(root, "/file-absent-c-raw-work"), + strings.concat(root, "/file-absent-ww-raw-work"), testartifacts); + wrongsuffixartifactparity(strings.concat(root, "/file-absent-c-filter-work"), + strings.concat(root, "/file-absent-ww-filter-work"), testartifacts); + wrongsuffixartifactparity(strings.concat(root, "/file-absent-c-compile-work"), + strings.concat(root, "/file-absent-ww-compile-work"), testartifacts); + wrongsuffixartifactparity(strings.concat(root, "/file-absent-c-asm-work"), + strings.concat(root, "/file-absent-ww-asm-work"), asmartifacts); + let modes: []str = ["build", "raw", "filter", "compile", "asm"]; + let mi: i32 = 0; + for (mi < modes.len) { + wrongsuffixsemanticparity(strings.concat(root, "/file-absent-c-", + modes[mi], "-work"), strings.concat(root, "/file-absent-ww-", + modes[mi], "-work")); + mi += 1; + }; + let state: i32 = 0; + for (state < 3) { + if (state == 0) { writefile(collision, wrongbytes); } + else if (state == 1) { + assert(os.symlink(wrongtarget, collision) == 0); + } else { assert(os.symlink("/dev/null", collision) == 0); }; + let si: i32 = 0; + for (si < stages.len) { + let observed: []str = alloc([], 32u64)!; + let statetag: str = "special"; + if (state == 0) { statetag = "regular"; } + else if (state == 1) { statetag = "link"; }; + let label: str = strings.concat("file-state-", tags[si], "-", + statetag); + wrongsuffixlogicalcapture(root, label, stages[si], filedir, + logicalvalid, logicaltestvalid, &observed); + if (si == 0) { wrongsuffixsame(reference, observed); } + else { wrongsuffixsame(wwreference, observed); }; + si += 1; + }; + let statetag: str = "special"; + if (state == 0) { statetag = "regular"; } + else if (state == 1) { statetag = "link"; }; + mi = 0; + for (mi < modes.len) { + wrongsuffixsemanticparity(strings.concat(root, "/file-state-c-", + statetag, "-", modes[mi], "-work"), strings.concat(root, + "/file-state-ww-", statetag, "-", modes[mi], "-work")); + mi += 1; + }; + assert(os.remove(collision) == 0); + state += 1; + }; + + // A single persistent action is reusable while the colliding object moves + // only among absent and stat-successful non-directory states. + let warmrefs: []str = alloc([], 4u64)!; + let runtimepriorrefs: []str = alloc([], 2u64)!; + let runtimefailedrefs: []str = alloc([], 2u64)!; + let si: i32 = 0; + for (si < stages.len) { + let ctrace: str = strings.concat(root, "/warm-", tags[si], ".c.trace"); + let atrace: str = strings.concat(root, "/warm-", tags[si], ".a.trace"); + let ltrace: str = strings.concat(root, "/warm-", tags[si], ".l.trace"); + writefile(ctrace, ""); writefile(atrace, ""); writefile(ltrace, ""); + let cwrap: str = strings.concat(root, "/warm-", tags[si], "-w6c"); + let awrap: str = strings.concat(root, "/warm-", tags[si], "-w6a"); + let lwrap: str = strings.concat(root, "/warm-", tags[si], "-w6l"); + writeexecutable(cwrap, strings.concat("#!/bin/sh\n", + "printf '%s\\n' \"$*\" >> \"$WW_SUFFIX_CTRACE\"\n", + "exec \"$WW_SUFFIX_REAL_C\" \"$@\"\n")); + writeexecutable(awrap, strings.concat("#!/bin/sh\n", + "printf '%s\\n' \"$*\" >> \"$WW_SUFFIX_ATRACE\"\n", + "exec \"$WW_SUFFIX_REAL_A\" \"$@\"\n")); + writeexecutable(lwrap, strings.concat("#!/bin/sh\n", + "printf '%s\\n' \"$*\" >> \"$WW_SUFFIX_LTRACE\"\n", + "exec \"$WW_SUFFIX_REAL_L\" \"$@\"\n")); + let inherited: []str = os.getenvs(); + let env: []str = alloc([], (inherited.len + 9): u64)!; + let ei: i32 = 0; + for (ei < inherited.len) { + if (!strings.hasprefix(inherited[ei], "WW_W6C=") + && !strings.hasprefix(inherited[ei], "WW_W6A=") + && !strings.hasprefix(inherited[ei], "WW_W6L=") + && !strings.hasprefix(inherited[ei], "WW_SUFFIX_")) { + append(env, inherited[ei]); + }; + ei += 1; + }; + append(env, strings.concat("WW_W6C=", cwrap)); + append(env, strings.concat("WW_W6A=", awrap)); + append(env, strings.concat("WW_W6L=", lwrap)); + append(env, strings.concat("WW_SUFFIX_CTRACE=", ctrace)); + append(env, strings.concat("WW_SUFFIX_ATRACE=", atrace)); + append(env, strings.concat("WW_SUFFIX_LTRACE=", ltrace)); + let realc: str = "w6c_ww"; + let reala: str = "w6a_ww"; + let reall: str = "w6l_ww"; + if (si == 0) { realc = "w6c"; reala = "w6a"; reall = "w6l"; }; + append(env, strings.concat("WW_SUFFIX_REAL_C=", driver(realc))); + append(env, strings.concat("WW_SUFFIX_REAL_A=", driver(reala))); + append(env, strings.concat("WW_SUFFIX_REAL_L=", driver(reall))); + let work: str = strings.concat(root, "/warm-", tags[si], "-work"); + let output: str = strings.concat(root, "/warm-", tags[si], "-output"); + mkdirall(work); + let av: []str = [driver(stages[si]), "build", "-w", work, + "-I", filedir, "-o", output, "foo.bar"]; + let out: commandout; + runcommandenvdir(root, strings.concat("warm-", tags[si], "-seed"), + av, env, filedir, + (120i64 * (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); + let binref: str = strings.dup(readfile(output)); + let treeref: str = strings.dup(treesnapshot(work)); + if (si == 0) { + append(warmrefs, strings.dup(binref)); + append(warmrefs, strings.dup(artifacttreesnapshot(work))); + } else { + assert(same(warmrefs[0], binref)); + }; + rewritefile(ctrace, ""); rewritefile(atrace, ""); + rewritefile(ltrace, ""); + runcommandenvdir(root, strings.concat("warm-", tags[si], + "-absent-control"), av, env, filedir, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0 + && same(binref, readfile(output)) + && same(treeref, treesnapshot(work)) + && readfile(ctrace).len == 0 && readfile(atrace).len == 0 + && readfile(ltrace).len != 0); + let linkref: str = strings.dup(readfile(ltrace)); + let transition: i32 = 0; + for (transition < 7) { + if (transition == 0) { writefile(collision, wrongbytes); } + else if (transition == 1) { + let chmodav: []str = ["/bin/chmod", "000", collision]; + runcommand(root, strings.concat("warm-", tags[si], "-chmod-zero"), + chmodav, time.second, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0 + && permissionmode(collision) == 0u32); + } else if (transition == 2) { + let chmodav: []str = ["/bin/chmod", "600", collision]; + runcommand(root, strings.concat("warm-", tags[si], + "-chmod-restore"), chmodav, time.second, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0 + && permissionmode(collision) == 384u32); + rewritefile(collision, + "different malformed bytes { must stay unread\n"); + } else if (transition == 3) { + assert(os.remove(collision) == 0); + assert(os.symlink(wrongtarget, collision) == 0); + } else if (transition == 4) { + assert(os.remove(collision) == 0); + assert(os.symlink("/dev/null", collision) == 0); + } else if (transition == 5) { + assert(os.remove(collision) == 0); + } else { void; }; + rewritefile(ctrace, ""); rewritefile(atrace, ""); + rewritefile(ltrace, ""); + let transitiontag: str = "repeat"; + if (transition == 0) { transitiontag = "regular"; } + else if (transition == 1) { transitiontag = "chmod"; } + else if (transition == 2) { transitiontag = "mutated"; } + else if (transition == 3) { transitiontag = "link"; } + else if (transition == 4) { transitiontag = "special"; } + else if (transition == 5) { transitiontag = "absent"; }; + runcommandenvdir(root, strings.concat("warm-", tags[si], "-", + transitiontag), av, env, filedir, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0 + && same(binref, readfile(output)) + && same(treeref, treesnapshot(work)) + && readfile(ctrace).len == 0 && readfile(atrace).len == 0 + && same(linkref, readfile(ltrace)) + && !directoryhasnew(work) + && !directoryhasfragment(work, ".wwtxn.") + && !directoryhasfragment(work, ".install") + && !directoryhasfragment(work, ".sepwork")); + transition += 1; + }; + + // A reachable logical producer failure rolls back the generation; the + // ignored object's malformed content never reaches this path. + rewritefile(logicalfile, logicalinvalid); + rewritefile(ctrace, ""); rewritefile(atrace, ""); + rewritefile(ltrace, ""); + runcommandenvdir(root, strings.concat("warm-", tags[si], "-invalid"), + av, env, filedir, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(out.stdout.len == 0 && has(out.stderr, "missing") + && same(binref, readfile(output)) + && same(treeref, treesnapshot(work)) + && readfile(ctrace).len != 0 + && !os.exists(strings.concat(output, ".new")) + && !os.exists(strings.concat(output, ".sepwork")) + && !directoryhasnew(work) + && !directoryhasfragment(work, ".wwtxn.") + && !wrongsuffixpathfragment(work, ".new") + && !wrongsuffixpathfragment(work, ".wwtxn.") + && !wrongsuffixpathfragment(work, ".install") + && !wrongsuffixpathfragment(work, ".sepwork") + && !wrongsuffixpathfragment(work, ".capture") + && !wrongsuffixpathfragment(work, ".result") + && !wrongsuffixpathfragment(work, ".request")); + rewritefile(logicalfile, logicalvalid); + runcommandenvdir(root, strings.concat("warm-", tags[si], "-restored"), + av, env, filedir, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0 + && same(binref, readfile(output)) + && same(treeref, treesnapshot(work))); + + let testwork: str = strings.concat(root, "/runtime-", tags[si], + "-work"); + let retained: str = strings.concat(root, "/runtime-", tags[si], + ".test"); + mkdirall(testwork); + let testav: []str = [driver(stages[si]), "test", "-w", testwork, + "-I", filedir, "-o", retained, "foo.bar", "logical_selected"]; + rewritefile(logicalfile, logicaltestvalid); + runcommanddir(root, strings.concat("runtime-", tags[si], "-seed"), + filedir, testav, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stderr.len == 0 && os.exists(retained)); + let retainedref: str = strings.dup(readfile(retained)); + let runtimepriorfull: str = strings.dup(treesnapshot(testwork)); + let runtimepriorsemantic: str = strings.dup( + wrongsuffixsemantictreesnapshot(testwork)); + append(runtimepriorrefs, strings.dup(runtimepriorsemantic)); + rewritefile(logicalfile, logicalruntimefail); + runcommanddir(root, strings.concat("runtime-", tags[si], "-failed"), + filedir, testav, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + let runtimefailedsemantic: str = strings.dup( + wrongsuffixsemantictreesnapshot(testwork)); + append(runtimefailedrefs, strings.dup(runtimefailedsemantic)); + assert(strings.hassuffix(out.stdout, "\nFAIL\n") + && out.stderr.len == 0 + && same(retainedref, readfile(retained)) + && !same(runtimepriorsemantic, runtimefailedsemantic) + && os.exists(strings.concat(testwork, "/__root.unit.ww")) + && os.exists(strings.concat(testwork, "/__root.wwi")) + && os.exists(strings.concat(testwork, "/__root.s")) + && os.exists(strings.concat(testwork, "/__root.o")) + && os.exists(strings.concat(testwork, "/__root.a")) + && !os.exists(strings.concat(retained, ".new")) + && !os.exists(strings.concat(retained, ".sepwork")) + && !directoryhasnew(testwork) + && !wrongsuffixpathfragment(testwork, ".new") + && !wrongsuffixpathfragment(testwork, ".wwtxn.") + && !wrongsuffixpathfragment(testwork, ".install") + && !wrongsuffixpathfragment(testwork, ".sepwork") + && !wrongsuffixpathfragment(testwork, ".capture") + && !wrongsuffixpathfragment(testwork, ".result") + && !wrongsuffixpathfragment(testwork, ".request")); + wrongsuffixfamilyabsent(testwork, "foo.bar"); + rewritefile(logicalfile, logicaltestvalid); + runcommanddir(root, strings.concat("runtime-", tags[si], "-restored"), + filedir, testav, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(same(retainedref, readfile(retained)) + && same(runtimepriorfull, treesnapshot(testwork))); + rewritefile(logicalfile, logicalvalid); + si += 1; + }; + assert(runtimepriorrefs.len == 2 && runtimefailedrefs.len == 2 + && same(runtimepriorrefs[0], runtimepriorrefs[1]) + && same(runtimefailedrefs[0], runtimefailedrefs[1])); + wrongsuffixartifactparity(strings.concat(root, "/warm-c-work"), + strings.concat(root, "/warm-ww-work"), buildartifacts); + wrongsuffixsemanticparity(strings.concat(root, "/warm-c-work"), + strings.concat(root, "/warm-ww-work")); + + // A directory selected through the same dotted request retains dotted + // action identity; a physical collision cannot turn it into __root. + let dirdir: str = strings.concat(root, "/directory-request"); + let dirprovider: str = strings.concat(dirdir, "/foo/bar"); + let dirdep: str = strings.concat(dirdir, "/proof/marker"); + mkdirall(dirprovider); mkdirall(dirdep); + writefile(strings.concat(dirprovider, "/main.ww"), strings.concat( + "package main;\nimport proof.marker;\n", + "fn main() i32 = { return marker.value() + 8; };\n")); + writefile(strings.concat(dirdep, "/main.ww"), + "package marker;\nexport fn value() i32 = { return 23; };\n"); + let dircollision: str = strings.concat(dirdir, "/foo.bar"); + let dirrefs: []str = alloc([], 8u64)!; + state = 0; + for (state < 2) { + if (state == 1) { writefile(dircollision, wrongbytes); }; + si = 0; + for (si < stages.len) { + let statetag: str = "collision"; + if (state == 0) { statetag = "absent"; }; + let work: str = strings.concat(root, "/directory-", tags[si], "-", + statetag, "-work"); + let output: str = strings.concat(root, "/directory-", tags[si], "-", + statetag, "-output"); + mkdirall(work); + let av: []str = [driver(stages[si]), "build", "-w", work, + "-I", dirdir, "-o", output, "foo.bar"]; + let out: commandout; + runcommanddir(root, strings.concat("directory-build-", tags[si], + "-", statetag), dirdir, av, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0 + && os.exists(strings.concat(work, "/foo.bar.unit.ww")) + && os.exists(strings.concat(work, "/foo.bar.wwi")) + && os.exists(strings.concat(work, "/foo.bar.s")) + && os.exists(strings.concat(work, "/foo.bar.a")) + && os.exists(strings.concat(work, "/foo.bar.init.unit.ww")) + && !os.exists(strings.concat(work, "/__root.unit.ww"))); + wrongsuffixfamilyabsent(work, "__root"); + let bytes: str = strings.dup(readfile(output)); + let tree: str = strings.dup(artifacttreesnapshot(work)); + if (state == 0) { + append(dirrefs, bytes); append(dirrefs, tree); + if (si == 1) { assert(same(dirrefs[0], dirrefs[2])); }; + } else { + let off: i32 = si * 2; + assert(same(dirrefs[off], bytes) + && same(dirrefs[off + 1], tree)); + }; + let runav: []str = [output]; + runcommand(root, strings.concat("directory-built-", tags[si], + "-", statetag), runav, + time.second, &out); + expectexit(&out, 31); + assert(out.stdout.len == 0 && out.stderr.len == 0 + && !directoryhasnew(work) + && !directoryhasfragment(work, ".wwtxn.")); + si += 1; + }; + state += 1; + }; + let dirartifacts: []str = ["foo.bar.unit.ww", "foo.bar.wwi", + "foo.bar.s", "foo.bar.o", "foo.bar.a", "foo.bar.init.unit.ww", + "foo.bar.init.s", "foo.bar.init.o", "proof.marker.unit.ww", + "proof.marker.wwi", "proof.marker.s", "proof.marker.o", + "proof.marker.a"]; + wrongsuffixartifactparity(strings.concat(root, "/directory-c-absent-work"), + strings.concat(root, "/directory-ww-absent-work"), dirartifacts); + wrongsuffixartifactparity(strings.concat(root, "/directory-c-collision-work"), + strings.concat(root, "/directory-ww-collision-work"), dirartifacts); + wrongsuffixsemanticparity(strings.concat(root, "/directory-c-absent-work"), + strings.concat(root, "/directory-ww-absent-work")); + wrongsuffixsemanticparity(strings.concat(root, + "/directory-c-collision-work"), strings.concat(root, + "/directory-ww-collision-work")); + assert(os.remove(dircollision) == 0); + let directdir: str = strings.concat(root, "/direct-directory"); + mkdirall(directdir); + writefile(strings.concat(directdir, "/main.ww"), + "package main;\nfn main() i32 = { return 43; };\n"); + assert(os.symlink(directdir, dircollision) == 0); + si = 0; + for (si < stages.len) { + let av: []str = [driver(stages[si]), "run", "-I", dirdir, + "foo.bar"]; + let out: commandout; + runcommanddir(root, strings.concat("wrong-suffix-directory-link-", + tags[si]), dirdir, av, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 43); + assert(out.stdout.len == 0 && out.stderr.len == 0); + si += 1; + }; + assert(os.remove(dircollision) == 0); + + // Missing logical providers retain ordinary driver rejection, while a + // second positional retains the historical package-coordinator route. + let missingdir: str = strings.concat(root, "/missing-request"); + mkdirall(missingdir); + let missingcollision: str = strings.concat(missingdir, "/missing.pkg"); + let ordinaryrefs: []str = alloc([], 32u64)!; + let filteredrefs: []str = alloc([], 32u64)!; + let coordinatordiag: str = strings.concat( + "wwtest package: logical_selected: cannot canonicalize package directory\n", + "wwtest package: missing.pkg: cannot canonicalize package directory\n"); + let coordinatorusage: str = strings.concat( + "usage: wwtest package [-c] [-S] [-list] [-j N] [-I DIR] [-L DIR] [-l LIB] [-w DIR] [-run|-filter GLOB] [-timeout-ms=N] [DIR | DIR/... ...] [-- GLOB ...]\n", + " *_test.ww is the sole test-source form; @test elsewhere is rejected\n", + " -c retains without running; -o retains and still runs unless -c is present\n", + " -w DIR is one persistent semantic-action store shared by the selected packages\n"); + assert(coordinatorusage.len == 381); + let rejectenvs: [][]str = alloc([], 2u64)!; + let rejectctraces: []str = alloc([], 2u64)!; + let rejectatraces: []str = alloc([], 2u64)!; + let rejectltraces: []str = alloc([], 2u64)!; + si = 0; + for (si < stages.len) { + let ctrace: str = strings.concat(root, "/reject-", tags[si], ".c.trace"); + let atrace: str = strings.concat(root, "/reject-", tags[si], ".a.trace"); + let ltrace: str = strings.concat(root, "/reject-", tags[si], ".l.trace"); + writefile(ctrace, ""); writefile(atrace, ""); writefile(ltrace, ""); + append(rejectctraces, ctrace); append(rejectatraces, atrace); + append(rejectltraces, ltrace); + let cwrap: str = strings.concat(root, "/reject-", tags[si], "-w6c"); + let awrap: str = strings.concat(root, "/reject-", tags[si], "-w6a"); + let lwrap: str = strings.concat(root, "/reject-", tags[si], "-w6l"); + writeexecutable(cwrap, strings.concat("#!/bin/sh\n", + "printf invoked >> \"$WW_SUFFIX_REJECT_CTRACE\"\nexit 97\n")); + writeexecutable(awrap, strings.concat("#!/bin/sh\n", + "printf invoked >> \"$WW_SUFFIX_REJECT_ATRACE\"\nexit 97\n")); + writeexecutable(lwrap, strings.concat("#!/bin/sh\n", + "printf invoked >> \"$WW_SUFFIX_REJECT_LTRACE\"\nexit 97\n")); + let inherited: []str = os.getenvs(); + let rejectenv: []str = alloc([], (inherited.len + 6): u64)!; + let ei: i32 = 0; + for (ei < inherited.len) { + if (!strings.hasprefix(inherited[ei], "WW_W6C=") + && !strings.hasprefix(inherited[ei], "WW_W6A=") + && !strings.hasprefix(inherited[ei], "WW_W6L=") + && !strings.hasprefix(inherited[ei], "WW_SUFFIX_REJECT_")) { + append(rejectenv, inherited[ei]); + }; + ei += 1; + }; + append(rejectenv, strings.concat("WW_W6C=", cwrap)); + append(rejectenv, strings.concat("WW_W6A=", awrap)); + append(rejectenv, strings.concat("WW_W6L=", lwrap)); + append(rejectenv, strings.concat("WW_SUFFIX_REJECT_CTRACE=", ctrace)); + append(rejectenv, strings.concat("WW_SUFFIX_REJECT_ATRACE=", atrace)); + append(rejectenv, strings.concat("WW_SUFFIX_REJECT_LTRACE=", ltrace)); + append(rejectenvs, rejectenv); + si += 1; + }; + state = 0; + for (state < 2) { + if (state == 1) { writefile(missingcollision, wrongbytes); }; + si = 0; + for (si < stages.len) { + let ordinary: []str = alloc([], 24u64)!; + let filtered: []str = alloc([], 24u64)!; + let statetag: str = "collision-"; + if (state == 0) { statetag = "absent-"; }; + let prefix: str = strings.concat("missing-", tags[si], "-", + statetag); + let out: commandout; + let buildwork: str = strings.concat(root, "/", prefix, "build-work"); + let buildout: str = strings.concat(root, "/", prefix, "build-output"); + let buildav: []str = [driver(stages[si]), "build", "-w", + buildwork, "-o", buildout, "missing.pkg"]; + runcommanddir(root, strings.concat(prefix, "build"), missingdir, + buildav, (30i64 * (time.second: i64)): time.duration, &out); + wrongsuffixrecord(&out, 1, &ordinary); + assert(same(out.stderr, + "ww build: cannot find module missing.pkg\n") + && !os.exists(buildwork) && !os.exists(buildout)); + let runav: []str = [driver(stages[si]), "run", "missing.pkg"]; + runcommanddir(root, strings.concat(prefix, "run"), missingdir, + runav, (30i64 * (time.second: i64)): time.duration, &out); + wrongsuffixrecord(&out, 1, &ordinary); + assert(same(out.stderr, + "ww run: cannot find module missing.pkg\n")); + let rawwork: str = strings.concat(root, "/", prefix, "raw-work"); + let rawav: []str = [driver(stages[si]), "test", "-w", rawwork, + "missing.pkg"]; + runcommanddir(root, strings.concat(prefix, "raw"), missingdir, + rawav, (30i64 * (time.second: i64)): time.duration, &out); + wrongsuffixrecord(&out, 1, &ordinary); + assert(same(out.stdout, "FAIL\n") && same(out.stderr, + "ww test: cannot find missing.pkg\n") && !os.exists(rawwork)); + let compiled: str = strings.concat(root, "/", prefix, "compiled"); + let compileav: []str = [driver(stages[si]), "test", "-c", "-o", + compiled, "missing.pkg"]; + runcommanddir(root, strings.concat(prefix, "compile"), missingdir, + compileav, (30i64 * (time.second: i64)): time.duration, &out); + wrongsuffixrecord(&out, 1, &ordinary); + assert(out.stdout.len == 0 && same(out.stderr, + "ww test: cannot find missing.pkg\n") && !os.exists(compiled)); + let assembly: str = strings.concat(root, "/", prefix, "assembly"); + let asmav: []str = [driver(stages[si]), "test", "-S", "-o", + assembly, "missing.pkg"]; + runcommanddir(root, strings.concat(prefix, "assembly"), missingdir, + asmav, (30i64 * (time.second: i64)): time.duration, &out); + wrongsuffixrecord(&out, 1, &ordinary); + assert(out.stdout.len == 0 && same(out.stderr, + "ww test: cannot find missing.pkg\n") && !os.exists(assembly)); + + rewritefile(rejectctraces[si], ""); + rewritefile(rejectatraces[si], ""); + rewritefile(rejectltraces[si], ""); + let frawav: []str = [driver(stages[si]), "test", "missing.pkg", + "logical_selected"]; + runcommandenvdir(root, strings.concat(prefix, "filtered-raw"), + frawav, rejectenvs[si], missingdir, + (30i64 * (time.second: i64)): time.duration, &out); + wrongsuffixrecord(&out, 1, &filtered); + assert(same(out.stdout, "FAIL\n") + && same(out.stderr, coordinatordiag)); + let fcompiled: str = strings.concat(root, "/", prefix, + "filtered-compiled"); + let fcompileav: []str = [driver(stages[si]), "test", "-c", "-o", + fcompiled, "missing.pkg", "logical_selected"]; + runcommandenvdir(root, strings.concat(prefix, "filtered-compile"), + fcompileav, rejectenvs[si], missingdir, + (30i64 * (time.second: i64)): time.duration, &out); + wrongsuffixrecord(&out, 1, &filtered); + assert(out.stdout.len == 0 && same(out.stderr, coordinatordiag) + && !os.exists(fcompiled)); + let fnooutav: []str = [driver(stages[si]), "test", "-S", + "missing.pkg", "logical_selected"]; + runcommandenvdir(root, strings.concat(prefix, "filtered-assembly-noout"), + fnooutav, rejectenvs[si], missingdir, + (30i64 * (time.second: i64)): time.duration, &out); + wrongsuffixrecord(&out, 2, &filtered); + assert(out.stdout.len == 0 + && same(out.stderr, "ww test: -S needs -o\n")); + let fassembly: str = strings.concat(root, "/", prefix, + "filtered-assembly"); + let fasmav: []str = [driver(stages[si]), "test", "-S", "-o", + fassembly, "missing.pkg", "logical_selected"]; + runcommandenvdir(root, strings.concat(prefix, "filtered-assembly"), + fasmav, rejectenvs[si], missingdir, + (30i64 * (time.second: i64)): time.duration, &out); + wrongsuffixrecord(&out, 2, &filtered); + assert(out.stdout.len == 0 && out.stderr.len == 381 + && same(out.stderr, coordinatorusage) + && !os.exists(fassembly) + && readfile(rejectctraces[si]).len == 0 + && readfile(rejectatraces[si]).len == 0 + && readfile(rejectltraces[si]).len == 0); + if (state == 0 && si == 0) { + let oi: i32 = 0; + for (oi < ordinary.len) { + append(ordinaryrefs, strings.dup(ordinary[oi])); oi += 1; + }; + let fi: i32 = 0; + for (fi < filtered.len) { + append(filteredrefs, strings.dup(filtered[fi])); fi += 1; + }; + } else { + wrongsuffixsame(ordinaryrefs, ordinary); + wrongsuffixsame(filteredrefs, filtered); + }; + si += 1; + }; + state += 1; + }; + assert(os.remove(missingcollision) == 0); + + // Eligible visible .ww spellings stay named sources, including a symlink + // to a regular source. A .ww directory keeps its established rejection at + // run's named-source front while remaining an ordinary build directory. + let controls: str = strings.concat(root, "/positive-controls"); + mkdirall(controls); + let visible: str = strings.concat(controls, "/visible.ww"); + let visiblelink: str = strings.concat(controls, "/visible-link.ww"); + writefile(visible, "package main;\nfn main() i32 = { return 53; };\n"); + assert(os.symlink(visible, visiblelink) == 0); + let positivebins: []str = alloc([], 2u64)!; + let positivetrees: []str = alloc([], 2u64)!; + let routes: []str = [visible, visiblelink]; + let ri: i32 = 0; + for (ri < routes.len) { + si = 0; + for (si < stages.len) { + let routetag: str = "link"; + if (ri == 0) { routetag = "regular"; }; + let work: str = strings.concat(root, "/positive-", tags[si], "-", + routetag, "-work"); + let output: str = strings.concat(root, "/positive-", tags[si], "-", + routetag, "-output"); + mkdirall(work); + let av: []str = [driver(stages[si]), "build", "-w", work, + "-o", output, routes[ri]]; + let out: commandout; + runcommand(root, strings.concat("positive-build-", tags[si], "-", + routetag), av, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0); + if (ri == 0) { + append(positivebins, strings.dup(readfile(output))); + append(positivetrees, + strings.dup(artifacttreesnapshot(work))); + if (si == 1) { + assert(same(positivebins[0], positivebins[1])); + }; + } else { + assert(same(positivebins[si], readfile(output)) + && same(positivetrees[si], artifacttreesnapshot(work))); + }; + let runav: []str = [output]; + runcommand(root, strings.concat("positive-built-", tags[si], "-", + routetag), runav, time.second, &out); + expectexit(&out, 53); + si += 1; + }; + ri += 1; + }; + let positiveartifacts: []str = ["__root.unit.ww", "__root.wwi", + "__root.s", "__root.o", "__root.a", "__root.init.unit.ww", + "__root.init.s", "__root.init.o"]; + wrongsuffixartifactparity(strings.concat(root, "/positive-c-regular-work"), + strings.concat(root, "/positive-ww-regular-work"), positiveartifacts); + wrongsuffixartifactparity(strings.concat(root, "/positive-c-link-work"), + strings.concat(root, "/positive-ww-link-work"), positiveartifacts); + wrongsuffixsemanticparity(strings.concat(root, "/positive-c-regular-work"), + strings.concat(root, "/positive-ww-regular-work")); + wrongsuffixsemanticparity(strings.concat(root, "/positive-c-link-work"), + strings.concat(root, "/positive-ww-link-work")); + let nameddir: str = strings.concat(controls, "/directory.ww"); + mkdirall(nameddir); + writefile(strings.concat(nameddir, "/main.ww"), + "package main;\nfn main() i32 = { return 55; };\n"); + let nameddirbin: str = ""; + si = 0; + for (si < stages.len) { + let work: str = strings.concat(root, "/named-directory-", tags[si], + "-work"); + let output: str = strings.concat(root, "/named-directory-", tags[si], + "-output"); + mkdirall(work); + let av: []str = [driver(stages[si]), "build", "-w", work, + "-o", output, nameddir]; + let out: commandout; + runcommand(root, strings.concat("named-directory-build-", tags[si]), av, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + if (si == 0) { nameddirbin = strings.dup(readfile(output)); } + else { assert(same(nameddirbin, readfile(output))); }; + let runav: []str = [driver(stages[si]), "run", nameddir]; + runcommand(root, strings.concat("named-directory-run-", tags[si]), + runav, (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(out.stdout.len == 0 && same(out.stderr, strings.concat(nameddir, + " is a directory, should be a WW file\n"))); + si += 1; + }; + wrongsuffixsemanticparity(strings.concat(root, "/named-directory-c-work"), + strings.concat(root, "/named-directory-ww-work")); + + // The suffix decision is request-local: concurrent stages can build the + // same logical action through one ignored collision into separate owners. + writefile(collision, wrongbytes); + let pcwork: str = strings.concat(root, "/parallel-c-work"); + let pwwork: str = strings.concat(root, "/parallel-ww-work"); + let pcout: str = strings.concat(root, "/parallel-c-output"); + let pwout: str = strings.concat(root, "/parallel-ww-output"); + mkdirall(pcwork); mkdirall(pwwork); + let pcav: []str = [driver("ww"), "build", "-w", pcwork, + "-I", filedir, "-o", pcout, "foo.bar"]; + let pwav: []str = [driver("ww_ww"), "build", "-w", pwwork, + "-I", filedir, "-o", pwout, "foo.bar"]; + let cc: exec.command; + cc.path = pcav[0]; cc.argv = pcav; cc.env = os.getenvs(); cc.dir = filedir; + cc.stdoutpath = strings.concat(root, "/parallel-c.stdout"); + cc.stderrpath = strings.concat(root, "/parallel-c.stderr"); + cc.deadline = time.add(time.now(time.clock.monotonic), + (120i64 * (time.second: i64)): time.duration); + cc.grace = (100i64 * (time.millisecond: i64)): time.duration; + let wc: exec.command; + wc.path = pwav[0]; wc.argv = pwav; wc.env = os.getenvs(); wc.dir = filedir; + wc.stdoutpath = strings.concat(root, "/parallel-ww.stdout"); + wc.stderrpath = strings.concat(root, "/parallel-ww.stderr"); + wc.deadline = time.add(time.now(time.clock.monotonic), + (120i64 * (time.second: i64)): time.duration); + wc.grace = (100i64 * (time.millisecond: i64)): time.duration; + let cp: exec.process; + let wp: exec.process; + exec.start(&cp, &cc); exec.start(&wp, &wc); + let cdone: bool = false; + let wdone: bool = false; + for (!cdone || !wdone) { + if (!cdone) { cdone = exec.poll(&cp); }; + if (!wdone) { wdone = exec.poll(&wp); }; + if (!cdone || !wdone) { + time.sleep(time.millisecond, time.clock.monotonic); + }; + }; + assert(cp.result.errno == 0 && cp.result.cleanuperrno == 0 + && cp.result.termination == exec.termination.EXIT && cp.result.code == 0 + && wp.result.errno == 0 && wp.result.cleanuperrno == 0 + && wp.result.termination == exec.termination.EXIT && wp.result.code == 0 + && readfile(cc.stdoutpath).len == 0 && readfile(cc.stderrpath).len == 0 + && readfile(wc.stdoutpath).len == 0 && readfile(wc.stderrpath).len == 0 + && same(reference[2], readfile(pcout)) + && same(reference[2], readfile(pwout)) + && same(reference[3], artifacttreesnapshot(pcwork)) + && same(wwreference[3], artifacttreesnapshot(pwwork)) + && !directoryhasnew(pcwork) && !directoryhasnew(pwwork) + && !directoryhasfragment(pcwork, ".wwtxn.") + && !directoryhasfragment(pwwork, ".wwtxn.")); + wrongsuffixsemanticparity(pcwork, pwwork); + assert(os.remove(collision) == 0); + + assert(!directoryhasnew(root) + && !directoryhasfragment(root, ".wwtxn.") + && !directoryhasfragment(root, ".install") + && !directoryhasfragment(root, ".sepwork") + && !directoryhasfragment(root, ".capture") + && !directoryhasfragment(root, ".result") + && !directoryhasfragment(root, ".request") + && !wrongsuffixpathfragment(root, ".new") + && !wrongsuffixpathfragment(root, ".wwtxn.") + && !wrongsuffixpathfragment(root, ".install") + && !wrongsuffixpathfragment(root, ".sepwork") + && !wrongsuffixpathfragment(root, ".capture") + && !wrongsuffixpathfragment(root, ".result") + && !wrongsuffixpathfragment(root, ".request")); + clean(root); +}; + fn envrequired(name: str) str = { match (os.getenv(name)) { case let value: str => {