diff --git a/cmd/ww/main.c b/cmd/ww/main.c index a5a3e21c..54cf79ee 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -1300,6 +1301,7 @@ struct sepproduct { const char *internal_package; const char *external_package; const char *status; + const char *publish; /* optional retained test executable */ const char *artifact; int variant; int directory_product; @@ -1312,6 +1314,7 @@ struct sepproduct { int pxtest; int support; /* direct generated-main support action, or -1 */ char *stage_out; /* request-private linked/published output */ + char *stage_publish; /* request-private retained executable copy */ char *stage_iface; /* request-private published package interface */ char *stage_status; /* request-private completion marker */ }; @@ -4932,6 +4935,34 @@ copy_file_stage(const char *src, const char *dst) return bad ? -1 : 0; } +/* Go's BuildInstallFunc installs linked test binaries with 0777 filtered by + * the caller's umask. Keep the retained copy byte-identical to the temporary + * runnable while giving the new staging inode that executable mode. */ +static int +copy_executable_stage(const char *src, const char *dst) +{ + FILE *in = fopen(src, "rb"); + if (in == NULL) return -1; + int fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0777); + if (fd < 0) { fclose(in); return -1; } + FILE *out = fdopen(fd, "wb"); + if (out == NULL) { close(fd); fclose(in); (void)unlink(dst); return -1; } + unsigned char buf[65536]; + int bad = 0; + for (;;) { + size_t n = fread(buf, 1, sizeof buf, in); + if (n != 0 && fwrite(buf, 1, n, out) != n) bad = 1; + if (bad || n < sizeof buf) { + if (ferror(in)) bad = 1; + break; + } + } + if (fclose(in) != 0) bad = 1; + if (fclose(out) != 0) bad = 1; + if (bad) (void)unlink(dst); + return bad ? -1 : 0; +} + struct septxnentry { char *stage; char *dst; @@ -5117,9 +5148,11 @@ sep_free_product_staging(struct sepproduct *products, int nproducts) for (int i = 0; i < nproducts; i++) { free(products[i].stage_status); free(products[i].stage_iface); + free(products[i].stage_publish); free(products[i].stage_out); products[i].stage_status = NULL; products[i].stage_iface = NULL; + products[i].stage_publish = NULL; products[i].stage_out = NULL; } } @@ -5158,11 +5191,13 @@ sep_validate_product_path_pair(const struct sepproduct *a, const char *ap[] = { a->stage_status != NULL ? a->status : NULL, a->stage_out != NULL ? a->out : NULL, - a->stage_status, a->stage_out, a->stage_iface }; + a->stage_publish != NULL ? a->publish : NULL, + a->stage_status, a->stage_out, a->stage_publish, a->stage_iface }; const char *bp[] = { b->stage_status != NULL ? b->status : NULL, b->stage_out != NULL ? b->out : NULL, - b->stage_status, b->stage_out, b->stage_iface }; + b->stage_publish != NULL ? b->publish : NULL, + b->stage_status, b->stage_out, b->stage_publish, b->stage_iface }; for (size_t i = 0; i < nelem(ap); i++) { if (ap[i] == NULL) continue; for (size_t j = 0; j < nelem(bp); j++) { @@ -5221,6 +5256,10 @@ sep_validate_request_staging(struct sepgraph *g, const char *scratch, int warm, && sep_prepare_product_stage(&products[i].stage_status, products[i].status) < 0) return -1; + if (products[i].publish != NULL && !products[i].no_tests + && sep_prepare_product_stage(&products[i].stage_publish, + products[i].publish) < 0) + return -1; if (emit_asm) continue; int owns_output = root_package ? publish_package : is_test ? !products[i].no_tests @@ -5336,7 +5375,8 @@ sep_discard_request_staging(struct sepgraph *g, const char *scratch, int warm, } for (int i = 0; i < nproducts; i++) { const char *path[] = { products[i].stage_out, - products[i].stage_iface, products[i].stage_status }; + products[i].stage_publish, products[i].stage_iface, + products[i].stage_status }; for (size_t j = 0; j < nelem(path); j++) if (path[j] != NULL && unlink(path[j]) != 0 && errno != ENOENT) @@ -5564,6 +5604,7 @@ build_one_sep_impl(const char *src, int entry_is_dir, products[i].ptest = -1; products[i].pxtest = -1; products[i].stage_out = NULL; + products[i].stage_publish = NULL; products[i].stage_iface = NULL; products[i].stage_status = NULL; } @@ -5876,6 +5917,9 @@ build_one_sep_impl(const char *src, int entry_is_dir, if (!g->pkg[root].failed && sep_root_is_command(&g->pkg[root]) && validate_command_output_path(products[i].out) < 0) return 1; + if (products[i].publish != NULL + && validate_command_output_path(products[i].publish) < 0) + return 1; if (products[i].status != NULL && validate_command_output_path(products[i].status) < 0) return 1; @@ -5980,8 +6024,11 @@ build_one_sep_impl(const char *src, int entry_is_dir, struct sep_created_dirs created_output = {0}; struct sep_created_dirs created_work = {0}; if (create_output_dir != NULL && create_output_dir[0] != '\0' - && sep_mkdirs(create_output_dir, 0700, &created_output) != 0) { - fprintf(stderr, "ww: cannot create build output directory %s\n", + && sep_mkdirs(create_output_dir, is_test ? 0777 : 0700, + &created_output) != 0) { + fprintf(stderr, is_test + ? "ww: cannot create test output directory %s\n" + : "ww: cannot create build output directory %s\n", create_output_dir); free(order); return 1; @@ -6527,6 +6574,14 @@ build_one_sep_impl(const char *src, int entry_is_dir, g->pkg[root].failed = 1; goto request_fail; } + if (products[i].publish != NULL + && (products[i].stage_publish == NULL + || copy_executable_stage(products[i].stage_out, + products[i].stage_publish) != 0)) { + fprintf(stderr, "ww: cannot stage test binary %s\n", + products[i].publish); + goto request_fail; + } if (sep_stage_product_status(&products[i]) != 0) { fprintf(stderr, "ww: cannot stage package-test product\n"); goto request_fail; @@ -6594,6 +6649,10 @@ prepare_transaction: && sep_txn_add(&tx, products[i].stage_out, products[i].out) < 0) goto request_fail; + if (products[i].stage_publish != NULL + && sep_txn_add(&tx, products[i].stage_publish, + products[i].publish) < 0) + goto request_fail; if (products[i].stage_iface != NULL) { char outiface[SEP_ARTIFACT_MAX]; int on = snprintf(outiface, sizeof outiface, "%s.wwi", @@ -6655,6 +6714,7 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity, .identity = root_identity, .test_package = test_package, .status = NULL, + .publish = NULL, .artifact = NULL, .variant = root_variant, .root = -1, @@ -7203,9 +7263,9 @@ do_test(int argc, char **argv) } package_create_output_dir = argv[++i]; } else if (strcmp(argv[i], "--ww-package-test") == 0) { - if (i + 8 >= argc) { + if (i + 9 >= argc) { fprintf(stderr, - "ww test: --ww-package-test needs kind, package, production, internal, external, directory, output, and status\n"); + "ww test: --ww-package-test needs kind, package, production, internal, external, directory, output, publication, and status\n"); return 2; } const char *kind = argv[++i]; @@ -7215,6 +7275,7 @@ do_test(int argc, char **argv) const char *external = argv[++i]; const char *dir = argv[++i]; const char *output = argv[++i]; + const char *publish = argv[++i]; const char *status = argv[++i]; size_t pn = strlen(name); int build_product = strcmp(kind, "build") == 0; @@ -7225,6 +7286,7 @@ do_test(int argc, char **argv) if ((!build_product && !test_product) || pn == 0 || dir[0] == '\0' || output[0] == '\0' + || publish[0] == '\0' || status[0] == '\0' || (has_production && strcmp(production, name) != 0) || (has_internal && strcmp(internal, name) != 0) @@ -7232,9 +7294,12 @@ do_test(int argc, char **argv) || strncmp(external, name, pn) != 0 || strcmp(external + pn, "_test") != 0)) || (build_product && (!has_production - || has_internal || has_external)) + || has_internal || has_external + || strcmp(publish, "-") != 0)) || (test_product && !has_production - && !has_internal && !has_external)) { + && !has_internal && !has_external) + || (test_product && !has_internal && !has_external + && strcmp(publish, "-") != 0)) { fprintf(stderr, "ww test: invalid --ww-package-test product\n"); return 2; @@ -7256,6 +7321,8 @@ do_test(int argc, char **argv) products[nproducts].external_package = has_external ? external : NULL; products[nproducts].status = status; + products[nproducts].publish = strcmp(publish, "-") == 0 + ? NULL : publish; products[nproducts].artifact = NULL; products[nproducts].variant = build_product ? SEP_VARIANT_PRODUCTION : SEP_VARIANT_TEST_MAIN; @@ -7389,7 +7456,7 @@ do_test(int argc, char **argv) fprintf(stderr, "ww test: invalid --ww-package-publish\n"); return 2; } - if (package_create_output_dir != NULL && !package_build) { + if (package_create_output_dir != NULL && nproducts == 0) { fprintf(stderr, "ww test: invalid private directory creation\n"); return 2; } @@ -7460,13 +7527,8 @@ do_test(int argc, char **argv) "ww test: -S needs a single test file\n"); return 2; } - /* -c -o forwards: the coordinator names the single - * package's artifact and rejects a multi-package fan-out. */ - if (outstem[0] && !compileonly) { - fprintf(stderr, - "ww test: -o needs -c for a package target\n"); - return 2; - } + /* The coordinator independently wires -o retention and -c run + * suppression after it has loaded the complete package set. */ /* -w forwards one caller-owned semantic-action store shared by * the complete selected package universe. */ return exec_package_command(argc, argv, src, NULL, NULL, 0, 0); @@ -7488,11 +7550,6 @@ do_test(int argc, char **argv) "ww test: -S needs a single test file\n"); return 2; } - if (outstem[0] && !compileonly) { - fprintf(stderr, - "ww test: -o needs -c for a package target\n"); - return 2; - } if (nproducts != 0) { if (!compileonly) { fprintf(stderr, @@ -7656,10 +7713,6 @@ do_test(int argc, char **argv) fprintf(stderr, "ww test: -S needs a single test file\n"); return 2; } - if (outstem[0] && !compileonly) { - fprintf(stderr, "ww test: -o needs -c for a package target\n"); - return 2; - } if (nproducts != 0) { if (!compileonly) { fprintf(stderr, diff --git a/docs/build-system.md b/docs/build-system.md index 7be2d386..e7b72dce 100644 --- a/docs/build-system.md +++ b/docs/build-system.md @@ -5474,11 +5474,13 @@ as same/external rather than blindly stripping every suffix. The private driver descriptor is an ordered directory record: ``` ---ww-package-test KIND FAMILY PRODUCTION INTERNAL EXTERNAL DIR OUTPUT STATUS +--ww-package-test KIND FAMILY PRODUCTION INTERNAL EXTERNAL DIR OUTPUT PUBLICATION STATUS ``` -Missing action selectors are `-`. One descriptor owns at most one output and -one status. Canonically duplicate products and pairwise output/status/staging +Missing action selectors and absent publication are `-`. `OUTPUT` is the +request-private runnable, while optional `PUBLICATION` is its caller-visible +retained copy. One descriptor owns at most one output, publication, and status. +Canonically duplicate products and pairwise output/publication/status/staging collisions reject before producer execution. Declared names and output stems do not identify products or actions. @@ -6158,6 +6160,154 @@ outer stderr to be empty, direct/raw stderr to remain separate, artifacts and binaries to remain byte-identical, and every temporary or staged path to be cleaned. +### 11.27 Implemented Go-like directory test-binary retention + +Directory-package `ww test` now separates the request-private executable that +the coordinator may run from the optional caller-visible executable it retains. +`-c` means retain without running; `-o` means retain at the requested location +and still run unless `-c` is also present. Output naming, directory fan-out, +duplicate-name preflight, exact null-device discard, executable mode, and +no-test behavior follow the applicable Go 1.26.5 contract. + +#### Pinned Go evidence and direct pre-fix measurements + +The authority is official Go 1.26.5 at commit +`c19862e5f8415b4f24b189d065ed739517c548ba`: + +- `CmdTest.Long` directly states that `-c` writes `pkg.test` in the current + directory and does not run it, while `-o` saves a copy and still runs unless + `-c` is present; a trailing slash or existing directory receives + `pkg.test` + ([`cmd/go/internal/test/test.go`, lines 150–168](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L150-L168)). +- `testNeedBinary` makes nonempty `-o` an independent retention request + ([`test.go`, lines 631–646](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L631-L646)). + `runTest` recognizes an existing directory or trailing separator, rejects a + multi-package non-directory output, and preflights every selected package + for duplicate test-binary names before builder execution, except when the + output is the null device + ([`test.go`, lines 771–804](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L771-L804)). +- `builderTest` takes the ordinary production-only branch when no test files + exist, creating no test link or retained binary + ([`test.go`, lines 1133–1169](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1133-L1169)). + A real test first links into its action object directory; `-c` or binary + retention adds an install action, only `-c` selects the no-op print action, + and the non-`-c` run action depends on the original build action rather than + the installed copy + ([`test.go`, lines 1200–1313](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1200-L1313)). +- `testBinaryName` explicitly uses the final import-path element rather than + the declared package name; its command-line-files exception uses the source + package name + ([`test.go`, lines 2287–2300](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L2287-L2300), + [`cmd/go/internal/load/pkg.go`, lines 1727–1769](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L1727-L1769)). +- `BuildInstallFunc` creates parents and installs a linked executable with mode + `0777` filtered by the process umask + ([`cmd/go/internal/work/exec.go`, lines 1904–2000](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/exec.go#L1904-L2000), + [`cmd/go/internal/work/shell.go`, lines 119–220 and 283–301](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/shell.go#L119-L220)). + On the pinned Unix target, only exact `/dev/null` is the null spelling + ([`cmd/go/internal/base/path.go`, lines 81–92](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/base/path.go#L81-L92)). +- Official `test_compile_multi_pkg.txt` requires missing nested output + directory creation, default current-directory output, rejection of a + non-directory multi-output and duplicate names, `/dev/null` acceptance, and + `-o DIR` retention while tests still run + ([lines 3–38](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_compile_multi_pkg.txt#L3-L38)). + +The separation between saved and executed paths is a conclusion derived from +the pinned action dependencies: the run consumes the temporary link action, +not the install action. Duplicate names are likewise a materialization +collision, not package identity. Go's sources do not assert that a set of +retained binaries is installed as one rollback transaction; WW keeps its +existing stronger request-wide transaction while matching the observable +accepted, rejected, and preserved outputs. No installed host Go behavior was +used as authority. + +Before this slice, direct native measurements of both Cstage and WWstage showed +that single-package `-c -o FILE` retained and did not run, but `-o FILE` +without `-c` exited 2 with `-o needs -c for a package target`; multi-package +`-c -o FILE` exited 2 with the older unconditional fan-out rejection. Default +multi-package `-c` scattered `.test` binaries into their +source directories. Those measurements used the public driver route and +observed exits, diagnostics, files, executable behavior, and stage-equal bytes; +they were not conclusions drawn from WW source. + +#### Coordinator policy and identity boundaries + +`internal/wwpackage.packagecommand` is the sole owner of public output policy. +It resolves the invocation directory, computes each visible +`.test` name, recognizes output-directory and `/dev/null` forms, +rejects non-directory fan-out and duplicate names, omits publication for +no-test products, and schedules execution according to `-c`. A contextual +dotted request uses its exact final component; a local path request uses its +directory leaf as the manifest-free presentation equivalent. Neither becomes +declared-name or physical-directory identity. + +Every actual test product still links to `package.test` below its private plan +root. The private descriptor carries that `OUTPUT` plus an optional absolute +`PUBLICATION`. The Cstage and WWstage drivers implement only this symmetric +mechanism; they do not independently decide names or CLI policy. The +coordinator always executes `OUTPUT`, so `-o` cannot alter executable argv, +cwd, environment, null stdin, combined output, filters, action topology, or +test outcome. + +Visible basename, publication path, private runnable path, declared family, +physical source directory, production/internal/external/recompiled/support/main +variants, symbols, `.wwi`, archives, action identity, and persistence keys +remain distinct. A duplicate basename rejects only the requested +materialization. It never merges, renames, folds, or rekeys either canonical +package. Compiler inputs, exported interfaces, generated main, archive order, +and linked bytes are otherwise unchanged. + +#### Publication, execution, failure, and cleanup + +Without explicit `-o`, `-c` retains each binary in the invocation directory. +An existing directory or a path ending in `/` receives one visible name per +selected package; missing parents are created with `0777` subject to umask. A +non-directory destination accepts exactly one selected package. Exact +`/dev/null` suppresses retained copies, permits duplicate visible names, and +does not suppress execution unless `-c` is also present. A no-test package +performs ordinary production validation, reports `[no tests]`, and creates no +binary or otherwise-unused output directory. Successful compile-only products +are silent, matching Go's no-op print action. + +After linking, the driver copies the private runnable bytes to a distinct +`.new` inode opened with executable mode `0777` subject to umask. Temporary +runnable, retained copy, statuses, changed persistent actions, tool records, +and stamp then enter the existing one-request transaction. All producers and +linkers complete before installation. Any load, compile, assemble, archive, +link, stage, or install failure preserves old retained binaries and persistent +bytes, discards all stages, removes cold scratch, and rolls back only output +prefixes created by that request. Occupied or dangling `.new` paths reject +before tools and are never overwritten. + +Execution begins only after publication commits. Assertion failure, signal, +timeout, or child-setup failure therefore leaves an explicitly retained binary +while retaining the established stdout/stderr result routing and sibling +isolation. Parallel products stage independent runnable/copy pairs; the shared +transaction prevents partial sibling publication and canonical result emission +order remains unchanged. Direct invocation of a retained binary continues to +inherit caller cwd, environment, and separate standard descriptors. + +`-o` can accompany `-w`: unchanged semantic actions are reused, changed source +invalidates the applicable test actions, the always-run link refreshes the +private runnable and retained copy, and the test still runs. This is build +reuse, not a result cache. `-c` retains its established incompatibility with +`-w`; this slice does not invent persistent compile-only ownership. + +No persisted byte schema changed. Build workdir format remains `18`, test +workdir format remains `19`, and semantic storage remains `3`. + +The focused native owners are `compile_artifact_naming` and +`test_binary_publication_transaction` in `test/package/package_test.ww`. Their +Cstage/WWstage matrix covers single/default/directory/nested/multi/null output; +declared-name versus import-leaf naming; executable mode and direct execution; +temporary argv versus retained path; no-test omission; duplicate and +non-directory rejection; occupied stages; serial and parallel sibling +publication; injected late-link rollback over old files and newly created +parents; runtime-failure retention; persistent cold/warm/invalidation behavior; +diagnostic equality; retained binary byte identity; and absence of `.new` +residue. Existing package tests continue to own all action/test variants, +graph identity, output ordering, cwd/environment/stdin, timeout, and broader +transaction behavior. + ## 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 7b5df9d2..0a4cdaf3 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -645,6 +645,27 @@ mixed directories are accepted from their selected test files. A directory with no selected test file validates ordinary production but creates no test support, generated main, link, binary, result, or process. +Every test-bearing directory product links one request-private runnable. +`-c` retains an executable copy and suppresses its execution; `-o` retains a +copy at the named destination and still executes unless `-c` is present. With +no explicit output, `-c` writes `.test` in the invocation +directory. An output ending in `/` or naming an existing directory receives +that basename and may have missing parent directories created. One +non-directory output may name only one package. Multiple packages whose +visible import leaves would produce the same test-binary name reject before +tools or output creation; exact `/dev/null` is the discard exception. Declared +package names, test variants, source filenames, physical directories, output +paths, and retained binary names remain presentation or loader metadata and do +not become canonical package or action identity. + +The retained file is an executable, byte-identical copy of the private +runnable. It joins package artifacts and statuses in the request-wide atomic +publication transaction. Build, link, stage, or install failure preserves old +destinations and removes temporary stages and invocation-created output +prefixes. Test execution starts only after that transaction commits, so a +runtime failure leaves an explicitly retained binary. A no-test product +publishes no binary and does not create a directory solely for one. + When `ww test` executes a directory-owned product, the child process working directory is that product's canonical absolute physical package source directory. Its per-run environment has exactly one effective uppercase `PWD`, diff --git a/docs/test-system-v2.md b/docs/test-system-v2.md index d4cd95ec..67f33b04 100644 --- a/docs/test-system-v2.md +++ b/docs/test-system-v2.md @@ -216,18 +216,28 @@ test binaries concurrently under `os.exec` start/poll supervision (no threads); emission stays strictly in group order, so the byte stream is identical at 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`. With `-c`, it publishes each exact -`.test` directory binary; the first output owns the one -shared cold sepwork containing the command-global action universe. Those become -caller-owned artifacts. `-c -o ` -names that artifact instead of the fixed stem for exactly one directory, -including a combined internal/external directory: -the coordinator rejects a multi-package fan-out ("cannot use -o with -multiple packages", Go's `go test -o` rule), and `-o` without `-c` is -rejected at the driver ("needs -c for a package target") because a plain -run leaves no caller-owned artifact for `-o` to name. Without `-c`, it removes the -temporary binary and scratch with its workspace. The language runtime -owns individual `@test` functions. +7.0s sequential, 2.4s at `-j 4`. + +Directory test binaries are always linked under the coordinator's temporary +product root. `-c` independently requests a caller-visible executable copy and +suppresses execution. `-o` independently requests a copy and still runs the +temporary binary unless `-c` is also present. Without `-o`, `-c` publishes +`.test` in the invocation directory; an output ending in `/` or +naming an existing directory receives one such name per selected package and +missing parents are created. A single non-directory output is legal for one +package only. Multi-package non-directory output and duplicate visible binary +names reject before tools; exact `/dev/null` discards every copy and permits +duplicate names. A package with no selected test source validates production +but publishes nothing and does not create an otherwise unneeded output +directory. Successful `-c` output is silent apart from no-test reporting. + +The retained executable is byte-identical to the temporary runnable and has +executable mode `0777` filtered by the caller's umask, but it is never the path +executed by the coordinator. Publication participates in the driver's one +request-wide transaction: producer, linker, staging, or installation failure +preserves every prior destination and removes stages and newly created output +prefixes. A runtime failure occurs after commit and therefore leaves the saved +binary. The language runtime owns individual `@test` functions. Every actually executed directory product gives its single generated binary the product's canonical absolute physical source directory as child cwd. A @@ -409,16 +419,19 @@ bounded-memory Cstage/WWstage allocation-failure parity across the complete combined package-test graph, under independently discovered ceilings supplied by the repository-built `sep-limitexec` helper. -`ww build`, an explicit single-file `ww test -o `, and each successful -directory-package `ww test -c` build publish `.sepwork` as a caller-owned -artifact directory. The driver acquires it with one fresh `mkdir` and refuses -an existing path; it never clears a collision. A caller keeps only the exact -artifacts it observes and removes that exact tree on every later success or -failure. `ww run` and no-output single-file `ww test` use driver-owned scratch -instead; both driver stages place that scratch and their temporary executable -beneath one freshly acquired directory, remove both after every build result, -and make cleanup failure fail the command. Make recipes build driver-produced -tools in invocation-owned directories and apply the same exact cleanup rule. +`ww build` and an explicit single-file `ww test -o ` publish +`.sepwork` as a caller-owned artifact directory. The driver acquires it +with one fresh `mkdir` and refuses an existing path; it never clears a +collision. A caller keeps only the exact artifacts it observes and removes +that exact tree on every later success or failure. Directory-package test +plans instead keep their cold semantic-action scratch and runnable binary +inside the coordinator's temporary root; only the optional retained executable +escapes through the transaction above. `ww run` and no-output single-file +`ww test` use driver-owned scratch instead; both driver stages place that +scratch and their temporary executable beneath one freshly acquired directory, +remove both after every build result, and make cleanup failure fail the +command. Make recipes build driver-produced tools in invocation-owned +directories and apply the same exact cleanup rule. `ww build -w DIR` and single-file `ww test -w DIR` replace that scratch with a caller-owned persistent package-artifact workdir: for these direct routes the @@ -561,8 +574,8 @@ timeout policy in this architecture. ## Open driver work -None; the package-level `-o` contract (the last carried bullet) landed as -`-c -o ` for exactly one package. +None; directory-package `-c` and `-o` now have the applicable Go 1.26.5 +retention, naming, fan-out, execution, and publication behavior. ## Validation policy diff --git a/internal/wwpackage/package.ww b/internal/wwpackage/package.ww index fb709825..fc5e2374 100644 --- a/internal/wwpackage/package.ww +++ b/internal/wwpackage/package.ww @@ -24,6 +24,8 @@ type pkgfolder = struct { type pkggroup = struct { dir: str, pkg: str, + testname: str, + publish: str, prodpkg: str, samepkg: str, externalpkg: str, @@ -467,7 +469,7 @@ fn pkgusage() void = { pkgput(os.STDERR_FILENO, " *_test.ww is the sole test-source form; @test elsewhere is rejected\n"); pkgput(os.STDERR_FILENO, - " -c retains the compiled package binaries; -j N runs up to N build or test processes at once\n"); + " -c retains without running; -o retains and still runs unless -c is present\n"); pkgput(os.STDERR_FILENO, " -w DIR is one persistent semantic-action store shared by the selected packages\n"); }; @@ -616,6 +618,28 @@ fn pkgbase(path: str) str = { return path; }; +// Directory test binaries are presentation artifacts. Their Go-like visible +// basename comes from the selected canonical import spelling, never from the +// declared package name or any test variant. A physical root is the fallback +// when the request has no explicit logical identity. +fn pkgimportbase(path: str) str = { + let i: i32 = path.len - 1; + for (i >= 0) { + if (path[i] == '.') { + let r: str; + r.ptr = path.ptr + ((i + 1): u64); + r.len = path.len - i - 1; + return r; + }; + i -= 1; + }; + return path; +}; + +fn pkgisabs(path: str) bool = { + return path.len != 0 && path[0] == '/'; +}; + fn pkgmodeis(m: os.mode, want: os.mode) bool = { return (((m: u32) & 61440u32) == (want: u32)); }; @@ -1319,7 +1343,7 @@ fn pkgemitfile(path: str, fd: i32) bool = { fn pkgsetplanpaths(p: *pkgplan, groups: []pkggroup, root: str, index: i32, compileonly: bool, buildonly: bool, outname: str, outputdir: bool, - workroot: str) bool = { + createdir: str, workroot: str) bool = { let num: str = strconv.i32tos(index, strconv.base.DEC); if (!pkgstring(&p.root, root, "/plan-", num)) { return false; }; if (!pkgmakedir(p.root)) { @@ -1330,8 +1354,7 @@ fn pkgsetplanpaths(p: *pkgplan, groups: []pkggroup, root: str, index: i32, // spelling or traversal prefix must never select another cache container. // The private driver creates it only after graph preflight succeeds. p.workdir = workroot; - p.outputdir = ""; - if (outputdir) { p.outputdir = outname; }; + p.outputdir = createdir; if (!pkgstring(&p.buildout, p.root, "/build.stdout") || !pkgstring(&p.builderr, p.root, "/build.stderr")) { return false; }; let i: i32 = p.start; @@ -1349,12 +1372,6 @@ fn pkgsetplanpaths(p: *pkgplan, groups: []pkggroup, root: str, index: i32, if (!pkgjoinpath(outname, pkgbase(g.dir), &g.bin)) { return false; }; } else if (outname.len != 0 && !outputdir) { g.bin = outname; } else if (!pkgstring(&g.bin, g.root, "/package.build")) { return false; }; - } else if (compileonly) { - if (outname.len != 0) { - g.bin = outname; - } else { - if (!pkgstring(&g.bin, g.dir, "/", g.pkg, ".test")) { return false; }; - }; } else { if (!pkgstring(&g.bin, g.root, "/package.test")) { return false; }; }; @@ -1400,11 +1417,11 @@ fn pkgstartbuild(p: *pkgplan, groups: []pkggroup, builder: str, includes: []str, libdirs: []str, libs: []str, h: *exec.process) bool = { let nproducts: i32 = p.end - p.start; let capacity: i32 = 16; - if (nproducts < 0 || nproducts > (PKG_COUNT_MAX - capacity) / 9) { + if (nproducts < 0 || nproducts > (PKG_COUNT_MAX - capacity) / 10) { pkgputln(os.STDERR_FILENO, "wwtest package: package graph is too large"); return false; }; - capacity += nproducts * 9; + capacity += nproducts * 10; if (includes.len > (PKG_COUNT_MAX - capacity) / 2) { pkgputln(os.STDERR_FILENO, "wwtest package: package graph is too large"); return false; @@ -1459,6 +1476,8 @@ fn pkgstartbuild(p: *pkgplan, groups: []pkggroup, builder: str, includes: []str, else { append(ba, "-"); }; append(ba, g.dir); append(ba, g.bin); + if (g.publish.len != 0) { append(ba, g.publish); } + else { append(ba, "-"); }; append(ba, g.buildok); i += 1; }; @@ -1572,10 +1591,9 @@ fn pkgemitgroup(g: *pkggroup, compileonly: bool) bool = { return true; }; if (compileonly) { - pkgput(os.STDOUT_FILENO, "built "); - pkglabel(g); - pkgput(os.STDOUT_FILENO, " -> "); - pkgputln(os.STDOUT_FILENO, g.bin); + // Go's compile-only print action is a nop after the retained binary + // install completes. Build and publication failures still diagnose on + // stderr through the plan result above. return true; }; if (g.runstartfailed) { return false; }; @@ -1835,14 +1853,9 @@ export fn packagecommand(args: []str) int = { pkgusage(); return 2; }; - // A non-compile run leaves no caller-owned artifact for -o to name. - if (outname.len != 0 && !compileonly) { - pkgputln(os.STDERR_FILENO, "wwtest package: -o needs -c"); - return 2; - }; - // -c publishes caller-owned sepwork artifacts; mixing that - // contract with a persistent workdir is unwired — reject rather - // than guess which tree the caller owns. + // -c suppresses execution and requests a caller-visible binary. Keep it + // separate from -w until compile-only persistent-action ownership is wired; + // -o without -c already permits a retained copy beside a persistent store. if (workroot.len != 0 && compileonly && !buildonly) { pkgputln(os.STDERR_FILENO, "wwtest package: -w conflicts with -c"); @@ -2034,6 +2047,8 @@ export fn packagecommand(args: []str) int = { let g: pkggroup; g.dir = f.path; g.pkg = f.prodpkg; + g.testname = ""; + g.publish = ""; g.prodpkg = f.prodpkg; g.samepkg = ""; g.externalpkg = ""; @@ -2157,6 +2172,8 @@ export fn packagecommand(args: []str) int = { g.dir = f.path; g.pkg = family; if (g.pkg.len == 0) { g.pkg = srcs[f.start].pkg; }; + g.testname = ""; + g.publish = ""; g.prodpkg = f.prodpkg; g.samepkg = samepkg; g.externalpkg = externalpkg; @@ -2240,9 +2257,89 @@ export fn packagecommand(args: []str) int = { i += 1; }; }; - // A non-directory caller-owned name cannot fan out. A directory output - // publishes each selected command under its canonical directory basename. - if (outname.len != 0 && !outputdir && groups.len > 1) { + // Go's test binary is always linked into request-private storage. -c and + // -o independently request a caller-visible executable copy; only -c + // suppresses execution. Visible names are import-leaf metadata and never + // action, package, variant, symbol, or persistence identity. + let testretain: bool = !buildonly && (compileonly || explicitout); + let testnull: bool = !buildonly && explicitout + && strings.compare(outname, "/dev/null") == 0; + let testoutdir: bool = !buildonly && explicitout && !testnull + && pkgoutputdir(outname); + let invocationdir: str = ""; + if (testretain) { + let cwdoom: bool = false; + if (!pkgcanonicaldir(".", &invocationdir, &cwdoom)) { + if (!cwdoom) { + pkgputln(os.STDERR_FILENO, + "wwtest package: cannot determine invocation directory"); + }; + return 1; + }; + }; + i = 0; + for (i < groups.len) { + let leaf: str = pkgbase(groups[i].dir); + if (roots.len == 1 && !anyrecurse && requestidentity.len != 0) { + leaf = pkgimportbase(requestidentity); + }; + if (!pkgstring(&groups[i].testname, leaf, ".test")) { return 1; }; + groups[i].publish = ""; + if (testretain && !groups[i].notests && !testnull) { + if (!explicitout) { + if (!pkgjoinpath(invocationdir, groups[i].testname, + &groups[i].publish)) { return 1; }; + } else if (testoutdir) { + let targetdir: str = outname; + if (!pkgisabs(outname) + && !pkgjoinpath(invocationdir, outname, &targetdir)) { + return 1; + }; + if (!pkgjoinpath(targetdir, groups[i].testname, + &groups[i].publish)) { return 1; }; + } else if (pkgisabs(outname)) { + groups[i].publish = outname; + } else if (!pkgjoinpath(invocationdir, outname, + &groups[i].publish)) { return 1; }; + }; + i += 1; + }; + if (!buildonly && explicitout && groups.len > 1 + && !testnull && !testoutdir) { + pkgputln(os.STDERR_FILENO, + "ww test: with multiple packages, -o must refer to a directory or /dev/null"); + return 1; + }; + if (!buildonly && groups.len > 1 && testretain && !testnull) { + i = 0; + for (i < groups.len) { + let j: i32 = 0; + for (j < i) { + if (strings.compare(groups[j].testname, + groups[i].testname) == 0) { + pkgput(os.STDERR_FILENO, + "ww test: cannot write test binary "); + pkgput(os.STDERR_FILENO, groups[i].testname); + pkgputln(os.STDERR_FILENO, + " for multiple packages:"); + let k: i32 = 0; + for (k < groups.len) { + if (strings.compare(groups[k].testname, + groups[i].testname) == 0) { + pkgputln(os.STDERR_FILENO, groups[k].dir); + }; + k += 1; + }; + return 1; + }; + j += 1; + }; + i += 1; + }; + }; + // A non-directory caller-owned build name cannot fan out. A directory + // build output publishes each selected command under its directory leaf. + if (buildonly && outname.len != 0 && !outputdir && groups.len > 1) { pkgputln(os.STDERR_FILENO, "wwtest package: cannot use -o with multiple packages"); return 2; @@ -2277,13 +2374,27 @@ export fn packagecommand(args: []str) int = { && strings.compare(groups[0].pkg, "main") != 0; plan.emitasm = emitasm; append(plans, plan); + let createdir: str = ""; + if (buildonly && outputdir) { + createdir = outname; + } else if (!buildonly && testretain && !testnull) { + i = 0; + for (i < groups.len) { + if (groups[i].publish.len != 0) { + createdir = pkgdirname(groups[i].publish); + break; + }; + i += 1; + }; + }; let tmproot: str = temp.dir(); let failed: i32 = 0; i = 0; for (i < plans.len) { if (!pkgsetplanpaths(&plans[i], groups, tmproot, i, - compileonly, buildonly, outname, outputdir, workroot)) { + compileonly, buildonly, outname, outputdir, createdir, + workroot)) { if (!pkgremoveall(tmproot)) { pkgput(os.STDERR_FILENO, "wwtest package: cleanup failed; retained "); diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index c2a53ace..fc26e22e 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -1508,6 +1508,7 @@ type sepproduct = struct { internalpackage: *u8, externalpackage: *u8, status: *u8, + publish: *u8, artifact: *u8, variant: i32, directoryproduct: bool, @@ -1520,6 +1521,7 @@ type sepproduct = struct { pxtest: i32, support: i32, stageout: *u8, + stagepublish: *u8, stageiface: *u8, stagestatus: *u8, }; @@ -5914,6 +5916,32 @@ fn copyfilestage(src: *u8, dst: *u8) i32 = { return 0; }; +// BuildInstallFunc's linked-executable mode is 0777 filtered by umask. The +// retained stage owns a distinct inode but exactly the temporary runnable's +// bytes; the request transaction publishes them together below. +fn copyexecutablestage(src: *u8, dst: *u8) i32 = { + let in: i32 = os.open(pathstr(src), os.flag.RDONLY, 0i32); + if (in < 0) { return -1; }; + let out: i32 = os.open(pathstr(dst), + os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 511i32); + if (out < 0) { os.close(in); return -1; }; + let buf: [65536]u8; + let bad: bool = false; + for (!bad) { + let n: i64 = os.read(in, &buf[0], 65536u64); + if (n < 0) { bad = true; break; }; + if (n == 0) { break; }; + match (os.writeall(out, &buf[0], n: u64)) { + case let wrote: i64 => { if (wrote != n) { bad = true; }; }; + case let e: os.oserror => bad = true; + }; + }; + if (os.close(in) != 0) { bad = true; }; + if (os.close(out) != 0) { bad = true; }; + if (bad) { os.remove(pathstr(dst)); return -1; }; + return 0; +}; + fn sepproductstagepath(dst: *u8) *u8 = { let path: *u8 = sepappendlit(dst, ".new"); if (path != nil && cstrlen(path) + 1u64 > os.PATH_MAX: u64) { @@ -5945,12 +5973,16 @@ fn sepproductpathsoverlap(a: *u8, b: *u8) bool = { }; fn sepvalidateproductpathpair(a: *sepproduct, b: *sepproduct) i32 = { - let ap: []*u8 = [nil, nil, a.stagestatus, a.stageout, a.stageiface]; - let bp: []*u8 = [nil, nil, b.stagestatus, b.stageout, b.stageiface]; + let ap: []*u8 = [nil, nil, nil, a.stagestatus, a.stageout, + a.stagepublish, a.stageiface]; + let bp: []*u8 = [nil, nil, nil, b.stagestatus, b.stageout, + b.stagepublish, b.stageiface]; if (a.stagestatus != nil) { ap[0] = a.status; }; if (a.stageout != nil) { ap[1] = a.out; }; + if (a.stagepublish != nil) { ap[2] = a.publish; }; if (b.stagestatus != nil) { bp[0] = b.status; }; if (b.stageout != nil) { bp[1] = b.out; }; + if (b.stagepublish != nil) { bp[2] = b.publish; }; let i: i32 = 0; for (i < ap.len) { if (ap[i] != nil) { @@ -6037,6 +6069,11 @@ fn sepvalidaterequeststaging(g: *sepgraph, scratch: *u8, warm: bool, products[i].stagestatus, products[i].status); if (products[i].stagestatus == nil) { return -1; }; }; + if (products[i].publish != nil && !products[i].notests) { + products[i].stagepublish = sepprepareproductstage( + products[i].stagepublish, products[i].publish); + if (products[i].stagepublish == nil) { return -1; }; + }; if (emitasm == 0) { let ownsoutput: bool = false; if (rootpackage) { ownsoutput = publishpackage != 0; } @@ -6413,7 +6450,8 @@ fn sepdiscardrequeststaging(g: *sepgraph, scratch: *u8, warm: bool, let producti: i32 = 0; for (producti < nproducts) { let paths: []*u8 = [products[producti].stageout, - products[producti].stageiface, products[producti].stagestatus]; + products[producti].stagepublish, products[producti].stageiface, + products[producti].stagestatus]; let si: i32 = 0; for (si < paths.len) { if (paths[si] != nil) { @@ -6564,8 +6602,8 @@ fn sepfinishfail(entries: *[]septxnentry, n: i32) i32 = { fn sepfreeproductstaging(products: *sepproduct, nproducts: i32) void = { let i: i32 = 0; for (i < nproducts) { - let paths: []*u8 = [products[i].stageout, products[i].stageiface, - products[i].stagestatus]; + let paths: []*u8 = [products[i].stageout, products[i].stagepublish, + products[i].stageiface, products[i].stagestatus]; let k: i32 = 0; for (k < paths.len) { if (paths[k] != nil) { @@ -6574,6 +6612,7 @@ fn sepfreeproductstaging(products: *sepproduct, nproducts: i32) void = { k += 1; }; products[i].stageout = nil; + products[i].stagepublish = nil; products[i].stageiface = nil; products[i].stagestatus = nil; i += 1; @@ -6779,6 +6818,14 @@ fn sepfinishrequest(selfdir: *u8, l6: *u8, c6: *u8, a6: *u8, g.pkg[root].failed = true; return sepfinishfail(&entries, ntxn); }; + if (products[producti].publish != nil + && (products[producti].stagepublish == nil + || copyexecutablestage(products[producti].stageout, + products[producti].stagepublish) != 0)) { + cerrpath("ww: cannot stage test binary ", + products[producti].publish, "\n"); + return sepfinishfail(&entries, ntxn); + }; if (sepstageproductstatus(&products[producti]) != 0) { cerr("ww: cannot stage package-test product\n"); return sepfinishfail(&entries, ntxn); @@ -6860,6 +6907,11 @@ fn sepfinishrequest(selfdir: *u8, l6: *u8, c6: *u8, a6: *u8, products[producti].out)) { return sepfinishfail(&entries, ntxn); }; + if (products[producti].stagepublish != nil + && !septxnadd(&entries, &ntxn, products[producti].stagepublish, + products[producti].publish)) { + return sepfinishfail(&entries, ntxn); + }; if (products[producti].stageiface != nil) { let outiface: *u8 = sepappendlit(products[producti].out, ".wwi"); if (outiface == nil @@ -7091,6 +7143,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, products[producti].ptest = -1; products[producti].pxtest = -1; products[producti].stageout = nil; + products[producti].stagepublish = nil; products[producti].stageiface = nil; products[producti].stagestatus = nil; producti += 1; @@ -7443,6 +7496,10 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, && validatecommandoutputpath(products[producti].out) < 0) { return 1; }; + if (products[producti].publish != nil + && validatecommandoutputpath(products[producti].publish) < 0) { + return 1; + }; if (products[producti].status != nil && validatecommandoutputpath(products[producti].status) < 0) { return 1; @@ -7563,10 +7620,17 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, let createdwork: sepcreateddirs; createdoutput.n = 0; createdwork.n = 0; + let outputmode: i32 = 448; + if (istest != 0) { outputmode = 511; }; if (createoutputdir != nil - && sepmkdirsrecord(createoutputdir, 448, &createdoutput) != 0) { - cerrpath("ww: cannot create build output directory ", - createoutputdir, "\n"); + && sepmkdirsrecord(createoutputdir, outputmode, &createdoutput) != 0) { + if (istest != 0) { + cerrpath("ww: cannot create test output directory ", + createoutputdir, "\n"); + } else { + cerrpath("ww: cannot create build output directory ", + createoutputdir, "\n"); + }; return 1; }; if (warm && !workdirexists) { @@ -8095,6 +8159,7 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, product.internalpackage = nil; product.externalpackage = nil; product.status = nil; + product.publish = nil; product.artifact = nil; if (entryisdir == 0) { product.artifact = "__root\0".ptr; @@ -8910,8 +8975,8 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { continue; }; if (cstreqlit(p, "--ww-package-test")) { - if (i + 8 >= argc) { - cerr("ww test: --ww-package-test needs kind, package, production, internal, external, directory, output, and status\n"); + if (i + 9 >= argc) { + cerr("ww test: --ww-package-test needs kind, package, production, internal, external, directory, output, publication, and status\n"); return 2; }; let kind: *u8 = argv[i + 1]; @@ -8921,7 +8986,8 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let external: *u8 = argv[i + 5]; let dir: *u8 = argv[i + 6]; let output: *u8 = argv[i + 7]; - let status: *u8 = argv[i + 8]; + let publish: *u8 = argv[i + 8]; + let status: *u8 = argv[i + 9]; let pn: u64 = cstrlen(name); let buildproduct: bool = cstreqlit(kind, "build"); let testproduct: bool = cstreqlit(kind, "test"); @@ -8931,6 +8997,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { if ((!buildproduct && !testproduct) || pn == 0u64 || dir[0u64] == 0u8 || output[0u64] == 0u8 + || publish[0u64] == 0u8 || status[0u64] == 0u8 || (hasproduction && !cstreq(production, name)) || (hasinternal && !cstreq(internal, name)) @@ -8938,9 +9005,12 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { || !strings.hasprefix(pathstr(external), pathstr(name)) || !cstrendswithlit(external, "_test"))) || (buildproduct && (!hasproduction - || hasinternal || hasexternal)) + || hasinternal || hasexternal + || !cstreqlit(publish, "-"))) || (testproduct && !hasproduction - && !hasinternal && !hasexternal)) { + && !hasinternal && !hasexternal) + || (testproduct && !hasinternal && !hasexternal + && !cstreqlit(publish, "-"))) { cerr("ww test: invalid --ww-package-test product\n"); return 2; }; @@ -8956,6 +9026,8 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { product.externalpackage = nil; if (hasexternal) { product.externalpackage = external; }; product.status = status; + product.publish = nil; + if (!cstreqlit(publish, "-")) { product.publish = publish; }; product.artifact = nil; product.variant = SEP_VARIANT_TEST_MAIN; if (buildproduct) { product.variant = SEP_VARIANT_PRODUCTION; }; @@ -8974,7 +9046,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { if (!sepreserveproducts(&products, products.len + 1)) { return 1; }; append(products, product); - i += 9; + i += 10; continue; }; if (p[1u64] == 73u8) { // '-I' @@ -9133,7 +9205,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { cerr("ww test: invalid --ww-package-publish\n"); return 2; }; - if (packagecreateoutputdir != nil && !packagebuild) { + if (packagecreateoutputdir != nil && products.len == 0) { cerr("ww test: invalid private directory creation\n"); return 2; }; @@ -9220,12 +9292,8 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { cerr("ww test: -S needs a single test file\n"); return 2; }; - // -c -o forwards: the coordinator names the single - // package's artifact and rejects a multi-package fan-out. - if (outstem != nil && compileonly == 0) { - cerr("ww test: -o needs -c for a package target\n"); - return 2; - }; + // The coordinator independently wires -o retention and -c run + // suppression after loading the complete package set. // -w forwards one caller-owned semantic-action store shared by // the complete selected package universe. return execpackagetests(selfdir, argv, argc, start, @@ -9273,10 +9341,6 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { cerr("ww test: -S needs a single test file\n"); return 2; }; - if (outstem != nil && compileonly == 0) { - cerr("ww test: -o needs -c for a package target\n"); - return 2; - }; if (products.len != 0) { if (compileonly == 0) { cerr("ww test: package-test products need -c\n"); diff --git a/test/package/package_test.ww b/test/package/package_test.ww index 1bf9a9bb..4fe6d3bc 100644 --- a/test/package/package_test.ww +++ b/test/package/package_test.ww @@ -1159,12 +1159,12 @@ fn cwdwritedata(dir: str, label: str) void = { caller, stdinpath, (90i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); - let defaultbin: str = strings.concat(p2, "/p2.test"); + let defaultbin: str = strings.concat(caller, "/p2.test"); assert(os.exists(defaultbin)); + assert(!os.exists(strings.concat(p2, "/p2.test"))); assert(!os.exists(strings.concat(p2, "/created.txt"))); assert(!has(out.stdout, "dep-init") && !has(out.stdout, "p2-external")); assert(os.remove(defaultbin) == 0); - clean(strings.concat(defaultbin, ".sepwork")); assert(os.remove(strings.concat(p1, "/created.txt")) == 0); let p1cbin: str = strings.concat(root, "/p1-c.test"); @@ -1814,6 +1814,12 @@ fn cwdwritedata(dir: str, label: str) void = { "toexternal_test", "tomixed"]; let shapetargetsecond: []str = ["", "shapeexternal_test", "", "", "tomixed_test"]; + let shapeproductionselector: []str = ["shapeinternal", "shapeexternal", + "-", "-", "-"]; + let shapeinternalselector: []str = ["shapeinternal", "-", "tosame", + "-", "tomixed"]; + let shapeexternalselector: []str = ["-", "shapeexternal_test", "-", + "toexternal_test", "tomixed_test"]; let shapecompilerrefs: []str = ["", "", "", "", ""]; let shapelinkerrefs: []str = ["", "", "", "", ""]; let shapemainrefs: []str = ["", "", "", "", ""]; @@ -1827,6 +1833,7 @@ fn cwdwritedata(dir: str, label: str) void = { let shapebin: str = strings.concat(root, "/shape-product-", boundarypkgname(sj), ".test"); let shapework: str = strings.concat(shapebin, ".sepwork"); + let shapestatus: str = strings.concat(shapebin, ".status"); let ctracepath: str = strings.concat(root, "/shape-compiler-", boundarypkgname(sj), ".trace"); let ltracepath: str = strings.concat(root, "/shape-linker-", @@ -1835,6 +1842,7 @@ fn cwdwritedata(dir: str, label: str) void = { writefile(ltracepath, ""); let stagei: i32 = 0; for (stagei < stages.len) { + assert(os.mkdir(shapework, 448i32) == 0); if (stagei != 0) { rewritefile(ctracepath, ""); rewritefile(ltracepath, ""); @@ -1864,8 +1872,12 @@ fn cwdwritedata(dir: str, label: str) void = { driver(shapecompilers[stagei]))); append(env, strings.concat("WW_SHAPE_REAL_LINKER=", driver(shapelinkers[stagei]))); - let shapeav: []str = [driver(stages[stagei]), "test", "-c", "-o", - shapebin, "-I", root, shapes[index]]; + let shapeav: []str = [driver(stages[stagei]), "test", "-c", "-w", + shapework, "-I", root, + "--ww-package-test", "test", shapefamilies[index], + shapeproductionselector[sj], shapeinternalselector[sj], + shapeexternalselector[sj], shapes[index], shapebin, "-", + shapestatus, shapes[index]]; runcommandenv(root, strings.concat("shape-structure-", stages[stagei], "-", boundarypkgname(sj)), shapeav, env, (120i64 * (time.second: i64)): time.duration, &out); @@ -1876,9 +1888,9 @@ fn cwdwritedata(dir: str, label: str) void = { shapefamilies[index], ".test"))); let ctrace: str = readfile(ctracepath); let ltrace: str = readfile(ltracepath); - assert(occurrences(ctrace, "-test-main.unit.ww") == 1); + assert(occurrences(ctrace, "-test-main.unit.new") == 1); let mainline: str = linecontaining(ctrace, - strings.concat("/", shapemains[sj], ".unit.ww")); + strings.concat("/", shapemains[sj], ".unit.new")); let targetcount: i32 = 1; if (shapetargetsecond[sj].len != 0) { targetcount = 2; }; assert(occurrences(mainline, "--test-target-package") @@ -1932,6 +1944,7 @@ fn cwdwritedata(dir: str, label: str) void = { }; clean(shapework); clean(shapebin); + clean(shapestatus); stagei += 1; }; sj += 1; @@ -2191,37 +2204,39 @@ fn cwdwritedata(dir: str, label: str) void = { assert(same(outc.stderr, outw.stderr)); assert(has(outc.stderr, "usage: wwtest package")); - let cc: []str = [driver("ww"), "test", "-c", "-I", root, route]; + let cwork: str = strings.concat(root, "/compile-c-work"); + let wwork: str = strings.concat(root, "/compile-ww-work"); + let cbinarypath: str = strings.concat(root, "/route-c.test"); + let wbinarypath: str = strings.concat(root, "/route-ww.test"); + let cc: []str = [driver("ww"), "test", "-w", cwork, "-o", + cbinarypath, "-I", root, route]; runcommand(root, "compile-c", cc, (30i64 * (time.second: i64)): time.duration, &outc); expectexit(&outc, 0); - assert(has(outc.stdout, strings.concat(" -> ", route, "/route.test\n"))); - assert(occurrences(outc.stdout, "built ") == 1); - let cwhite: str = readfile(strings.concat(route, - "/route.test.sepwork/route-internal-test.s")); - let cexternal: str = readfile(strings.concat(route, - "/route.test.sepwork/route_test-external-test.s")); - let cmain: str = readfile(strings.concat(route, - "/route.test.sepwork/route-test-main.s")); - let cbinary: str = readfile(strings.concat(route, "/route.test")); - // The explicit package `-c` outputs are caller-owned artifacts. Release - // the shared C-stage tree before asking the WW driver to acquire the same - // stem; the driver never deletes a pre-existing `.sepwork` path. - clean(strings.concat(route, "/route.test.sepwork")); + assert(has(outc.stdout, "route.white ... ok\n")); + assert(has(outc.stdout, "route.external ... ok\n")); + let cwhite: str = readfile(strings.concat(cwork, + "/route-internal-test.s")); + let cexternal: str = readfile(strings.concat(cwork, + "/route_test-external-test.s")); + let cmain: str = readfile(strings.concat(cwork, + "/route-test-main.s")); + let cbinary: str = readfile(cbinarypath); - let wc: []str = [driver("ww_ww"), "test", "-c", "-I", root, route]; + let wc: []str = [driver("ww_ww"), "test", "-w", wwork, "-o", + wbinarypath, "-I", root, route]; runcommand(root, "compile-ww", wc, (30i64 * (time.second: i64)): time.duration, &outw); expectexit(&outw, 0); assert(same(outc.stdout, outw.stdout)); assert(same(outc.stderr, outw.stderr)); - assert(same(cwhite, readfile(strings.concat(route, - "/route.test.sepwork/route-internal-test.s")))); - assert(same(cexternal, readfile(strings.concat(route, - "/route.test.sepwork/route_test-external-test.s")))); - assert(same(cmain, readfile(strings.concat(route, - "/route.test.sepwork/route-test-main.s")))); - assert(same(cbinary, readfile(strings.concat(route, "/route.test")))); + assert(same(cwhite, readfile(strings.concat(wwork, + "/route-internal-test.s")))); + assert(same(cexternal, readfile(strings.concat(wwork, + "/route_test-external-test.s")))); + assert(same(cmain, readfile(strings.concat(wwork, + "/route-test-main.s")))); + assert(same(cbinary, readfile(wbinarypath))); clean(root); }; @@ -2464,7 +2479,7 @@ fn cwdwritedata(dir: str, label: str) void = { let av: []str = [driver(stages[si]), "test", "-c", "-w", works[si], "-I", root, "--ww-package-test", "test", "pkg", "pkg", "pkg", - "pkg_test", pkg, bins[si], statuses[si], pkg]; + "pkg_test", pkg, bins[si], "-", statuses[si], pkg]; runcommandenv(root, strings.concat("graph-cold-", stages[si]), av, env, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -2728,7 +2743,7 @@ fn cwdwritedata(dir: str, label: str) void = { driver(linkers[si]))); let av: []str = [driver(stages[si]), "test", "-c", "-w", works[si], "-I", root, "--ww-package-test", "test", "pkg", "pkg", "pkg", - "pkg_test", pkg, bins[si], statuses[si], pkg]; + "pkg_test", pkg, bins[si], "-", statuses[si], pkg]; runcommandenv(root, strings.concat("graph-body-", stages[si]), av, tracedenv, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -2807,7 +2822,7 @@ fn cwdwritedata(dir: str, label: str) void = { driver(linkers[si]))); let av: []str = [driver(stages[si]), "test", "-c", "-w", works[si], "-I", root, "--ww-package-test", "test", "pkg", "pkg", "pkg", - "pkg_test", pkg, bins[si], statuses[si], pkg]; + "pkg_test", pkg, bins[si], "-", statuses[si], pkg]; runcommandenv(root, strings.concat("graph-external-", stages[si]), av, tracedenv, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -2878,7 +2893,7 @@ fn cwdwritedata(dir: str, label: str) void = { driver(linkers[si]))); let av: []str = [driver(stages[si]), "test", "-c", "-w", works[si], "-I", root, "--ww-package-test", "test", "pkg", "pkg", "pkg", - "pkg_test", pkg, bins[si], statuses[si], pkg]; + "pkg_test", pkg, bins[si], "-", statuses[si], pkg]; runcommandenv(root, strings.concat("graph-export-", stages[si]), av, tracedenv, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -2981,7 +2996,7 @@ fn cwdwritedata(dir: str, label: str) void = { driver(linkers[si]))); let av: []str = [driver(stages[si]), "test", "-c", "-w", works[si], "-I", root, "--ww-package-test", "test", "pkg", "pkg", "pkg", - "-", pkg, bins[si], statuses[si], pkg]; + "-", pkg, bins[si], "-", statuses[si], pkg]; runcommandenv(root, strings.concat("graph-remove-external-", stages[si]), av, env, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -3106,7 +3121,7 @@ fn cwdwritedata(dir: str, label: str) void = { driver(linkers[si]))); let av: []str = [driver(stages[si]), "test", "-c", "-w", works[si], "-I", root, "--ww-package-test", "test", "pkg", "pkg", "pkg", - "pkg_test", pkg, bins[si], statuses[si], pkg]; + "pkg_test", pkg, bins[si], "-", statuses[si], pkg]; runcommandenv(root, strings.concat("graph-readd-external-", stages[si]), av, env, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -3191,7 +3206,7 @@ fn cwdwritedata(dir: str, label: str) void = { runcommand(root, strings.concat("graph-direct-product-", stages[si]), directav, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); - assert(occurrences(out.stdout, "built ") == 1); + assert(out.stdout.len == 0); assert(os.exists(directbin)); if (si == 0) { directbytes = readfile(directbin); @@ -3376,19 +3391,19 @@ fn cwdwritedata(dir: str, label: str) void = { let forward: []str = [driver(stages[si]), "test", "-c", "-w", works[si], "-I", suite, "-I", root, "--ww-package-test", "test", "alpha", "alpha", "alpha", - "alpha_test", alpha, bins[0], statuses[0], + "alpha_test", alpha, bins[0], "-", statuses[0], "--ww-package-test", "test", "beta", "beta", "beta", - "beta_test", beta, bins[1], statuses[1], + "beta_test", beta, bins[1], "-", statuses[1], "--ww-package-test", "test", "gamma", "gamma", "-", "-", - gamma, gammabin, statuses[2], alpha]; + gamma, gammabin, "-", statuses[2], alpha]; let reverse: []str = [driver(stages[si]), "test", "-c", "-w", works[si], "-I", suite, "-I", root, "--ww-package-test", "test", "gamma", "gamma", "-", "-", - gamma, gammabin, statuses[2], + gamma, gammabin, "-", statuses[2], "--ww-package-test", "test", "beta", "beta", "beta", - "beta_test", beta, bins[1], statuses[1], + "beta_test", beta, bins[1], "-", statuses[1], "--ww-package-test", "test", "alpha", "alpha", "alpha", - "alpha_test", alpha, bins[0], statuses[0], alpha]; + "alpha_test", alpha, bins[0], "-", statuses[0], alpha]; if (si == 0) { runcommandenv(root, "multi-forward-c", forward, env, (180i64 * (time.second: i64)): time.duration, &out); @@ -3531,14 +3546,11 @@ fn cwdwritedata(dir: str, label: str) void = { si += 1; }; - // Public root order is presentation-only: two directories publish one - // binary each, while the selected no-test directory publishes no binary. - let publicalpha: str = strings.concat(alpha, "/alpha.test"); - let publicbeta: str = strings.concat(beta, "/beta.test"); - let publicgamma: str = strings.concat(gamma, "/gamma.test"); - let publicworks: []str = [strings.concat(publicalpha, ".sepwork"), - strings.concat(publicbeta, ".sepwork"), - strings.concat(publicgamma, ".sepwork")]; + // Public root order is presentation-only. Default -c destinations are in + // the invocation cwd, and the selected no-test package publishes nothing. + let publicalpha: str = strings.concat(root, "/alpha.test"); + let publicbeta: str = strings.concat(root, "/beta.test"); + let publicgamma: str = strings.concat(root, "/gamma.test"); let publicout: str = ""; let publicerr: str = ""; let publicalphabytes: str = ""; @@ -3562,11 +3574,11 @@ fn cwdwritedata(dir: str, label: str) void = { append(publicav, gamma); append(publicav, beta); append(publicav, alpha); }; - runcommand(root, strings.concat("multi-public-order-", - publiclabels[pi]), publicav, + runcommanddir(root, strings.concat("multi-public-order-", + publiclabels[pi]), root, publicav, (180i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); - assert(occurrences(out.stdout, "built ") == 2); + assert(occurrences(out.stdout, "built ") == 0); assert(has(out.stdout, strings.concat( "? ", gamma, " [no tests]\n"))); assert(os.exists(publicalpha) && os.exists(publicbeta)); @@ -3584,8 +3596,6 @@ fn cwdwritedata(dir: str, label: str) void = { assert(same(publicalphabytes, readfile(publicalpha))); assert(same(publicbetabytes, readfile(publicbeta))); }; - let wi: i32 = 0; - for (wi < publicworks.len) { clean(publicworks[wi]); wi += 1; }; clean(publicalpha); clean(publicbeta); clean(publicgamma); pi += 1; }; @@ -3756,6 +3766,8 @@ fn cwdwritedata(dir: str, label: str) void = { append(linkenv, strings.concat("WW_MIXED_W6L=", driver("w6l"))); let bins: []str = [strings.concat(named, "/test.test"), strings.concat(consumer, "/zconsumer.test")]; + let statuses: []str = [strings.concat(root, "/mixed-test.status"), + strings.concat(root, "/mixed-zconsumer.status")]; let keys: []str = ["test-internal-test", "test_test-external-test", "test-test-main", "zconsumer-internal-test", "zconsumer_test-external-test", "zconsumer-test-main"]; @@ -3766,8 +3778,14 @@ fn cwdwritedata(dir: str, label: str) void = { let out: commandout; let si: i32 = 0; for (si < stages.len) { - let av: []str = [driver(stages[si]), "test", "-c", "-j", "1", - "-I", suite, strings.concat(suite, "/...")]; + assert(os.mkdir(workroot, 448i32) == 0); + let av: []str = [driver(stages[si]), "test", "-c", "-w", workroot, + "-I", suite, + "--ww-package-test", "test", "test", "test", "test", + "test_test", named, bins[0], "-", statuses[0], + "--ww-package-test", "test", "zconsumer", "zconsumer", + "zconsumer", "zconsumer_test", consumer, bins[1], "-", + statuses[1], named]; if (si == 0) { runcommandenv(root, "mixed-actions-c", av, linkenv, (120i64 * (time.second: i64)): time.duration, &out); @@ -3776,7 +3794,7 @@ fn cwdwritedata(dir: str, label: str) void = { (120i64 * (time.second: i64)): time.duration, &out); }; expectexit(&out, 0); - assert(occurrences(out.stdout, "built ") == 2); + assert(out.stdout.len == 0 && out.stderr.len == 0); let namedprod: str = readfile(strings.concat(work, "test-internal-test.unit.ww")); let support: str = readfile(strings.concat(work, "__wwtest.unit.ww")); @@ -4441,7 +4459,7 @@ fn cwdwritedata(dir: str, label: str) void = { append(env, strings.concat("WW_UNIVERSE_BUILDER_TRACE=", buildertraces[si])); - let av: []str = alloc([], (8 + productcount * 9): u64)!; + let av: []str = alloc([], (8 + productcount * 10): u64)!; append(av, driver(stages[si])); append(av, "test"); append(av, "-c"); append(av, "-w"); append(av, works[si]); @@ -4461,6 +4479,7 @@ fn cwdwritedata(dir: str, label: str) void = { }; append(av, dir); append(av, strings.concat(root, "/product-", name, ".test")); + append(av, "-"); append(av, strings.concat(root, "/product-", name, ".status")); i += 1; }; @@ -4479,12 +4498,13 @@ fn cwdwritedata(dir: str, label: str) void = { }; append(av, dir); append(av, strings.concat(root, "/product-", name, ".test")); + append(av, "-"); append(av, strings.concat(root, "/product-", name, ".status")); i -= 1; }; }; append(av, strings.concat(suite, "/p000")); - assert(av.len == 8 + productcount * 9); + assert(av.len == 8 + productcount * 10); runcommandenv(root, strings.concat("dynamic-universe-", stages[si]), av, env, (1200i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -5038,7 +5058,7 @@ fn cwdwritedata(dir: str, label: str) void = { let seedav: []str = [driver(stages[i]), "test", "-c", "-w", lateworks[i], "-I", root, "--ww-package-test", "test", "late", "late", "late", "late_test", late, latebins[i], - latestatuses[i], late]; + "-", latestatuses[i], late]; runcommandenv(root, strings.concat("late-seed-", tags[i]), seedav, seedenv, (180i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -5164,7 +5184,7 @@ fn cwdwritedata(dir: str, label: str) void = { let failav: []str = [driver(stages[i]), "test", "-c", "-w", lateworks[i], "-I", root, "--ww-package-test", "test", "late", "late", "late", "late_test", late, attemptbin, - attemptstatus, late]; + "-", attemptstatus, late]; runcommandenv(root, strings.concat("late-failure-", phaselabels[phasei], "-", tags[i], "-", retrylabel), failav, env, (180i64 * (time.second: i64)): time.duration, &out); @@ -5276,8 +5296,8 @@ fn cwdwritedata(dir: str, label: str) void = { let out: commandout; let i: i32 = 0; for (i < stages.len) { - let av: []str = [driver(stages[i]), "test", "-c", "-I", root, - "-o", bin, pkg]; + let av: []str = [driver(stages[i]), "test", "-w", workroot, + "-I", root, "-o", bin, pkg]; runcommand(root, strings.concat("named-test-build-", stages[i]), av, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -5914,18 +5934,22 @@ fn cwdwritedata(dir: str, label: str) void = { orderwork, "-I", source, "--ww-package-test", "test", "main", "main", "-", "-", client, strings.concat(root, "/order-client-", stages[si]), + "-", strings.concat(root, "/order-client-", stages[si], ".status"), "--ww-package-test", "test", "main", "main", "-", "-", outsider, strings.concat(root, "/order-outsider-", stages[si]), + "-", strings.concat(root, "/order-outsider-", stages[si], ".status"), client]; let orderreverse: []str = [driver(stages[si]), "test", "-c", "-w", orderwork, "-I", source, "--ww-package-test", "test", "main", "main", "-", "-", outsider, strings.concat(root, "/order-outsider-", stages[si]), + "-", strings.concat(root, "/order-outsider-", stages[si], ".status"), "--ww-package-test", "test", "main", "main", "-", "-", client, strings.concat(root, "/order-client-", stages[si]), + "-", strings.concat(root, "/order-client-", stages[si], ".status"), client]; let orderrequests: [][]str = [orderforward, orderreverse]; @@ -6352,10 +6376,10 @@ fn cwdwritedata(dir: str, label: str) void = { source, "--ww-package-build", "--ww-package-test", "build", "paritybase", "paritybase", "-", "-", base, - combinedbase, combinedbasestatus, + combinedbase, "-", combinedbasestatus, "--ww-package-test", "build", "parityleft", "parityleft", "-", "-", left, - combinedleft, combinedleftstatus, left]; + combinedleft, "-", combinedleftstatus, left]; runcommandenv(root, strings.concat("long-combined-", tags[si]), combinedav, env, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -9113,13 +9137,17 @@ fn cwdwritedata(dir: str, label: str) void = { strings.concat(root, "/stem"), spec]; let ow: []str = [driver("ww_ww"), "test", "-o", strings.concat(root, "/stem"), spec]; - runcommand(root, "tree-o-c", oc, time.second, &outc); - runcommand(root, "tree-o-ww", ow, time.second, &outw); - expectexit(&outc, 2); - expectexit(&outw, 2); + runcommand(root, "tree-o-c", oc, + (30i64 * (time.second: i64)): time.duration, &outc); + runcommand(root, "tree-o-ww", ow, + (30i64 * (time.second: i64)): time.duration, &outw); + expectexit(&outc, 1); + expectexit(&outw, 1); + assert(outc.stdout.len == 0 && outw.stdout.len == 0); assert(same(outc.stderr, outw.stderr)); assert(has(outc.stderr, - "ww test: -o needs -c for a package target\n")); + "with multiple packages, -o must refer to a directory or /dev/null")); + assert(!os.exists(strings.concat(root, "/stem"))); // -w on a package target forwards to the coordinator: one shared // persistent driver workdir for the complete selected request, @@ -9691,9 +9719,11 @@ fn cwdwritedata(dir: str, label: str) void = { "--ww-package-build", "--ww-package-test", "build", "foo", "foo", "-", "-", localleft, leftout, + "-", strings.concat(root, "/local-", stages[si], "-left.status"), "--ww-package-test", "build", "foo", "foo", "-", "-", localright, rightout, + "-", strings.concat(root, "/local-", stages[si], "-right.status"), localleft]; runcommand(root, strings.concat("local-command-union-", stages[si]), @@ -10440,15 +10470,15 @@ fn runtimepath(relative: str) str = { let foldforward: []str = [driver(stages[si]), "test", "-c", "-w", foldmultiwork, "-I", source, "--ww-package-build", "--ww-package-test", "build", "main", "main", "-", "-", client, - foldallowedout, foldallowedstatus, + foldallowedout, "-", foldallowedstatus, "--ww-package-test", "build", "main", "main", "-", "-", - foldvendorclient, foldcollisionout, foldcollisionstatus, client]; + foldvendorclient, foldcollisionout, "-", foldcollisionstatus, client]; let foldreverse: []str = [driver(stages[si]), "test", "-c", "-w", foldmultiwork, "-I", source, "--ww-package-build", "--ww-package-test", "build", "main", "main", "-", "-", - foldvendorclient, foldcollisionout, foldcollisionstatus, + foldvendorclient, foldcollisionout, "-", foldcollisionstatus, "--ww-package-test", "build", "main", "main", "-", "-", client, - foldallowedout, foldallowedstatus, client]; + foldallowedout, "-", foldallowedstatus, client]; let foldrequests: [][]str = [foldforward, foldreverse]; let foldmultidiags: []str = ["", ""]; let foldorderlabels: []str = ["forward", "reverse"]; @@ -10793,7 +10823,7 @@ fn runtimepath(relative: str) str = { strings.concat(combinedoutputs[5], ".status"), strings.concat(combinedoutputs[6], ".status")]; let combinedav: []str = alloc([], - (9 + combinedtargets.len * 9): u64)!; + (9 + combinedtargets.len * 10): u64)!; append(combinedav, driver(stages[si])); append(combinedav, "test"); append(combinedav, "-c"); append(combinedav, "-w"); append(combinedav, combinedwork); @@ -10809,6 +10839,7 @@ fn runtimepath(relative: str) str = { append(combinedav, "-"); append(combinedav, combinedtargets[ui]); append(combinedav, combinedoutputs[ui]); + append(combinedav, "-"); append(combinedav, combinedstatuses[ui]); ui += 1; }; @@ -10882,15 +10913,15 @@ fn runtimepath(relative: str) str = { let orderforward: []str = [driver(stages[si]), "test", "-c", "-w", orderwork, "-I", source, "--ww-package-build", "--ww-package-test", "build", "main", "main", "-", "-", client, - orderallowed, orderallowedstatus, + orderallowed, "-", orderallowedstatus, "--ww-package-test", "build", "main", "main", "-", "-", outsider, - orderforbidden, orderforbiddenstatus, client]; + orderforbidden, "-", orderforbiddenstatus, client]; let orderreverse: []str = [driver(stages[si]), "test", "-c", "-w", orderwork, "-I", source, "--ww-package-build", "--ww-package-test", "build", "main", "main", "-", "-", outsider, - orderforbidden, orderforbiddenstatus, + orderforbidden, "-", orderforbiddenstatus, "--ww-package-test", "build", "main", "main", "-", "-", client, - orderallowed, orderallowedstatus, client]; + orderallowed, "-", orderallowedstatus, client]; let orderrequests: [][]str = [orderforward, orderreverse]; let orderdiags: []str = ["", ""]; let oi: i32 = 0; @@ -12162,23 +12193,32 @@ fn runtimepath(relative: str) str = { let pdir: str = strings.concat(root, "/pkg"); assert(os.mkdir(pdir, 493i32) == 0); writefile(strings.concat(pdir, "/pkg.ww"), - "package pkg;\nexport fn v() i32 = { return 7; };\n"); - writefile(strings.concat(pdir, "/pkg_test.ww"), strings.concat( - "package pkg_test;\nimport pkg;\n", - "@test fn seven() void = { assert(pkg.v() == 7); };\n")); + "package family;\nexport fn v() i32 = { return 7; };\n"); + writefile(strings.concat(pdir, "/same_test.ww"), strings.concat( + "package family;\nimport os;\n", + "@test fn seven() void = { let av: []str = os.args();", + " if (av.len != 0) { os.write(os.STDOUT_FILENO, av[0].ptr,", + " av[0].len: u64); os.write(os.STDOUT_FILENO, \"\\n\".ptr, 1u64); };", + " assert(v() == 7); };\n")); let adir: str = strings.concat(root, "/a"); assert(os.mkdir(adir, 493i32) == 0); - // '_'-prefixed: the tree walk skips it, so the artifacts and - // their .sepwork trees never pollute the multi-package discovery. + writefile(strings.concat(adir, "/a.ww"), + "package other;\nexport fn v() i32 = { return 1; };\n"); + writefile(strings.concat(adir, "/a_test.ww"), + "package other;\n@test fn one() void = { assert(v() == 1); };\n"); + let none: str = strings.concat(root, "/znone"); + assert(os.mkdir(none, 493i32) == 0); + writefile(strings.concat(none, "/none.ww"), + "package renamed;\nexport fn v() i32 = { return 9; };\n"); + // '_' roots are excluded from recursive package discovery. let outdir: str = strings.concat(root, "/_out"); assert(os.mkdir(outdir, 493i32) == 0); - writefile(strings.concat(adir, "/a.ww"), - "package a;\nexport fn v() i32 = { return 1; };\n"); - writefile(strings.concat(adir, "/a_test.ww"), strings.concat( - "package a_test;\nimport a;\n", - "@test fn one() void = { assert(a.v() == 1); };\n")); let out: commandout; let drvs: []str = ["ww", "ww_ww"]; + let namedbytes: str = ""; + let defaultbytes: str = ""; + let fanabytes: str = ""; + let fanpkgbytes: str = ""; let i: i32 = 0; for (i < 2) { let named: str = strings.concat(outdir, "/out_", drvs[i], @@ -12193,31 +12233,379 @@ fn runtimepath(relative: str) str = { case void => void; case let e: os.oserror => abort("-c -o artifact missing"); }; + assert(((fi.mode: u32) & 73u32) != 0u32); + assert(!has(out.stdout, "seven ... ")); match (os.stat(&fi, strings.concat(pdir, "/pkg.test"))) { case void => abort("-c -o still published the fixed stem"); case let e: os.oserror => void; }; + if (i == 0) { namedbytes = readfile(named); } + else { assert(same(namedbytes, readfile(named))); }; let runav: []str = [named]; runcommand(root, strings.concat("namerun_", drvs[i]), runav, (30i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); - assert(has(out.stdout, "seven ... ok")); + assert(has(out.stdout, "seven ... ")); + let runnamed: str = strings.concat(outdir, "/run_", drvs[i], ".bin"); let nocav: []str = [driver(drvs[i]), "test", "-I", root, - "-o", named, pdir]; + "-o", runnamed, pdir]; runcommand(root, strings.concat("noc_", drvs[i]), nocav, (30i64 * (time.second: i64)): time.duration, &out); - expectexit(&out, 2); - assert(has(out.stderr, "-o needs -c for a package target")); + expectexit(&out, 0); + assert(out.stderr.len == 0 && os.exists(runnamed)); + assert(has(out.stdout, "seven ... ")); + assert(has(out.stdout, "/package.test\n")); + assert(!has(out.stdout, runnamed)); + let defaultbin: str = strings.concat(root, "/pkg.test"); + let defaultav: []str = [driver(drvs[i]), "test", "-c", "-I", root, + pdir]; + runcommanddir(root, strings.concat("default_", drvs[i]), root, + defaultav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(os.exists(defaultbin)); + assert(!os.exists(strings.concat(root, "/family.test"))); + assert(!os.exists(strings.concat(pdir, "/family.test"))); + if (i == 0) { defaultbytes = readfile(defaultbin); } + else { assert(same(defaultbytes, readfile(defaultbin))); }; + clean(defaultbin); + + let fanroot: str = strings.concat(root, "/_fan_", drvs[i]); + let fanarg: str = strings.concat(fanroot, "/nested/"); + let fanav: []str = [driver(drvs[i]), "test", "-c", "-j", "2", + "-I", root, "-o", fanarg, strings.concat(root, "/...")]; + runcommand(root, strings.concat("fan_", drvs[i]), fanav, + (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let fanabin: str = strings.concat(fanarg, "a.test"); + let fanpkg: str = strings.concat(fanarg, "pkg.test"); + assert(os.exists(fanabin) && os.exists(fanpkg)); + assert(!os.exists(strings.concat(fanarg, "znone.test"))); + assert(has(out.stdout, strings.concat("? ", none, " [no tests]\n"))); + if (i == 0) { + fanabytes = readfile(fanabin); + fanpkgbytes = readfile(fanpkg); + } else { + assert(same(fanabytes, readfile(fanabin))); + assert(same(fanpkgbytes, readfile(fanpkg))); + }; + + let badout: str = strings.concat(outdir, "/multi-file-", drvs[i]); let mulav: []str = [driver(drvs[i]), "test", "-c", "-o", - named, strings.concat(root, "/...")]; + badout, strings.concat(root, "/...")]; runcommand(root, strings.concat("multi_", drvs[i]), mulav, (30i64 * (time.second: i64)): time.duration, &out); - expectexit(&out, 2); + expectexit(&out, 1); assert(has(out.stderr, - "cannot use -o with multiple packages")); + "with multiple packages, -o must refer to a directory or /dev/null")); + assert(!os.exists(badout)); + + let nullcompile: []str = [driver(drvs[i]), "test", "-c", "-I", + root, "-o", "/dev/null", pdir]; + runcommand(root, strings.concat("null-compile_", drvs[i]), nullcompile, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(!has(out.stdout, "seven ... ")); + assert(!os.exists("/dev/null.new")); + let nullrun: []str = [driver(drvs[i]), "test", "-I", root, + "-o", "/dev/null", pdir]; + runcommand(root, strings.concat("null-run_", drvs[i]), nullrun, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "seven ... ")); i += 1; }; clean(root); }; + +@test fn test_binary_publication_transaction() void = { + let root: str = fresh(); + let suite: str = strings.concat(root, "/suite"); + let leftsame: str = strings.concat(suite, "/left/same"); + let rightsame: str = strings.concat(suite, "/right/same"); + let alpha: str = strings.concat(suite, "/alpha"); + let beta: str = strings.concat(suite, "/beta"); + let failed: str = strings.concat(suite, "/failed"); + let none: str = strings.concat(suite, "/none"); + let persist: str = strings.concat(suite, "/persist"); + mkdirall(leftsame); mkdirall(rightsame); mkdirall(alpha); + mkdirall(beta); mkdirall(failed); mkdirall(none); mkdirall(persist); + writefile(strings.concat(leftsame, "/same.ww"), + "package declared_left;\nfn value() i32 = { return 1; };\n"); + writefile(strings.concat(leftsame, "/same_test.ww"), strings.concat( + "package declared_left;\n", + "@test fn left() void = { assert(value() == 1); };\n")); + writefile(strings.concat(rightsame, "/same.ww"), + "package declared_right;\nfn value() i32 = { return 2; };\n"); + writefile(strings.concat(rightsame, "/same_test.ww"), strings.concat( + "package declared_right;\n", + "@test fn right() void = { assert(value() == 2); };\n")); + writefile(strings.concat(alpha, "/alpha.ww"), + "package alpha_decl;\nfn value() i32 = { return 3; };\n"); + writefile(strings.concat(alpha, "/alpha_test.ww"), strings.concat( + "package alpha_decl;\n", + "@test fn alpha_ok() void = { assert(value() == 3); };\n")); + writefile(strings.concat(beta, "/beta.ww"), + "package beta_decl;\nfn value() i32 = { return 4; };\n"); + writefile(strings.concat(beta, "/beta_test.ww"), strings.concat( + "package beta_decl;\n", + "@test fn beta_ok() void = { assert(value() == 4); };\n")); + writefile(strings.concat(failed, "/failed.ww"), + "package failed_decl;\nfn value() i32 = { return 5; };\n"); + writefile(strings.concat(failed, "/failed_test.ww"), strings.concat( + "package failed_decl;\n", + "@test fn runtime_failure() void = { assert(false); };\n")); + writefile(strings.concat(none, "/none.ww"), + "package none_decl;\nfn value() i32 = { return 6; };\n"); + let persisttest: str = strings.concat(persist, "/persist_test.ww"); + let persistbase: str = strings.concat( + "package persist_decl;\n", + "@test fn persistent_base() void = { assert(true); };\n"); + let persistchanged: str = strings.concat( + "package persist_decl;\n", + "@test fn persistent_changed() void = { assert(1 + 1 == 2); };\n"); + writefile(strings.concat(persist, "/persist.ww"), + "package persist_decl;\nfn value() i32 = { return 7; };\n"); + writefile(persisttest, persistbase); + + let compilerwrapper: str = strings.concat(root, "/publish-w6c.sh"); + let linkerwrapper: str = strings.concat(root, "/publish-w6l.sh"); + writeexecutable(compilerwrapper, strings.concat( + "#!/bin/sh\nprintf '%s\\n' \"$*\" >> \"$WW_PUBLISH_CTRACE\"\n", + "exec \"$WW_PUBLISH_REAL_C\" \"$@\"\n")); + writeexecutable(linkerwrapper, strings.concat( + "#!/bin/sh\nprintf '%s\\n' \"$*\" >> \"$WW_PUBLISH_LTRACE\"\n", + "if test -n \"$WW_PUBLISH_FAIL\"; then\n", + " for arg do case \"$arg\" in *\"$WW_PUBLISH_FAIL\"*)\n", + " printf 'injected publication linker failure\\n' >&2\n", + " exit 97;; esac; done\nfi\n", + "exec \"$WW_PUBLISH_REAL_L\" \"$@\"\n")); + let stages: []str = ["ww", "ww_ww"]; + let compilers: []str = ["w6c", "w6c_ww"]; + let linkers: []str = ["w6l", "w6l_ww"]; + let tags: []str = ["c", "ww"]; + let duplicatediag: str = ""; + let rollbackdiag: str = ""; + let missingdiag: str = ""; + let occupieddiag: str = ""; + let failurebytes: str = ""; + let persistbasebytes: str = ""; + let persistchangedbytes: str = ""; + let baseenv: []str = os.getenvs(); + let out: commandout; + let si: i32 = 0; + for (si < stages.len) { + let ctrace: str = strings.concat(root, "/publish-", tags[si], + "-w6c.trace"); + let ltrace: str = strings.concat(root, "/publish-", tags[si], + "-w6l.trace"); + writefile(ctrace, ""); writefile(ltrace, ""); + let env: []str = alloc([], (baseenv.len + 7): u64)!; + let ei: i32 = 0; + for (ei < baseenv.len) { + if (!strings.hasprefix(baseenv[ei], "WW_W6C=") + && !strings.hasprefix(baseenv[ei], "WW_W6L=") + && !strings.hasprefix(baseenv[ei], "WW_PUBLISH_CTRACE=") + && !strings.hasprefix(baseenv[ei], "WW_PUBLISH_LTRACE=") + && !strings.hasprefix(baseenv[ei], "WW_PUBLISH_REAL_C=") + && !strings.hasprefix(baseenv[ei], "WW_PUBLISH_REAL_L=") + && !strings.hasprefix(baseenv[ei], "WW_PUBLISH_FAIL=")) { + append(env, baseenv[ei]); + }; + ei += 1; + }; + append(env, strings.concat("WW_W6C=", compilerwrapper)); + append(env, strings.concat("WW_W6L=", linkerwrapper)); + append(env, strings.concat("WW_PUBLISH_CTRACE=", ctrace)); + append(env, strings.concat("WW_PUBLISH_LTRACE=", ltrace)); + append(env, strings.concat("WW_PUBLISH_REAL_C=", + driver(compilers[si]))); + append(env, strings.concat("WW_PUBLISH_REAL_L=", + driver(linkers[si]))); + append(env, "WW_PUBLISH_FAIL="); + + // Equal import leaves are rejected before tools or output creation, + // independent of their unequal declared package names. + let duplicateout: str = strings.concat(root, "/duplicate-output/"); + let duplicateav: []str = [driver(stages[si]), "test", "-c", + "-j", "2", "-I", suite, "-o", duplicateout, + leftsame, rightsame]; + runcommandenvdir(root, strings.concat("publish-duplicate-", tags[si]), + duplicateav, env, root, + (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(out.stdout.len == 0); + assert(has(out.stderr, + "ww test: cannot write test binary same.test for multiple packages:\n")); + assert(has(out.stderr, strings.concat(leftsame, "\n"))); + assert(has(out.stderr, strings.concat(rightsame, "\n"))); + assert(readfile(ctrace).len == 0 && readfile(ltrace).len == 0); + assert(!os.exists(duplicateout)); + if (si == 0) { duplicatediag = strings.dup(out.stderr); } + else { assert(same(duplicatediag, out.stderr)); }; + + // /dev/null is Go's exact multi-package discard exception: both + // products build, neither publishes, and duplicate names are harmless. + let nullav: []str = [driver(stages[si]), "test", "-c", "-j", "2", + "-I", suite, "-o", "/dev/null", leftsame, rightsame]; + runcommandenvdir(root, strings.concat("publish-null-", tags[si]), + nullav, env, root, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0); + assert(readfile(ctrace).len != 0); + assert(occurrences(readfile(ltrace), "\n") == 2); + assert(!os.exists("/dev/null.new")); + + // An occupied retained-binary stage is a pre-tool rejection. The + // caller's old destination and occupied stage both remain untouched. + let occupied: str = strings.concat(root, "/occupied"); + if (si == 0) { + assert(os.mkdir(occupied, 448i32) == 0); + writefile(strings.concat(occupied, "/alpha.test"), "old-alpha\n"); + writefile(strings.concat(occupied, "/alpha.test.new"), + "occupied-stage\n"); + }; + rewritefile(ctrace, ""); rewritefile(ltrace, ""); + let occupiedav: []str = [driver(stages[si]), "test", "-c", "-I", + suite, "-o", strings.concat(occupied, "/"), alpha]; + runcommandenvdir(root, strings.concat("publish-occupied-", tags[si]), + occupiedav, env, root, + (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(has(out.stderr, strings.concat( + "ww: product staging path already exists: ", occupied, + "/alpha.test.new\n"))); + assert(same(readfile(strings.concat(occupied, "/alpha.test")), + "old-alpha\n")); + assert(same(readfile(strings.concat(occupied, "/alpha.test.new")), + "occupied-stage\n")); + assert(readfile(ctrace).len == 0 && readfile(ltrace).len == 0); + if (si == 0) { occupieddiag = strings.dup(out.stderr); } + else { assert(same(occupieddiag, out.stderr)); }; + + // Link failure after an earlier sibling has staged a retained copy + // rejects the whole request and preserves every old destination. + let rollback: str = strings.concat(root, "/rollback-", tags[si]); + assert(os.mkdir(rollback, 448i32) == 0); + writefile(strings.concat(rollback, "/alpha.test"), "alpha-sentinel\n"); + writefile(strings.concat(rollback, "/beta.test"), "beta-sentinel\n"); + rewritefile(ctrace, ""); rewritefile(ltrace, ""); + env[env.len - 1] = "WW_PUBLISH_FAIL=beta-test-main"; + let rollbackav: []str = [driver(stages[si]), "test", "-c", "-j", + "1", "-I", suite, "-o", strings.concat(rollback, "/"), + alpha, beta]; + runcommandenvdir(root, strings.concat("publish-rollback-", tags[si]), + rollbackav, env, root, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(has(out.stderr, "injected publication linker failure\n")); + assert(has(out.stderr, "ww: w6l failed\n")); + assert(same(readfile(strings.concat(rollback, "/alpha.test")), + "alpha-sentinel\n")); + assert(same(readfile(strings.concat(rollback, "/beta.test")), + "beta-sentinel\n")); + assert(!directoryhasnew(rollback)); + assert(occurrences(readfile(ltrace), "\n") == 2); + if (si == 0) { rollbackdiag = strings.dup(out.stderr); } + else { assert(same(rollbackdiag, out.stderr)); }; + + // Output directories minted for a rejected request are rolled back. + let missingroot: str = strings.concat(root, "/missing-", tags[si]); + let missingout: str = strings.concat(missingroot, "/nested/"); + rewritefile(ctrace, ""); rewritefile(ltrace, ""); + env[env.len - 1] = "WW_PUBLISH_FAIL=alpha-test-main"; + let missingav: []str = [driver(stages[si]), "test", "-c", "-j", + "1", "-I", suite, "-o", missingout, alpha]; + runcommandenvdir(root, strings.concat("publish-missing-", tags[si]), + missingav, env, root, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(!os.exists(missingroot)); + assert(occurrences(readfile(ltrace), "\n") == 1); + if (si == 0) { missingdiag = strings.dup(out.stderr); } + else { assert(same(missingdiag, out.stderr)); }; + + // A package with no tests never claims a binary or creates a requested + // output hierarchy, although its production package is still checked. + let noneroot: str = strings.concat(root, "/none-output-", tags[si]); + let noneav: []str = [driver(stages[si]), "test", "-c", "-I", suite, + "-o", strings.concat(noneroot, "/nested/"), none]; + env[env.len - 1] = "WW_PUBLISH_FAIL="; + runcommandenvdir(root, strings.concat("publish-none-", tags[si]), + noneav, env, root, + (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, strings.concat("? ", none, + " [no tests]\n"))); + assert(out.stderr.len == 0 && !os.exists(noneroot)); + + // Publication commits before execution. A failing test therefore leaves + // a runnable retained binary while its product failure stays on stdout. + let failurebin: str = strings.concat(root, "/failure-", tags[si], + ".test"); + let failureav: []str = [driver(stages[si]), "test", "-I", suite, + "-o", failurebin, failed]; + runcommandenvdir(root, strings.concat("publish-runtime-", tags[si]), + failureav, env, root, + (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(os.exists(failurebin) && !os.exists(strings.concat(failurebin, + ".new"))); + assert(has(out.stdout, + "failed_decl.runtime_failure ... FAIL (exit 1)\n")); + assert(out.stderr.len == 0); + let fi: os.filestat; + match (os.stat(&fi, failurebin)) { + case void => void; + case let e: os.oserror => abort("retained failure binary missing"); + }; + assert(((fi.mode: u32) & 73u32) != 0u32); + if (si == 0) { failurebytes = readfile(failurebin); } + else { assert(same(failurebytes, readfile(failurebin))); }; + + // -w keeps semantic actions persistent while -o remains a presentation + // copy. Unchanged input reuses actions; changed input replaces the copy. + rewritefile(persisttest, persistbase); + let persistwork: str = strings.concat(root, "/persist-work-", tags[si]); + let persistbin: str = strings.concat(root, "/persist.test"); + if (os.exists(persistbin)) { clean(persistbin); }; + rewritefile(ctrace, ""); rewritefile(ltrace, ""); + let persistav: []str = [driver(stages[si]), "test", "-w", + persistwork, "-I", suite, "-o", persistbin, persist]; + runcommandenvdir(root, strings.concat("publish-persist-cold-", tags[si]), + persistav, env, root, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "persistent_base ... ok\n")); + let basebytes: str = readfile(persistbin); + if (si == 0) { persistbasebytes = strings.dup(basebytes); } + else { assert(same(persistbasebytes, basebytes)); }; + rewritefile(ctrace, ""); rewritefile(ltrace, ""); + runcommandenvdir(root, strings.concat("publish-persist-warm-", tags[si]), + persistav, env, root, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(readfile(ctrace).len == 0); + assert(same(basebytes, readfile(persistbin))); + rewritefile(persisttest, persistchanged); + rewritefile(ctrace, ""); rewritefile(ltrace, ""); + runcommandenvdir(root, strings.concat("publish-persist-change-", tags[si]), + persistav, env, root, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "persistent_changed ... ok\n")); + assert(readfile(ctrace).len != 0); + let changedbytes: str = readfile(persistbin); + assert(!same(basebytes, changedbytes)); + if (si == 0) { persistchangedbytes = strings.dup(changedbytes); } + else { assert(same(persistchangedbytes, changedbytes)); }; + assert(!directoryhasnew(persistwork)); + si += 1; + }; + assert(os.remove(strings.concat(root, + "/occupied/alpha.test.new")) == 0); + clean(root); +}; diff --git a/test/sep/importdir_test.ww b/test/sep/importdir_test.ww index 6995ac48..5814ad4b 100644 --- a/test/sep/importdir_test.ww +++ b/test/sep/importdir_test.ww @@ -197,18 +197,21 @@ fn samefile(a: str, b: str, why: str) void = { "@test fn directory_import() void = { assert(foo.value() == 42); };\n")); let testbins: []str = [strings.concat(td, "/test-c"), strings.concat(td, "/test-w")]; + let testworks: []str = [strings.concat(td, "/test-work-c"), + strings.concat(td, "/test-work-w")]; i = 0; for (i < 2) { - let tav: []str = [testenv.driver(drivers[i * 2]), "test", "-c", - "-I", early, "-I", late, "-o", testbins[i], checks]; + let tav: []str = [testenv.driver(drivers[i * 2]), "test", "-w", + testworks[i], "-I", early, "-I", late, "-o", testbins[i], + checks]; expectcode(td, strings.concat("test_build_", tags[i * 2]), tav, 0); let trav: []str = [testbins[i]]; expectcode(td, strings.concat("test_run_", tags[i * 2]), trav, 0); i += 1; }; samefile(testbins[0], testbins[1], "directory-test binaries differ"); - samefile(strings.concat(testbins[0], ".sepwork/example.foo.a"), - strings.concat(testbins[1], ".sepwork/example.foo.a"), + samefile(strings.concat(testworks[0], "/example.foo.a"), + strings.concat(testworks[1], "/example.foo.a"), "directory-test dependency archives differ"); testenv.clean(td); }; diff --git a/test/sep/sepinit_test.ww b/test/sep/sepinit_test.ww index c3cc60a4..4bd36945 100644 --- a/test/sep/sepinit_test.ww +++ b/test/sep/sepinit_test.ww @@ -814,7 +814,7 @@ fn rejectrow(td: str, label: str, src: str, expected: str) void = { let av: []str = [launcher, limitstr, testenv.driver(stages[stagei]), "test", "-c", "-w", work, "-I", tree, "--ww-package-test", "test", "target", "target", "target", - "target_test", target, output, status, target]; + "target_test", target, output, "-", status, target]; let out: testenv.commandout; runenv(td, strings.concat("allocation-", stages[stagei], "-", tag), av, env, &out); diff --git a/test/tool/driver_test.ww b/test/tool/driver_test.ww index bfdb6647..5a8e7231 100644 --- a/test/tool/driver_test.ww +++ b/test/tool/driver_test.ww @@ -167,13 +167,12 @@ fn runrootargv(dir: str, root: str, name: str, drv: str, // a3 == "" means a two-token argv tail; no row passes a literal "". @test fn flagargs() void = { let a1: []str = ["build", "build", "build", "build", "build", - "run", "run", "run", "test", "test", "test", "test", "test", - "test"]; + "run", "run", "run", "test", "test", "test", "test", "test"]; let a2: []str = ["-o", "-I", "-L", "-l", "-zz", - "-o", "-l", "-zz", "-l", "-zz", "-I", "-o", "-o", + "-o", "-l", "-zz", "-l", "-zz", "-I", "-o", "-run"]; let a3: []str = ["", "", "", "", "", - "", "", "", "", "", "", "", "x", + "", "", "", "", "", "", "", ""]; let subs: []str = [ "ww build: -o needs an argument", @@ -188,7 +187,6 @@ fn runrootargv(dir: str, root: str, name: str, drv: str, "ww test: unknown flag", "ww test: -I needs an argument", "ww test: -o needs an argument", - "ww test: -o needs -c for a package target", "ww test: -run needs an argument"]; let i: i32 = 0; for (i < subs.len) {