diff --git a/cmd/ww/main.c b/cmd/ww/main.c index 51032152..82317c42 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -7849,14 +7849,11 @@ parse_build_flags(const char *cmd, int argc, char **argv, return -1; } else if (*src_out == NULL) { *src_out = argv[i]; - /* Go's FlagSet stops at the first positional. For build, - * everything after it is package-request input, even "--". */ - if (strcmp(cmd, "build") == 0) { - i++; - break; - } - } else { - break; /* leave remaining argv to caller (run-args) */ + /* Go's FlagSet stops at the first positional. Build leaves + * the suffix as package-request input; run passes it verbatim + * to the selected program, including flag-like operands. */ + i++; + break; } } return i; diff --git a/docs/build-system.md b/docs/build-system.md index baf1d6d5..07df4745 100644 --- a/docs/build-system.md +++ b/docs/build-system.md @@ -2300,7 +2300,7 @@ The default workflow is deliberately short: ```sh ww init example.org/hello ww build -ww run -- argument +ww run . argument ww test . ``` @@ -2313,8 +2313,9 @@ shown in verbose output. `ww build [DIR|PRODUCT]` builds the default root product or one named product. The default profile is the fully specified `debug` profile; `--profile=release` selects the toolchain's immutable release profile. `ww run` first performs that -same build, then runs only a product with `H = B`; arguments after `--` are never -interpreted by the build. +same build, then runs only a product with `H = B`. After one explicit run target +is selected, every remaining operand is program input and is never interpreted +as a build option. A leading `--` is not the target boundary. There is no command that means “build and opportunistically download whatever is missing.” If a locked source or toolchain is absent, the diagnostic names its @@ -11168,6 +11169,165 @@ source packages, shared test-process state and failure topology, RE2-compatible flat `-run`, finite special-source handling, or external-driver interruption recovery. +### 11.57 Implemented post-target `ww run` argument boundary + +After one explicit `ww run` target has been selected, every later operand is +now program input. Neither driver reparses that suffix as build options. Known +and unknown option spellings, would-be option values, a lone `--`, empty +strings, later ordinary operands, and later `.ww` spellings retain their exact +bytes and order in the child vector. WW still supports only one selected run +target: this boundary does not turn a later `.ww` spelling into a second source +file. + +#### Pinned authority, official tests, and applicability + +The sole semantic authority is official Go 1.26.5 at commit +`c19862e5f8415b4f24b189d065ed739517c548ba`: + +- **behavior directly implemented or asserted by pinned Go** — the Go command + parses a run command's registered flags before entering `runRun` and passes + only `Flag.Args()` to it + ([`cmd/go/main.go`, lines 312–322](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/main.go#L312-L322)). + The standard flag parser stops at the first non-flag positional; it consumes + `--` only when that spelling occurs before the positional boundary + ([`flag/flag.go`, lines 1074–1089 and 1149–1176](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/flag/flag.go#L1074-L1089)). +- **behavior directly implemented or asserted by pinned Go** — `runRun` + consumes either the contiguous named-file prefix or one selected package, + leaves the suffix as `cmdArgs`, and attaches those exact arguments to the run + action + ([`cmd/go/internal/run/run.go`, lines 96–140 and 170–173](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/run/run.go#L96-L140)). +- **behavior directly implemented or asserted by pinned Go** — official + regression test + [`cmd/go/testdata/script/mod_run_flags_issue64738.txt`, lines 1–4](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/mod_run_flags_issue64738.txt#L1-L4) + asserts that `-p ignored` after a requested package is program input, not a + `cmd/go` flag. Official + [`cmd/go/testdata/script/run_dirs.txt`, lines 1–20](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/run_dirs.txt#L1-L20) + separately anchors Go's contiguous multi-file prefix; that source-set rule + remains open in WW. +- **behavior directly implemented or asserted by pinned Go** — Go deliberately + does not preserve the compiled program's exact nonzero exit status + ([`cmd/go/internal/run/run.go`, lines 56 and 198–210](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/run/run.go#L198-L210)); + command error accounting owns the resulting Go-command status + ([`cmd/go/internal/base/base.go`, lines 218–246](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/base/base.go#L218-L246)). + That independent exit-status difference is not credited to this slice. +- **behavior derived from the pinned implementation** — WW's one explicit + local run target supplies the same honest semantic boundary without modules, + manifests, registries, network resolution, generalized imports, or source + build expressions. WW's implicit default-current-directory extension has no + explicit target token, so this slice leaves its leading option parsing + unchanged. A leading or pre-target `--` therefore retains WW's existing + exact unknown-flag rejection and is not claimed as Go `FlagSet` terminator + parity. + +Before this slice, **directly measured WW behavior** was identical in Cstage +and WWstage but differed from the pinned boundary. Immediately target-following +`-p ignored` and `-- -p ignored` returned status 2, empty stdout, and exact +stderr `ww run: unknown flag\n`; `-o sentinel` and `-I path` were consumed by +the driver; and only a suffix after a second nonflag was passed unchanged. +Named-source and directory-package probes agreed on status, stdout, stderr, +and diagnostic order. Accepted runs loaded one target and its ordinary import +closure, constructed the private run action, compiled, assembled, archived, +linked, executed, propagated WW's established child status, and removed the +private executable and `.sepwork`. Rejected flag rows stopped before target +resolution, graph/action construction, tools, runtime, or scratch creation. + +#### Ownership, source selection, and the four permanent axes + +- **directly measured WW behavior** — Cstage owns the boundary in + `parse_build_flags` and its `do_run` consumer; WWstage owns the semantic twin + in its `dorun` parser and executor. No loader, package coordinator, compiler, + assembler, archiver, linker, test coordinator, checker, import binder, or + runtime library can repair a driver option that was already consumed. +- **behavior derived from the pinned implementation** — the primary axis is + build/run execution semantics. Driver options are recognized before the + first explicit target. That target alone controls resolution; the complete + later suffix controls only child invocation. +- **behavior derived from the pinned implementation** — the test axis is an + explicit non-effect. `ww test` option parsing, target discovery, filters, + source variants, generated harness, retained products, execution topology, + and result accounting do not use this run boundary. +- **behavior derived from the pinned implementation** — the package axis is an + explicit non-effect. The suffix is never searched, statted, opened, or + classified as source. Declared-name validation, source membership, command + classification, initializer topology, and canonical package identity remain + those of the one selected target. +- **behavior derived from the pinned implementation** — the import axis is an + explicit non-effect. Dotted spelling, aliases, local/vendor search, binding, + visibility, cycles, graph edges, interface ownership, and initialization + order are determined only by the selected source closure. Runtime argv is + never package, import, graph, action, symbol, artifact, `.wwi`, publication, + or persistence identity. + +Direct roots retain `__root.*`; dotted directories retain dotted package, +import, action, symbol, artifact, and semantic identities. Physical target +spellings and paths remain loader or presentation observations. Post-target +arguments add no root, edge, action, source, or invalidation input and cannot +change compilation or comparable artifact bytes. + +#### Phase order, diagnostics, and lifecycle + +- **behavior derived from the pinned implementation** — target resolution, + loading, import closure, graph construction, compilation, assembly, + in-process archiving, and linking retain their existing order and inputs. + After a successful private link, the child vector is the private executable + at index 0 followed by the exact post-target suffix. The suffix reaches no + earlier phase. +- **directly measured WW behavior** — `os.args()` exposes that complete vector, + including the PID-bearing private executable path at index 0. WW currently + propagates an ordinary child's exact exit code. The argument repair changes + only indices 1 onward; PID presentation and exact child-status propagation + are preserved, including their independent difference from pinned Go. +- **behavior derived from the pinned implementation** — known, unknown, + incomplete, or `--` option spellings before the target retain their existing + driver diagnostics and status. No spelling after the target can emit a + driver-option diagnostic. Missing, invalid, non-main, or producer-failing + targets diagnose before runtime; a valid target starts and thereafter owns + output and failure caused by its arguments. +- **behavior derived from the pinned implementation** — run products remain + request-private. No public executable, retained test product, semantic fact, + work record, transaction, result, or cache entry is published. Post-target + argv enters no unit, interface, assembly, object, archive, initializer, + executable, stamp, or persistence byte and creates no reuse or invalidation + key. Ordinary build and every test route are byte-for-byte non-effects. +- **behavior derived from the pinned implementation** — target or producer + failure starts no program and follows ordinary rollback. Runtime nonzero + follows the established WW status mapping after successful private linking. + Existing unrelated public and committed semantic bytes remain untouched. + Normal success, producer failure, runtime failure, and concurrent runs remove + each request's owned private executable, `.sepwork`, stage, transaction, + capture, result, request, descriptor, and child. Parser state and argv are + invocation-local, so overlapping suffixes cannot cross between requests. +- **behavior derived from the pinned implementation** — Cstage and WWstage + must select the same boundary and retain exact status, stdout, stderr, + diagnostic order, runtime argv, normal cleanup, and comparable build-artifact + byte identity. The existing PID-bearing private path difference outside + stable comparisons is not reclassified by this slice. + +The WW-native observer `run_post_target_arguments_are_program_argv` covers both +driver stages with literal named-source and directory-package targets. It +checks known separate and joined option spellings, unknown options, would-be +values, singleton value-taking spellings, `--`, later nonflags and `.ww`, an +empty string, pre-target controls, no-operand default-dot selection, diagnostic +precedence, exact child status and output, concurrent isolation, build/test +controls, artifact-byte parity, and normal residue cleanup. + +No signal or process-supervision owner changes. Direct external SIGTERM during +blocked persistent compilation still can leave the owned compiler alive and +exactly three fixed `.new` stages, poisoning the next request while preserving +prior public and committed semantic bytes. That verified interruption gap +remains open; blind stage deletion is not this argument-boundary repair. + +No serialized representation changes. Build workdir format remains `18`, test +workdir format remains `19`, and semantic storage format remains `3`; AST and +`.wwi` schemas, action descriptors, request protocols, transaction markers, +and stored facts are unchanged. This closes only the post-target argv slice. +Regular or missing `_test.ww`, missing `.ww`, hidden named sources, multiple +leading sources and their source-set boundary, shared test-package state, +panic/exit/Fatal/FailNow topology, RE2-compatible flat `-run`, literal +nonregular named-source behavior, three-way no-buildable-source diagnostics, +Go-like run exit-status mapping, and external-driver interruption recovery +remain open where applicable. + ## 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 213d6add..83e98c3e 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -703,6 +703,32 @@ ImportPath = ident { "." ident } . arguments and result; this rule does not adopt Go's source signature. An ordinary import of a package declared `main` is rejected, except for the toolchain's colocated external-test wiring. +- For `ww run`, the first explicit non-option operand selects the one run + target. Every later operand is program input and is passed unchanged and in + order after the private executable's `argv[0]`; it is not reparsed as a + driver option. This includes known or unknown option spellings, would-be + option values, a lone `--`, empty arguments, ordinary nonflags, and later + `.ww` spellings. A later `.ww` is therefore argv, not another source in a + command-line package; multiple named sources remain unsupported. Registered + driver options before the target retain their established spelling, value, + repetition, and diagnostic rules. A leading or otherwise pre-target `--` + retains WW's exact unknown-flag rejection rather than acting as a general + terminator. With no explicit target, `ww run` retains its implicit current- + directory selection and has no post-target suffix. + Only the selected target and pre-target driver options affect resolution, + source loading, package/import identity, graph construction, compiler, + assembler, archiver, linker, private executable bytes, reuse, or + invalidation. A missing, invalid, non-main, or producer-failing target + diagnoses before runtime and no suffix spelling can mask that result. A + successful private link executes with the exact suffix and retains WW's + existing stdout, stderr, and exact child-status propagation. The private run + product is removed normally and publishes no executable or persistent + semantic state. `ww build`, every `ww test` route, package membership, and + dotted import semantics are unchanged. Direct roots remain `__root.*`, + dotted directories retain dotted identity, and argv never becomes package, + import, action, symbol, artifact, `.wwi`, publication, or persistence + identity. Build workdir format remains 18, test workdir format remains 19, + and semantic storage format remains 3. - For `ww build`, the output-option name is exactly `o`. The accepted forms are `-o VALUE`, `--o VALUE`, `-o=VALUE`, and `--o=VALUE`. An equals form splits at its first `=` and preserves every later byte, including further `=` diff --git a/docs/test-system-v2.md b/docs/test-system-v2.md index a53afb92..bec66fc2 100644 --- a/docs/test-system-v2.md +++ b/docs/test-system-v2.md @@ -479,6 +479,82 @@ supervision is unchanged: the known orphan compiler, fixed `.new` staging, and later persistent-request poisoning remain open and are not credited to this slice. +The run front has one separate post-target argument contract. The sole +authority is official Go 1.26.5 at commit +`c19862e5f8415b4f24b189d065ed739517c548ba`: + +- **behavior directly implemented or asserted by pinned Go** — command flag + parsing supplies only its positional suffix to `runRun` + (`cmd/go/main.go:312–322`; `flag/flag.go:1074–1089,1149–1176`), and + `runRun` consumes the named-file prefix or one package before attaching the + untouched suffix to the run action + (`cmd/go/internal/run/run.go:96–140,170–173`). +- **behavior directly implemented or asserted by pinned Go** — official + `cmd/go/testdata/script/mod_run_flags_issue64738.txt:1–4` proves that + target-following `-p ignored` is program input rather than a Go-command + option. `cmd/go/testdata/script/run_dirs.txt:1–20` anchors the independent + contiguous named-file prefix, which WW does not claim here. +- **behavior derived from the pinned implementation** — after WW's one + explicit local run target, every remaining operand is runtime argv. The + rule honestly applies without importing Go modules or manifests. WW's + default-dot extension has no explicit target boundary, and leading or + pre-target `--` remains its existing unknown flag rather than gaining + general Go flag-terminator semantics. + +Before the repair, **directly measured WW behavior** was stage-equal but +different: both drivers rejected immediately target-following `-p ignored` and +`-- -p ignored` at status 2 with empty stdout and exact unknown-flag stderr, +consumed target-following `-o` and `-I` values as driver configuration, and +passed the ordered suffix only after a second nonflag. Accepted controls loaded +one target and its ordinary import closure and completed the private compiler, +assembler, archiver, linker, and runtime route; rejected rows stopped before +loading, actions, tools, runtime, or scratch. + +The focused `run_post_target_arguments_are_program_argv` observer owns the +completed boundary in `test/package/package_test.ww`. Across Cstage and WWstage +it uses both a literal named source and a directory package and requires exact +ordered delivery for separate and joined known-option spellings, unknown +options, would-be values, a singleton value-taking spelling, `--`, later +nonflags and `.ww`, and an empty string. It separately requires pre-target +options and their missing/unknown diagnostics to remain driver-owned, leading +`--` to keep its current rejection, no-operand run to keep default-dot +selection, and target load/producer diagnostics to precede inert suffix bytes. +Valid programs expose the private executable at `os.args()[0]`, receive the +exact suffix at indices 1 onward, and retain WW's existing stdout, stderr, and +exact child-status mapping. Pinned Go's distinct command-level nonzero status +mapping (`cmd/go/internal/run/run.go:56,198–210` and +`cmd/go/internal/base/base.go:218–246`) remains an open difference. + +The observer also treats all four permanent axes as one contract. The build/run +axis changes only the parser-to-child boundary. Build and test controls retain +their status, streams, action inputs, and comparable artifact bytes. Package +membership and command classification still come only from the selected +target, while dotted-import resolution, edges, interfaces, visibility, and +initialization still come only from that target's closure. Direct actions +remain `__root.*`; dotted directories retain dotted identity; argv creates no +package, import, graph, action, symbol, artifact, `.wwi`, publication, +persistence, reuse, or invalidation identity. + +Post-target bytes therefore cannot change compiler, assembler, archiver, +linker, initializer, or private executable bytes. Run publishes no public +product or semantic state. Normal success, target/producer failure, runtime +nonzero, and overlapping requests must keep suffixes isolated and remove each +owned private executable, `.sepwork`, stage, transaction, capture, result, +request, descriptor, and child. Cstage and WWstage must agree on stable status, +stdout, stderr, diagnostic order, runtime argv, cleanup, and comparable +artifact bytes. No AST, interface, descriptor, request, transaction, or +persistent schema changes; build workdir format remains 18, test workdir format +remains 19, semantic storage format remains 3, and no test-result cache is +introduced. + +The observer makes no claim for regular or missing `_test.ww`, missing `.ww`, +hidden named sources, multiple leading source operands and source-set +boundaries, literal nonregular named sources, shared test-package state, +panic/exit/Fatal/FailNow topology, Go-compatible RE2 `-run`, three-way +no-buildable-source causes, or Go-like run exit-status mapping. External-driver +SIGTERM supervision is also unchanged: the verified orphan compiler, three +fixed `.new` stages, and later persistent-request poisoning remain open. + An existing local directory whose requested build basename ends `.ww` (including a visible `_test.ww` symlink to a directory) remains a directory package, not a raw named test source. WWstage `ww build` now uses the same diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index d13d6956..640e6eee 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -9832,12 +9832,11 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { }; }; }; }; i += 1; } else { - if (src == nil) { - src = p; - i += 1; - } else { - passstart = i; - }; + src = p; + i += 1; + // The first target ends driver option parsing. The exact + // suffix, including flag-like operands, belongs to the program. + passstart = i; }; }; }; diff --git a/test/package/package_test.ww b/test/package/package_test.ww index 18ae009e..7197388b 100644 --- a/test/package/package_test.ww +++ b/test/package/package_test.ww @@ -25131,3 +25131,307 @@ fn documentationclean(root: str) void = { && !directoryhasfragment(root, ".request")); clean(root); }; + +// An explicit run target ends driver-option parsing. The child receives every +// later string unchanged, including spellings that would have been options at +// the driver front; argv[0] remains WW's private PID-bearing executable path. +fn runargvexpect(out: *commandout, code: i32, stdout: str, stderr: str, + message: str) void = { + if (out.termination != exec.termination.EXIT || out.code != code + || !same(out.stdout, stdout) || !same(out.stderr, stderr)) { + abort(message); + }; +}; + +fn runargvrequire(ok: bool, message: str) void = { + if (!ok) { abort(message); }; +}; + +fn runargvprivateclean(record: str, message: str) void = { + let path: str = readfile(record); + runargvrequire(path.len != 0 && !os.exists(path) + && !os.exists(strings.concat(path, ".sepwork")), message); +}; + +fn runargvnormalizedstderr(stderr: str) str = { + let prefix: str = "/tmp/ww_run_"; + let begin: i32 = pos(stderr, prefix); + runargvrequire(begin >= 0, "run argv: missing private diagnostic path"); + let digits: i32 = begin + prefix.len; + let end: i32 = digits; + for (end < stderr.len && stderr[end] >= '0' && stderr[end] <= '9') { + end += 1; + }; + runargvrequire(end > digits, + "run argv: malformed private diagnostic path"); + return strings.concat(strings.sub(stderr, 0, begin), "$RUN", + strings.sub(stderr, end, stderr.len)); +}; + +@test fn run_post_target_arguments_are_program_argv() void = { + let root: str = fresh(); + let source: str = strings.concat(root, "/source"); + let command: str = strings.concat(source, "/cmd/argv"); + let library: str = strings.concat(source, "/lib/argv"); + let tests: str = strings.concat(source, "/proof/tests"); + mkdirall(command); mkdirall(library); mkdirall(tests); + let named: str = strings.concat(source, "/named.ww"); + let program: str = strings.concat( + "package main;\nimport os;\nimport strings;\n", + "fn main() i32 = {\n", + " let av: []str = os.args(); let i: i32 = 1;\n", + " if (av.len > 1) {\n", + " let fd: i32 = os.open(av[1], os.flag.WRONLY | os.flag.CREATE |\n", + " os.flag.TRUNC, 384i32); if (fd < 0) { return 96; };\n", + " os.write(fd, av[0].ptr, av[0].len: u64); os.close(fd);\n", + " };\n", + " for (i < av.len) {\n", + " os.write(os.STDOUT_FILENO, \"<\".ptr, 1u64);\n", + " os.write(os.STDOUT_FILENO, av[i].ptr, av[i].len: u64);\n", + " os.write(os.STDOUT_FILENO, \">\\n\".ptr, 2u64); i += 1;\n", + " };\n", + " if (av.len == 3 && strings.compare(av[2], \"exit-zero\") == 0) {\n", + " return 0;\n", + " }; return 47;\n};\n"); + writefile(named, program); + writefile(strings.concat(command, "/main.ww"), program); + writefile(strings.concat(library, "/library.ww"), + "package argvlib;\nexport fn value() i32 = { return 1; };\n"); + writefile(strings.concat(tests, "/tests.ww"), + "package argvtests;\nfn value() i32 = { return 1; };\n"); + writefile(strings.concat(tests, "/tests_test.ww"), strings.concat( + "package argvtests;\n", + "@test fn ordinary() void = { assert(value() == 1); };\n")); + + let expected: str = strings.concat( + "<-I>\n\n<--I=joined-include>\n", + "<-o>\n\n<--o=joined-output>\n", + "<-L>\n\n<-l>\n\n", + "<-p>\n\n<-->\n\n\n<>\n<-I>\n"); + let suffix: []str = ["-I", "runtime-include", "--I=joined-include", + "-o", "runtime-output", "--o=joined-output", "-L", + "runtime-library-dir", "-l", "runtime-library", "-p", "ignored", + "--", "later", "later.ww", "", "-I"]; + let stages: []str = ["ww", "ww_ww"]; + let tags: []str = ["c", "ww"]; + let targets: []str = [named, "cmd.argv", command]; + let targettags: []str = ["named", "dotted", "directory"]; + let out: commandout; + let si: i32 = 0; + for (si < stages.len) { + let ti: i32 = 0; + for (ti < targets.len) { + let record: str = strings.concat(root, "/private-", tags[si], + "-", targettags[ti]); + let ignoredoutput: str = strings.concat(root, "/ignored-run-", + tags[si], "-", targettags[ti]); + let av: []str = [driver(stages[si]), "run", "-I", source, + "-o", ignoredoutput, targets[ti]]; + append(av, record); + let ai: i32 = 0; + for (ai < suffix.len) { append(av, suffix[ai]); ai += 1; }; + runcommand(root, strings.concat("run-argv-", tags[si], "-", + targettags[ti]), av, + (120i64 * (time.second: i64)): time.duration, &out); + runargvexpect(&out, 47, + strings.concat("<", record, ">\n", expected), "", + "run argv: target suffix result"); + runargvrequire(!os.exists(ignoredoutput) + && !os.exists(strings.concat(ignoredoutput, ".sepwork")), + "run argv: run published an output"); + runargvprivateclean(record, "run argv: runtime-nonzero cleanup"); + ti += 1; + }; + + let successrecord: str = strings.concat(root, "/private-success-", + tags[si]); + let successav: []str = [driver(stages[si]), "run", named, + successrecord, "exit-zero"]; + runcommand(root, strings.concat("run-argv-success-", tags[si]), + successav, (120i64 * (time.second: i64)): time.duration, &out); + runargvexpect(&out, 0, + strings.concat("<", successrecord, ">\n\n"), "", + "run argv: runtime-success result"); + runargvprivateclean(successrecord, "run argv: runtime-success cleanup"); + + // Before an explicit target, the existing option grammar remains in + // force. No-operand run retains its implicit current-directory target. + let unknownav: []str = [driver(stages[si]), "run", "-p", named]; + runcommand(root, strings.concat("run-argv-unknown-", tags[si]), + unknownav, time.second, &out); + runargvexpect(&out, 2, "", "ww run: unknown flag\n", + "run argv: pre-target unknown flag"); + let missingflagav: []str = [driver(stages[si]), "run", "-I"]; + runcommand(root, strings.concat("run-argv-missing-flag-", tags[si]), + missingflagav, time.second, &out); + runargvexpect(&out, 2, "", "ww run: -I needs an argument\n", + "run argv: pre-target missing option value"); + let leadingstopav: []str = [driver(stages[si]), "run", "--", named]; + runcommand(root, strings.concat("run-argv-leading-stop-", tags[si]), + leadingstopav, time.second, &out); + runargvexpect(&out, 2, "", "ww run: unknown flag\n", + "run argv: leading terminator control"); + let defaultav: []str = [driver(stages[si]), "run"]; + runcommanddir(root, strings.concat("run-argv-default-", tags[si]), + command, defaultav, + (120i64 * (time.second: i64)): time.duration, &out); + runargvexpect(&out, 47, "", "", "run argv: default-dot control"); + + // Target selection and loading diagnose before inert runtime bytes. + let missingtarget: []str = [driver(stages[si]), "run", "-I", source, + "missing.argv", "-p", "ignored"]; + runcommand(root, strings.concat("run-argv-missing-target-", tags[si]), + missingtarget, time.second, &out); + runargvexpect(&out, 1, "", + "ww run: cannot find module missing.argv\n", + "run argv: missing target precedence"); + let nonmain: []str = [driver(stages[si]), "run", "-I", source, + "lib.argv", "--", "later.ww"]; + runcommand(root, strings.concat("run-argv-non-main-", tags[si]), + nonmain, time.second, &out); + runargvexpect(&out, 1, "", + "ww: package lib.argv is not a main package\n", + "run argv: non-main target precedence"); + si += 1; + }; + + // A selected main that reaches the compiler still diagnoses before any + // flag-like suffix can be interpreted or any runtime side effect can occur. + let broken: str = strings.concat(source, "/broken.ww"); + let brokenmarker: str = strings.concat(root, "/broken-ran"); + writefile(broken, strings.concat( + "package main;\nimport os;\nfn main() i32 = {\n", + " let fd: i32 = os.open(\"", brokenmarker, "\", os.flag.WRONLY |\n", + " os.flag.CREATE | os.flag.TRUNC, 384i32);\n", + " if (fd >= 0) { os.close(fd); }; return missing;\n};\n")); + let producerstderr: str = ""; + let producerwant: str = strings.concat( + "$RUN/main.sepwork/__root.unit.ww:7:41: error: undefined: missing\n", + "ww: w6c failed for (root)\n"); + si = 0; + for (si < stages.len) { + let brokenav: []str = [driver(stages[si]), "run", broken, + "-p", "ignored", "--", brokenmarker]; + runcommand(root, strings.concat("run-argv-producer-", tags[si]), + brokenav, (120i64 * (time.second: i64)): time.duration, &out); + runargvrequire(out.termination == exec.termination.EXIT + && out.code == 1 && out.stdout.len == 0 + && occurrences(out.stderr, "undefined: missing") == 1 + && occurrences(out.stderr, "ww: w6c failed for (root)") == 1 + && pos(out.stderr, "undefined: missing") + < pos(out.stderr, "ww: w6c failed for (root)") + && !has(out.stderr, "unknown flag") && !os.exists(brokenmarker), + "run argv: producer failure precedence"); + let normalized: str = runargvnormalizedstderr(out.stderr); + runargvrequire(same(normalized, producerwant), + "run argv: exact producer diagnostic"); + if (si == 0) { producerstderr = strings.dup(normalized); } + else { + runargvrequire(same(producerstderr, normalized), + "run argv: producer diagnostic stage parity"); + }; + si += 1; + }; + + // Runtime suffixes are request-local even while the two fronts execute + // concurrently and share their source closure. + let crecord: str = strings.concat(root, "/private-parallel-c"); + let wrecord: str = strings.concat(root, "/private-parallel-ww"); + let cav: []str = [driver("ww"), "run", named, crecord, + "c-only", "", "-I"]; + let wav: []str = [driver("ww_ww"), "run", "-I", source, command, + wrecord, "ww-only", "--", "tail.ww"]; + let cc: exec.command; + cc.path = cav[0]; cc.argv = cav; cc.env = os.getenvs(); cc.dir = repo(); + cc.stdoutpath = strings.concat(root, "/run-argv-parallel-c.stdout"); + cc.stderrpath = strings.concat(root, "/run-argv-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 = wav[0]; wc.argv = wav; wc.env = os.getenvs(); wc.dir = repo(); + wc.stdoutpath = strings.concat(root, "/run-argv-parallel-ww.stdout"); + wc.stderrpath = strings.concat(root, "/run-argv-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); + }; + }; + runargvrequire(cp.result.errno == 0 && cp.result.cleanuperrno == 0 + && cp.result.termination == exec.termination.EXIT && cp.result.code == 47 + && wp.result.errno == 0 && wp.result.cleanuperrno == 0 + && wp.result.termination == exec.termination.EXIT && wp.result.code == 47 + && same(readfile(cc.stdoutpath), strings.concat("<", crecord, + ">\n\n<>\n<-I>\n")) + && readfile(cc.stderrpath).len == 0 + && same(readfile(wc.stdoutpath), strings.concat("<", wrecord, + ">\n\n<-->\n\n")) + && readfile(wc.stderrpath).len == 0, + "run argv: concurrent isolation"); + runargvprivateclean(crecord, "run argv: parallel C cleanup"); + runargvprivateclean(wrecord, "run argv: parallel WW cleanup"); + + // Run argv never changes build/test products or persistent action bytes. + let cbuildwork: str = strings.concat(root, "/build-work-c"); + let wbuildwork: str = strings.concat(root, "/build-work-ww"); + let cbuildout: str = strings.concat(root, "/build-output-c"); + let wbuildout: str = strings.concat(root, "/build-output-ww"); + mkdirall(cbuildwork); mkdirall(wbuildwork); + let cbuildav: []str = [driver("ww"), "build", "-w", cbuildwork, + "-o", cbuildout, named]; + let wbuildav: []str = [driver("ww_ww"), "build", "-w", wbuildwork, + "-o", wbuildout, named]; + runcommand(root, "run-argv-build-c", cbuildav, + (120i64 * (time.second: i64)): time.duration, &out); + runargvexpect(&out, 0, "", "", "run argv: C build control"); + runcommand(root, "run-argv-build-ww", wbuildav, + (120i64 * (time.second: i64)): time.duration, &out); + runargvexpect(&out, 0, "", "", "run argv: WW build control"); + runargvrequire(same(readfile(cbuildout), readfile(wbuildout)) + && same(wrongsuffixsemantictreesnapshot(cbuildwork), + wrongsuffixsemantictreesnapshot(wbuildwork)), + "run argv: build artifact parity"); + let ctestwork: str = strings.concat(root, "/test-work-c"); + let wtestwork: str = strings.concat(root, "/test-work-ww"); + let ctestout: str = strings.concat(root, "/test-output-c"); + let wtestout: str = strings.concat(root, "/test-output-ww"); + mkdirall(ctestwork); mkdirall(wtestwork); + let ctestav: []str = [driver("ww"), "test", "-c", "-w", ctestwork, + "-I", source, "-o", ctestout, "proof.tests"]; + let wtestav: []str = [driver("ww_ww"), "test", "-c", "-w", wtestwork, + "-I", source, "-o", wtestout, "proof.tests"]; + runcommand(root, "run-argv-test-c", ctestav, + (120i64 * (time.second: i64)): time.duration, &out); + runargvexpect(&out, 0, "", "", "run argv: C test control"); + runcommand(root, "run-argv-test-ww", wtestav, + (120i64 * (time.second: i64)): time.duration, &out); + runargvexpect(&out, 0, "", "", "run argv: WW test control"); + runargvrequire(same(readfile(ctestout), readfile(wtestout)) + && same(wrongsuffixsemantictreesnapshot(ctestwork), + wrongsuffixsemantictreesnapshot(wtestwork)), + "run argv: test artifact parity"); + + runargvrequire(!wrongsuffixpathfragment(root, ".new") + && !wrongsuffixpathfragment(root, ".old") + && !wrongsuffixpathfragment(root, ".wwtxn.") + && !wrongsuffixpathfragment(root, ".install") + && !wrongsuffixpathfragment(root, ".sepwork") + && !wrongsuffixpathfragment(root, ".stage") + && !wrongsuffixpathfragment(root, ".capture") + && !wrongsuffixpathfragment(root, ".result") + && !wrongsuffixpathfragment(root, ".request") + && !wrongsuffixpathfragment(root, ".descriptor") + && !wrongsuffixpathfragment(root, ".child"), + "run argv: normal residue"); + clean(root); +};