ww test: retain directory test binaries like Go

This commit is contained in:
2026-08-20 20:13:08 +09:00
parent f28843f2f5
commit db5782eff9
10 changed files with 1014 additions and 213 deletions

View File

@@ -13,6 +13,7 @@
#include <sys/wait.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <stdarg.h>
@@ -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,

View File

@@ -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 150168](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 631646](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 771804](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 11331169](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 12001313](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 22872300](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L2287-L2300),
[`cmd/go/internal/load/pkg.go`, lines 17271769](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 19042000](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/exec.go#L1904-L2000),
[`cmd/go/internal/work/shell.go`, lines 119220 and 283301](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 8192](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 338](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 `<declared-package>.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
`<import-leaf>.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.

View File

@@ -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 `<import-leaf>.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`,

View File

@@ -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
`<package>.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 <name>`
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
`<import-leaf>.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 <stem>`, and each successful
directory-package `ww test -c` build publish `<stem>.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 <stem>` publish
`<stem>.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 <name>` 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

View File

@@ -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 ");

View File

@@ -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");

View File

@@ -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);
};

View File

@@ -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);
};

View File

@@ -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);

View File

@@ -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) {