From 3922f1c34e625cc4fd12fed94ddaa142f7d2cd74 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 22 Aug 2026 19:28:54 +0900 Subject: [PATCH] fix: ignore hidden named source operands --- cmd/ww/main.c | 60 ++++++ docs/build-system.md | 125 +++++++++++++ docs/spec.md | 16 ++ docs/test-system-v2.md | 20 ++ selfhost/cmd/ww/main.ww | 58 ++++++ test/package/package_test.ww | 348 +++++++++++++++++++++++++++++++++++ 6 files changed, 627 insertions(+) diff --git a/cmd/ww/main.c b/cmd/ww/main.c index f7056faf..6c46e01f 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -7136,6 +7136,38 @@ basename_no_ext(const char *path, char *out, size_t outsz) if (dot && strcmp(dot, ".ww") == 0) *dot = '\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 + * hidden parent does not hide a visible named file, while a hidden symlink + * spelling remains hidden regardless of its non-directory target kind. */ +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 (stat(path, &st) != 0 || S_ISDIR(st.st_mode)) return 0; + const char *base = strrchr(path, '/'); + base = base ? base + 1 : path; + return base[0] == '.' || base[0] == '_'; +} + +static void +source_operand_no_sources(const char *path) +{ + const char *slash = strrchr(path, '/'); + fputs("ww: ", stderr); + if (slash == NULL) { + fputc('.', stderr); + } else if (slash == path) { + fputc('/', stderr); + } else { + (void)fwrite(path, 1, (size_t)(slash - path), stderr); + } + fputs(": directory contains no WW package sources\n", stderr); +} + /* Go's build -o directory branch follows an existing destination through * stat, and a trailing platform separator declares a directory which the * request may need to create. WW's platform separator is '/'. */ @@ -7442,6 +7474,11 @@ do_build(int argc, char **argv) } struct stat requested; int literal = stat(src, &requested) == 0; + if (source_operand_ignored(src)) { + source_operand_no_sources(src); + free(incs); + return 1; + } char resolved[PATH_MAX]; int is_dir = 0; if (!resolve_module(src, incs, resolved, sizeof resolved, &is_dir)) { @@ -7988,6 +8025,29 @@ do_test(int argc, char **argv) } } int discard_output = strcmp(outstem, "/dev/null") == 0; + if (strstr(target, "...") == NULL && source_operand_ignored(target)) { + if (nproducts != 0 && outstem[0]) { + fprintf(stderr, + "ww test: package-test products reject -o\n"); + return 2; + } + if (nproducts != 0) { + fprintf(stderr, + "ww test: package-test variant needs one directory\n"); + return 2; + } + if (packageopts) { + fprintf(stderr, + "ww test: package options need a directory\n"); + return 2; + } + source_operand_no_sources(target); + if (src != NULL && !compileonly && !emit_asm) + fputs("FAIL\n", stdout); + free(products); + free(incs); + return 1; + } if (pattern != NULL) { struct stat first; if (stat(target, &first) != 0 || !S_ISREG(first.st_mode)) { diff --git a/docs/build-system.md b/docs/build-system.md index cd58d49b..fd1156fb 100644 --- a/docs/build-system.md +++ b/docs/build-system.md @@ -9955,6 +9955,131 @@ artifact-layout, harness-protocol, cache, database, or publication field. Build workdir format remains `18`, test workdir format remains `19`, semantic storage format remains `3`, and no test-result cache is introduced. +### 11.50 Implemented named-source leading-dot/underscore eligibility + +An explicitly named raw `.ww` source now observes the same unconditional +basename exclusion as a named `.go` source in Go 1.26.5. This closes the gap in +WW's existing single-source command route: a final requested basename beginning +`.` or `_` is not a package source, even though named sources otherwise bypass +directory-only target-suffix selection. Directory selection already enforced +this rule and remains unchanged. + +#### Pinned Go evidence and applicability + +The reference is official Go 1.26.5 at commit +`c19862e5f8415b4f24b189d065ed739517c548ba`: + +- `PackagesAndErrors` recognizes an existing `.go` operand and routes the + complete named list to `GoFilesPackage` + ([`cmd/go/internal/load/pkg.go`, lines 2903–2918](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L2903-L2918)). + `GoFilesPackage` enables `UseAllFiles`, presents only the named `FileInfo` + entries through a synthetic directory, and loads one command-line package + ([lines 3244–3315](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L3244-L3315)). +- `go/build.Import` passes each synthetic entry through `Context.matchFile` + ([`go/build/build.go`, lines 886–914](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L886-L914)). + `matchFile` rejects a name beginning `_` or `.` before extension, target + suffix, open, imports, or build constraints; that branch precedes the + `UseAllFiles` condition + ([lines 1438–1509](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L1438-L1509)). + A package with no remaining source category returns `NoGoError` + ([lines 1076–1082](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L1076-L1082)); + the command loader presents the case as `no Go files` + ([`cmd/go/internal/load/pkg.go`, lines 250–270](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L250-L270)). +- Build reports load errors before installation/action construction + ([`cmd/go/internal/work/build.go`, lines 697–728](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build.go#L697-L728)). + Test uses the same loader + ([`cmd/go/internal/test/test.go`, lines 703–719](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L703-L719)) + and turns its error into package setup failure before constructing a runnable + ([lines 1015–1050](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1015-L1050)). +- Official command testdata explicitly records that `_cgo_yy.go` named on the + command line is ignored and permits the exact `no Go files` result + ([`cgo_bad_directives.txt`, lines 11–23](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/cgo_bad_directives.txt#L11-L23)). + `import_ignore.txt` independently proves a dot file contributes no import + ([lines 1–11](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/import_ignore.txt#L1-L11)). + +The rule honestly applies to WW's local literal `.ww` operand. It adds no +module, manifest, registry, network, generalized import, or build-expression +surface. This section is deliberately bounded to the existing single raw +source target. Multiple named sources as one package and visible +`*_test.ww` exclusion from `ww build` remain separate open semantics; when a +future source-set loader admits multiple names, it must apply this same rule to +each requested basename. + +#### Source, package, and import ownership + +The twin public drivers own one allocation-free operand predicate after +existing CLI-shape checks and a successful requested-path `Stat`, but before +logical resolution or graph entry. It requires an original spelling ending +exactly `.ww`, then examines only the final requested basename. A hidden parent +containing visible `main.ww` does not +exclude the named source. A requested hidden symlink spelling is excluded even +when its target is visible or non-regular, while a visible requested spelling +remains eligible even when its target basename is hidden. This follows the +`FileInfo.Name` seen +by Go's synthetic named-file directory: Unix `Stat` follows the target but +fills `Name` from the requested path +([`os/stat_unix.go`, lines 28–38](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/os/stat_unix.go#L28-L38), +[`os/stat_linux.go`, lines 13–30](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/os/stat_linux.go#L13-L30)). +Physical target paths do not become identity. + +Prefix exclusion precedes every later classifier. `_main_test.ww` and +`.main_test.ww` are absent before production/test partitioning. A hidden +`_main_windows.ww` is absent, but a visible `main_windows.ww` named directly +remains eligible because the named-file analogue uses `UseAllFiles`; directory +platform filtering is unchanged. Logical operands without a `.ww` suffix, +directory and recursive requests, imports, and `ww run` retain their existing +routes. + +An excluded operand creates no package clause, declaration, production/test +variant, or top-level state. Its bytes are not decoded or parsed: malformed +UTF-8, NUL, BOM, missing/invalid package clauses, late imports, missing imports, +unused imports, cycles, `internal`/vendor rules, and checker diagnostics cannot +resurrect it or outrank selection. It contributes no qualifier, import-use +obligation, edge, canonical dotted identity, initializer, or link closure. The +lexical parent printed by the diagnostic is presentation metadata only and is +never package, import, graph, action, symbol, `.wwi`, artifact, publication, or +persistence identity. + +#### Observable command and lifecycle behavior + +`ww build HIDDEN.ww` exits 1 with empty standard output and +`ww: PARENT: directory contains no WW package sources` on standard error. +An ordinary explicit running `ww test HIDDEN.ww` adds its established exact +command-owned `FAIL` line on standard output; compile-only and assembly-only +test requests do not. With no slash, `PARENT` is `.`, a root child uses `/`, +and otherwise it is the requested lexical bytes before the final slash. CLI +flag/path-length errors and the existing raw-test directory-only package-option +shape keep their precedence. A failed requested-path `Stat` retains ordinary +target/logical resolution; for an existing non-directory raw operand, +no-source selection precedes logical resolution, output-destination preflight, +and all source, producer, publication, and runtime diagnostics. Exact +`/dev/null` does not suppress the load failure. + +No root/dependency action, test support, generated main, compiler, assembler, +archiver, linker, harness, test child, or program process starts. Cold rejection +creates no default or explicit output, workdir, adjacent `.sepwork`, unit, +`.wwi`, assembly, object, archive, init artifact, binary, status, stage, capture, +result, transaction, or private temporary directory. Warm rejection starts no +transaction and preserves every prior work-artifact and public/retained byte. +It leaves no `.new`, `.install`, `.wwtxn.*`, or recovery residue. Producer, +runtime, publication-only, cleanup-only, signal, timeout, and descendant +semantics for visible inputs are unchanged. + +The predicate is request-local and creates no shared state, process group, +lock, or interruption owner, so overlapping hidden and visible requests remain +isolated. Cstage and WWstage must agree byte-for-byte on status, stdout, stderr, +and complete artifact absence; representative visible named sources retain +binary and semantic-artifact identity. The focused native +`named_source_prefixes_are_ignored` observer owns both prefixes, build/test and +compile-only paths, prefix-before-test/platform precedence, requested symlink +spelling, hidden-parent/visible-basename and wrong-platform controls, unread +malformed/import bytes, cold cleanup, warm preservation/restoration, residue, +and stage parity. + +No serialized format changes. Build workdir format remains `18`, test workdir +format remains `19`, semantic storage format remains `3`, and there is no test +result cache. + ## 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 cdd5de7b..7cb7b7ec 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -319,6 +319,22 @@ ImportPath = ident { "." ident } . 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. +- A single existing raw `.ww` operand is also subject to the unconditional + leading-name rule: if its final requested basename begins `.` or `_`, it is + ignored before the source is opened. Named raw sources otherwise retain their + existing all-files behavior, so a visible wrong-platform suffix remains + eligible. Only the requested basename participates: a hidden parent does not + hide visible `main.ww`, a hidden symlink spelling stays hidden for any + existing non-directory target, and a visible symlink spelling stays eligible. + The excluded operand creates no package, declaration, import binding/edge, + action, artifact, initialization, test, + publication, or persistent state. `ww build` reports the existing + `directory contains no WW package sources` condition; an explicit running + raw `ww test` also emits its command-owned `FAIL`, while `-c` and `-S` do not. + This rule does not add multiple named-source package support, does not change + visible `*_test.ww` handling, and does not apply to logical operands, + directory/recursive requests, imports, or `ww run`. The diagnostic parent is + presentation metadata and never canonical identity. - `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 91051c6d..17b59a11 100644 --- a/docs/test-system-v2.md +++ b/docs/test-system-v2.md @@ -219,6 +219,26 @@ every `-j` level, and `-j 1` — the default — matches the former sequential run loop exactly. Measured on the 31-package `lib/...` walk: 7.0s sequential, 2.4s at `-j 4`. +The raw single-source compatibility route applies package-source eligibility +before it invokes the compiler or harness. An existing operand ending `.ww` +whose final requested basename begins `.` or `_` is ignored exactly as it is by +directory discovery, even when the name also ends `_test.ww`; the file is not +opened and cannot contribute a test descriptor, package declaration, import, +variant, graph action, diagnostic, artifact, retained binary, or persistent +state. A normal explicit running request exits 1 with exact `FAIL\n` stdout and +the driver's `directory contains no WW package sources` stderr; `-c` and `-S` +omit the final marker. Visible wrong-platform raw names remain eligible, and +only the requested basename matters for symlinks (including non-regular +targets) and hidden parent directories. +CLI/target and the existing raw package-option shape retain precedence. Cold +rejection creates no work or temporary product; warm rejection preserves prior +work and public bytes without transaction residue. The focused +`named_source_prefixes_are_ignored` package observer proves these rules in both +driver stages, including malformed/import precedence, prefix-before-test and +platform classification, symlink spelling, output rollback, residue, and +diagnostic/artifact parity. Directory package coordination and test process, +filter, timeout, signal, and descendant topology are unchanged. + 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 a82642e4..08e31d2b 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -8564,6 +8564,40 @@ fn basenameoff(p: *u8, plen: u64) u64 = { return start; }; +// 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; }; + let fi: os.filestat; + match (os.stat(&fi, pathstr(path))) { + case void => { + let typ: u32 = (fi.mode: u32) & 61440u32; + if (typ == os.mode.DIR: u32) { return false; }; + }; + case let e: os.oserror => return false; + }; + let n: u64 = cstrlen(path); + let base: u64 = basenameoff(path, n); + return path[base] == '.' || path[base] == '_'; +}; + +fn sourceoperandnosources(path: *u8) void = { + let n: u64 = cstrlen(path); + let base: u64 = basenameoff(path, n); + cerr("ww: "); + if (base == 0u64) { + cerr("."); + } else { if (base == 1u64) { + cerr("/"); + } else { + os.write(os.STDERR_FILENO, path, base - 1u64); + }; }; + cerr(": directory contains no WW package sources\n"); +}; + fn arenadupcstr(src: *u8, plen: u64) *u8 = { let buf: []u8 = alloc([], plen + 1u64)!; let i: u64 = 0u64; @@ -8914,6 +8948,10 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { case void => requestedliteral = true; case let e: os.oserror => void; }; + if (sourceoperandignored(src)) { + sourceoperandnosources(src); + return 1; + }; let isdir: i32 = 0; let resolved: *u8 = resolvemodule(selfdir, src, incs.ptr, &isdir); if (resolved == nil) { @@ -9792,6 +9830,26 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { producti += 1; }; }; + if (!strings.contains(pathstr(target), "...") + && sourceoperandignored(target)) { + if (products.len != 0 && outstem != nil) { + cerr("ww test: package-test products reject -o\n"); + return 2; + }; + if (products.len != 0) { + cerr("ww test: package-test variant needs one directory\n"); + return 2; + }; + if (packageopts) { + cerr("ww test: package options need a directory\n"); + return 2; + }; + sourceoperandnosources(target); + if (targetindex >= 0 && compileonly == 0 && emitasm == 0) { + os.write(1, "FAIL\n".ptr, 5u64); + }; + return 1; + }; if (patarg != nil) { let first: os.filestat; let firstregular: bool = false; diff --git a/test/package/package_test.ww b/test/package/package_test.ww index 7c43d7e3..0cee0c38 100644 --- a/test/package/package_test.ww +++ b/test/package/package_test.ww @@ -987,6 +987,354 @@ fn cwdwritedata(dir: str, label: str) void = { clean(root); }; +@test fn named_source_prefixes_are_ignored() void = { + let root: str = fresh(); + let source: str = strings.concat(root, "/source"); + let hiddenparent: str = strings.concat(source, "/.parent"); + let symlinks: str = strings.concat(source, "/symlinks"); + let warm: str = strings.concat(source, "/warm"); + mkdirall(source); mkdirall(hiddenparent); mkdirall(symlinks); mkdirall(warm); + + let maintext: str = + "package main;\nfn main() i32 = { return 0; };\n"; + let testtext: str = strings.concat( + "package raw_windows_test;\n", + "@test fn selected() void = { assert(true); };\n"); + let missingmain: str = strings.concat( + "package main;\nimport absent.pkg;\n", + "fn main() i32 = { return pkg.value(); };\n"); + let missingtest: str = strings.concat( + "package missing_test;\nimport absent.pkg;\n", + "@test fn never() void = { abort(); };\n"); + let malformed: str = "this source must never be opened {\n"; + let hiddenbuild: str = strings.concat(source, "/_broken_windows_test.ww"); + let dotbuild: str = strings.concat(source, "/.missing.ww"); + let hiddentest: str = strings.concat(source, + "/_broken_windows_test.ww"); + let dottest: str = strings.concat(source, "/.missing_test.ww"); + let visiblewrong: str = strings.concat(source, "/visible_windows.ww"); + let visiblewrongtest: str = strings.concat(source, + "/visible_windows_test.ww"); + let hiddenparentmain: str = strings.concat(hiddenparent, "/main.ww"); + let visibletarget: str = strings.concat(symlinks, "/target.ww"); + let hiddentarget: str = strings.concat(symlinks, "/.target.ww"); + let hiddenlink: str = strings.concat(symlinks, "/_link.ww"); + let hiddendevice: str = strings.concat(symlinks, "/_device.ww"); + let visiblelink: str = strings.concat(symlinks, "/visible-link.ww"); + let warmvisible: str = strings.concat(warm, "/warm.ww"); + let warmhidden: str = strings.concat(warm, "/_warm.ww"); + writefile(hiddenbuild, malformed); + writefile(dotbuild, missingmain); + writefile(dottest, missingtest); + writefile(visiblewrong, maintext); + writefile(visiblewrongtest, testtext); + writefile(hiddenparentmain, maintext); + writefile(visibletarget, maintext); + writefile(hiddentarget, maintext); + assert(os.symlink(visibletarget, hiddenlink) == 0); + assert(os.symlink("/dev/null", hiddendevice) == 0); + assert(os.symlink(hiddentarget, visiblelink) == 0); + writefile(warmvisible, maintext); + + let stages: []str = ["ww", "ww_ww"]; + let tags: []str = ["c", "ww"]; + let rootartifacts: []str = ["/__root.unit.ww", "/__root.wwi", + "/__root.s", "/__root.o", "/__root.a", + "/__root.init.unit.ww", "/__root.init.s", "/__root.init.o"]; + let visiblebuildbin: str = ""; + let visiblebuildartifacts: []str = alloc([], rootartifacts.len: u64)!; + let visibletestbin: str = ""; + let visibletestartifacts: []str = alloc([], rootartifacts.len: u64)!; + let visibleteststdout: str = ""; + let hiddendiagnostics: []str = alloc([], 20u64)!; + let si: i32 = 0; + for (si < stages.len) { + let prefix: str = strings.concat(tags[si], "-"); + let coldwork: str = strings.concat(root, "/cold-work-", tags[si]); + let coldout: str = strings.concat(root, "/cold-output-", tags[si]); + let out: commandout; + + // A final requested basename, rather than any parent component, owns + // Go's unconditional editor-temporary exclusion. + let av: []str = [driver(stages[si]), "build", "-w", coldwork, + "-o", coldout, "_broken_windows_test.ww"]; + runcommanddir(root, strings.concat(prefix, "relative-hidden-build"), + source, av, (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + let relativebuilddiag: str = + "ww: .: directory contains no WW package sources\n"; + assert(out.stdout.len == 0 && same(out.stderr, relativebuilddiag) + && !os.exists(coldwork) && !os.exists(coldout) + && !os.exists(strings.concat(coldout, ".sepwork"))); + append(hiddendiagnostics, strings.dup(out.stderr)); + + let dotbuildav: []str = [driver(stages[si]), "build", "-o", + strings.concat(root, "/dot-output-", tags[si]), dotbuild]; + runcommand(root, strings.concat(prefix, "dot-missing-build"), dotbuildav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + let sourcediag: str = strings.concat("ww: ", source, + ": directory contains no WW package sources\n"); + assert(out.stdout.len == 0 && same(out.stderr, sourcediag) + && !has(out.stderr, "absent.pkg") + && !os.exists(strings.concat(root, "/dot-output-", tags[si])) + && !os.exists(strings.concat(root, "/dot-output-", tags[si], + ".sepwork"))); + append(hiddendiagnostics, strings.dup(out.stderr)); + + let testcoldwork: str = strings.concat(root, "/cold-test-work-", + tags[si]); + let testcoldout: str = strings.concat(root, "/cold-test-output-", + tags[si]); + let hiddentestrunav: []str = [driver(stages[si]), "test", "-w", + testcoldwork, "-o", + testcoldout, hiddentest]; + runcommand(root, strings.concat(prefix, "hidden-test-run"), + hiddentestrunav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(same(out.stdout, "FAIL\n") && same(out.stderr, sourcediag) + && !has(out.stderr, "invalid or missing package clause") + && !os.exists(testcoldwork) && !os.exists(testcoldout) + && !os.exists(strings.concat(testcoldout, ".sepwork"))); + append(hiddendiagnostics, strings.dup(out.stderr)); + + let compiled: str = strings.concat(root, "/hidden-compiled-", tags[si]); + let hiddencompileav: []str = [driver(stages[si]), "test", "-c", "-o", + compiled, + hiddentest]; + runcommand(root, strings.concat(prefix, "hidden-test-compile"), + hiddencompileav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(out.stdout.len == 0 && same(out.stderr, sourcediag) + && !os.exists(compiled) + && !os.exists(strings.concat(compiled, ".sepwork"))); + append(hiddendiagnostics, strings.dup(out.stderr)); + + let assembly: str = strings.concat(root, "/hidden-assembly-", tags[si]); + let hiddenasmav: []str = [driver(stages[si]), "test", "-S", "-o", + assembly, + hiddentest]; + runcommand(root, strings.concat(prefix, "hidden-test-assembly"), + hiddenasmav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(out.stdout.len == 0 && same(out.stderr, sourcediag) + && !os.exists(assembly) + && !os.exists(strings.concat(assembly, ".sepwork"))); + append(hiddendiagnostics, strings.dup(out.stderr)); + + let dottestav: []str = [driver(stages[si]), "test", dottest]; + runcommand(root, strings.concat(prefix, "dot-missing-test"), dottestav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(same(out.stdout, "FAIL\n") && same(out.stderr, sourcediag) + && !has(out.stderr, "absent.pkg")); + append(hiddendiagnostics, strings.dup(out.stderr)); + + let visiblework: str = strings.concat(root, "/visible-work-", tags[si]); + let visibleout: str = strings.concat(root, "/visible-output-", tags[si]); + mkdirall(visiblework); + let visiblebuildav: []str = [driver(stages[si]), "build", "-w", + visiblework, "-o", + visibleout, visiblewrong]; + runcommand(root, strings.concat(prefix, "visible-wrong-platform-build"), + visiblebuildav, (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0 + && os.exists(visibleout)); + let runav: []str = [visibleout]; + runcommand(root, strings.concat(prefix, "visible-wrong-platform-run"), + runav, (10i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0); + let visiblebytes: str = strings.dup(readfile(visibleout)); + let ai: i32 = 0; + if (si == 0) { visiblebuildbin = strings.dup(visiblebytes); } else { + assert(same(visiblebuildbin, visiblebytes)); + }; + for (ai < rootartifacts.len) { + let bytes: str = strings.dup(readfile(strings.concat(visiblework, + rootartifacts[ai]))); + if (si == 0) { append(visiblebuildartifacts, bytes); } + else { assert(same(visiblebuildartifacts[ai], bytes)); }; + ai += 1; + }; + + let testwork: str = strings.concat(root, "/visible-test-work-", tags[si]); + mkdirall(testwork); + let visibletestav: []str = [driver(stages[si]), "test", "-w", testwork, + visiblewrongtest]; + runcommand(root, strings.concat(prefix, "visible-wrong-platform-test"), + visibletestav, (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stderr.len == 0 + && occurrences(out.stdout, "selected ... ok\n") == 1 + && occurrences(out.stdout, + "1 passed, 0 failed, 0 skipped, 0 harness errors\n") == 1); + let testbytes: str = strings.dup(readfile(strings.concat(testwork, + "/main"))); + if (si == 0) { + visibletestbin = strings.dup(testbytes); + visibleteststdout = strings.dup(out.stdout); + } else { + assert(same(visibletestbin, testbytes) + && same(visibleteststdout, out.stdout)); + }; + ai = 0; + for (ai < rootartifacts.len) { + let bytes: str = strings.dup(readfile(strings.concat(testwork, + rootartifacts[ai]))); + if (si == 0) { append(visibletestartifacts, bytes); } + else { assert(same(visibletestartifacts[ai], bytes)); }; + ai += 1; + }; + + let parentout: str = strings.concat(root, "/hidden-parent-", tags[si]); + let parentwork: str = strings.concat(root, "/hidden-parent-work-", + tags[si]); + mkdirall(parentwork); + let parentav: []str = [driver(stages[si]), "build", "-w", parentwork, + "-o", parentout, hiddenparentmain]; + runcommand(root, strings.concat(prefix, "hidden-parent-visible-base"), + parentav, (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0 + && same(visiblebuildbin, readfile(parentout))); + + let hiddenlinkout: str = strings.concat(root, "/hidden-link-", tags[si]); + let hiddenlinkav: []str = [driver(stages[si]), "build", "-o", + hiddenlinkout, hiddenlink]; + runcommand(root, strings.concat(prefix, "hidden-request-symlink"), + hiddenlinkav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + let symlinkdiag: str = strings.concat("ww: ", symlinks, + ": directory contains no WW package sources\n"); + assert(out.stdout.len == 0 && same(out.stderr, symlinkdiag) + && !os.exists(hiddenlinkout) + && !os.exists(strings.concat(hiddenlinkout, ".sepwork"))); + append(hiddendiagnostics, strings.dup(out.stderr)); + + let deviceout: str = strings.concat(root, "/hidden-device-", tags[si]); + let devicework: str = strings.concat(root, "/hidden-device-work-", + tags[si]); + let deviceav: []str = [driver(stages[si]), "build", "-w", devicework, + "-o", deviceout, hiddendevice]; + runcommand(root, strings.concat(prefix, "hidden-device-build"), deviceav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(out.stdout.len == 0 && same(out.stderr, symlinkdiag) + && !os.exists(devicework) && !os.exists(deviceout) + && !os.exists(strings.concat(deviceout, ".sepwork"))); + append(hiddendiagnostics, strings.dup(out.stderr)); + + let devicetestwork: str = strings.concat(root, + "/hidden-device-test-work-", tags[si]); + let devicetestout: str = strings.concat(root, + "/hidden-device-test-output-", tags[si]); + let devicetestav: []str = [driver(stages[si]), "test", "-w", + devicetestwork, "-o", devicetestout, hiddendevice, "selected"]; + runcommand(root, strings.concat(prefix, "hidden-device-test"), + devicetestav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(same(out.stdout, "FAIL\n") && same(out.stderr, symlinkdiag) + && !os.exists(devicetestwork) && !os.exists(devicetestout) + && !os.exists(strings.concat(devicetestout, ".sepwork"))); + append(hiddendiagnostics, strings.dup(out.stderr)); + + let visiblelinkout: str = strings.concat(root, "/visible-link-", tags[si]); + let visiblelinkwork: str = strings.concat(root, "/visible-link-work-", + tags[si]); + mkdirall(visiblelinkwork); + let visiblelinkav: []str = [driver(stages[si]), "build", "-w", + visiblelinkwork, "-o", visiblelinkout, visiblelink]; + runcommand(root, strings.concat(prefix, "visible-request-symlink"), + visiblelinkav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0 + && same(visiblebuildbin, readfile(visiblelinkout))); + + let warmwork: str = strings.concat(root, "/warm-work-", tags[si]); + let warmout: str = strings.concat(root, "/warm-output-", tags[si]); + mkdirall(warmwork); + let warmvalidav: []str = [driver(stages[si]), "build", "-w", + warmwork, "-o", + warmout, warmvisible]; + runcommand(root, strings.concat(prefix, "warm-visible"), warmvalidav, + (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0 + && os.exists(warmout)); + let warmbin: str = strings.dup(readfile(warmout)); + let warmsnapshots: []str = alloc([], rootartifacts.len: u64)!; + ai = 0; + for (ai < rootartifacts.len) { + append(warmsnapshots, strings.dup(readfile(strings.concat(warmwork, + rootartifacts[ai])))); + ai += 1; + }; + assert(os.rename(warmvisible, warmhidden) == 0); + let warmhiddenav: []str = [driver(stages[si]), "build", "-w", + warmwork, "-o", + warmout, warmhidden]; + runcommand(root, strings.concat(prefix, "warm-hidden"), warmhiddenav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + let warmdiag: str = strings.concat("ww: ", warm, + ": directory contains no WW package sources\n"); + assert(out.stdout.len == 0 && same(out.stderr, warmdiag) + && same(warmbin, readfile(warmout)) + && !os.exists(strings.concat(warmout, ".new")) + && !os.exists(strings.concat(warmout, ".sepwork"))); + append(hiddendiagnostics, strings.dup(out.stderr)); + ai = 0; + for (ai < rootartifacts.len) { + assert(same(warmsnapshots[ai], readfile(strings.concat(warmwork, + rootartifacts[ai])))); + ai += 1; + }; + assert(!directoryhasnew(warmwork) + && !directoryhasfragment(warmwork, ".wwtxn.") + && !directoryhasfragment(warmwork, ".install") + && !directoryhasfragment(warmwork, ".sepwork")); + assert(os.rename(warmhidden, warmvisible) == 0); + runcommand(root, strings.concat(prefix, "warm-restored"), warmvalidav, + (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0 + && same(warmbin, readfile(warmout))); + ai = 0; + for (ai < rootartifacts.len) { + assert(same(warmsnapshots[ai], readfile(strings.concat(warmwork, + rootartifacts[ai])))); + ai += 1; + }; + assert(!directoryhasnew(warmwork) + && !directoryhasfragment(warmwork, ".wwtxn.") + && !directoryhasfragment(warmwork, ".install") + && !directoryhasfragment(warmwork, ".sepwork")); + si += 1; + }; + let half: i32 = hiddendiagnostics.len / 2; + let di: i32 = 0; + for (di < half) { + assert(same(hiddendiagnostics[di], hiddendiagnostics[di + half])); + di += 1; + }; + assert(!directoryhasnew(root) + && !directoryhasfragment(root, ".wwtxn.") + && !directoryhasfragment(root, ".install") + && !directoryhasfragment(root, ".sepwork") + && !directoryhasfragment(root, ".capture") + && !directoryhasfragment(root, ".result") + && !directoryhasfragment(root, ".request")); + clean(root); +}; + // A source file has one contiguous import section immediately after its // package clause. The parser may continue after a late import for recovery, // but the imports-only loader and the full compiler parser both reject it.