ww: print final status for explicit test failures
This commit is contained in:
@@ -211,12 +211,12 @@ exec_package_command(int argc, char **argv, const char *target,
|
||||
if (fallback == NULL) return 1;
|
||||
prog = fallback;
|
||||
}
|
||||
if (argc < 0 || argc > INT_MAX - 10) {
|
||||
if (argc < 0 || argc > INT_MAX - 11) {
|
||||
free(fallback);
|
||||
sep_fail_size();
|
||||
return 1;
|
||||
}
|
||||
char **xargv = calloc((size_t)argc + 10, sizeof *xargv);
|
||||
char **xargv = calloc((size_t)argc + 11, sizeof *xargv);
|
||||
if (xargv == NULL) {
|
||||
fprintf(stderr,
|
||||
"ww %s: cannot allocate package coordinator arguments\n",
|
||||
@@ -233,6 +233,8 @@ exec_package_command(int argc, char **argv, const char *target,
|
||||
}
|
||||
xargv[n++] = "--ww-driver";
|
||||
xargv[n++] = (char *)self_path;
|
||||
if (!build_only && !add_dot)
|
||||
xargv[n++] = "--ww-explicit-test-target";
|
||||
if (root_identity != NULL) {
|
||||
xargv[n++] = "--ww-root-identity";
|
||||
xargv[n++] = (char *)root_identity;
|
||||
@@ -7985,6 +7987,8 @@ do_test(int argc, char **argv)
|
||||
if (!resolve_module(target, incs, resolved, sizeof resolved,
|
||||
&is_dir)) {
|
||||
fprintf(stderr, "ww test: cannot find %s\n", target);
|
||||
if (src != NULL && !compileonly && !emit_asm)
|
||||
fputs("FAIL\n", stdout);
|
||||
return 1;
|
||||
}
|
||||
if (is_dir) {
|
||||
@@ -8064,6 +8068,7 @@ do_test(int argc, char **argv)
|
||||
fputs("ww: cannot remove temporary output\n", stderr);
|
||||
if (owntmp && rmdir(tmpdir) != 0)
|
||||
fputs("ww: cannot remove temporary directory\n", stderr);
|
||||
if (!compileonly && !emit_asm) fputs("FAIL\n", stdout);
|
||||
return 1;
|
||||
}
|
||||
if (compileonly || emit_asm) {
|
||||
@@ -8079,6 +8084,7 @@ do_test(int argc, char **argv)
|
||||
return cleanfail ? 1 : 0;
|
||||
}
|
||||
int rc = run_test_bin(outp, pattern);
|
||||
int test_failed = rc != 0;
|
||||
if (rc == 0 && deferred_install
|
||||
&& sep_install_test_output(outp, outstem) != 0)
|
||||
rc = 1;
|
||||
@@ -8090,6 +8096,7 @@ do_test(int argc, char **argv)
|
||||
fputs("ww: cannot remove temporary directory\n", stderr);
|
||||
if (rc == 0) rc = 1;
|
||||
}
|
||||
if (test_failed) fputs("FAIL\n", stdout);
|
||||
return rc;
|
||||
}
|
||||
if (S_ISREG(st.st_mode)) {
|
||||
@@ -8139,6 +8146,7 @@ do_test(int argc, char **argv)
|
||||
fputs("ww: cannot remove temporary output\n", stderr);
|
||||
if (owntmp && rmdir(tmpdir) != 0)
|
||||
fputs("ww: cannot remove temporary directory\n", stderr);
|
||||
if (!compileonly && !emit_asm) fputs("FAIL\n", stdout);
|
||||
return 1;
|
||||
}
|
||||
if (compileonly || emit_asm) {
|
||||
@@ -8154,6 +8162,7 @@ do_test(int argc, char **argv)
|
||||
return cleanfail ? 1 : 0;
|
||||
}
|
||||
int rc = run_test_bin(outp, pattern);
|
||||
int test_failed = rc != 0;
|
||||
if (rc == 0 && deferred_install
|
||||
&& sep_install_test_output(outp, outstem) != 0)
|
||||
rc = 1;
|
||||
@@ -8165,6 +8174,7 @@ do_test(int argc, char **argv)
|
||||
fputs("ww: cannot remove temporary directory\n", stderr);
|
||||
if (rc == 0) rc = 1;
|
||||
}
|
||||
if (test_failed) fputs("FAIL\n", stdout);
|
||||
return rc;
|
||||
}
|
||||
if (!S_ISDIR(st.st_mode)) {
|
||||
|
||||
@@ -7190,6 +7190,159 @@ must match exactly. Existing package execution, timeout, interruption,
|
||||
transaction, persistence, byte-identity, and cleanup owners continue to prove
|
||||
the mechanisms this slice does not alter.
|
||||
|
||||
### 11.34 Implemented final `FAIL` for explicit ordinary test failures
|
||||
|
||||
An ordinary `ww test` request with an explicit target now ends its ordered
|
||||
standard output with exactly one command-owned `FAIL\n` when test setup, build,
|
||||
or execution fails. The line follows every package result, including successful
|
||||
packages ordered after an earlier failure. It applies to one or many explicit
|
||||
directory, recursive, dotted-directory, or raw-file targets; filters and list
|
||||
mode; and the private execution of a retained test. It does not apply to bare
|
||||
implicit-current-directory `ww test`, `-c`, `-S`, `ww build`, command-line
|
||||
usage/shape or output preflight rejection, publication-only failure,
|
||||
capture-only failure, cleanup-only failure, allocation/systemic coordinator
|
||||
failure, or later direct execution of a retained binary.
|
||||
|
||||
#### Pinned Go evidence and classification
|
||||
|
||||
The sole authority is official Go 1.26.5 at commit
|
||||
`c19862e5f8415b4f24b189d065ed739517c548ba`:
|
||||
|
||||
- `runTest` reports setup errors, writes the per-package setup-failure result,
|
||||
and sets the command exit status
|
||||
([`cmd/go/internal/test/test.go`, lines 1010–1061](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1010-L1061)).
|
||||
It then creates a root `go test` action owned by `printExitStatus`, orders all
|
||||
package print actions, and executes that root
|
||||
([lines 1099–1124](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1099-L1124)).
|
||||
- `builderTest` constructs the ordinary build/run/clean/print chain and gives
|
||||
the run and print boundaries the failure handling needed to reach ordered
|
||||
output; its compile-only branch instead uses a dependency-sensitive nop
|
||||
print action
|
||||
([lines 1133–1169 and 1185–1366](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1133-L1366)).
|
||||
- `(*runTestActor).Act` turns a dependency build failure into a package test
|
||||
result and sets exit status 1
|
||||
([lines 1436–1521](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1436-L1521)).
|
||||
Every non-nil execution error, including nonzero exit or abnormal/start
|
||||
failure, likewise sets the exit status and emits the package failure result
|
||||
([lines 1644–1774](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1644-L1774)).
|
||||
- `builderCleanTest` and `builderPrintTest` put cleanup and captured package
|
||||
output before the root status action
|
||||
([lines 2237–2259](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L2237-L2259)).
|
||||
`printExitStatus` then prints exactly `FAIL\n` when at least one package
|
||||
argument was explicit and the global exit status is nonzero
|
||||
([lines 2262–2284](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L2262-L2284)).
|
||||
- Official `test_status.txt` requires a failing package, a later successful
|
||||
package, and a final standalone `FAIL\n`
|
||||
([lines 3–6](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_status.txt#L3-L6)).
|
||||
Official `test_syntax_error_says_fail.txt` requires `FAIL` for an explicit
|
||||
test build/setup syntax failure
|
||||
([lines 1–13](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_syntax_error_says_fail.txt#L1-L13)).
|
||||
- `testFlags` records explicit file operands in `pkgArgs`
|
||||
([`cmd/go/internal/test/testflag.go`, lines 219–290](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/testflag.go#L219-L290));
|
||||
`PackagesAndErrors` turns those files into the command-line package
|
||||
([`cmd/go/internal/load/pkg.go`, lines 2895–2918](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L2895-L2918)),
|
||||
which `GoFilesPackage` constructs
|
||||
([lines 3244–3315](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L3244-L3315)).
|
||||
|
||||
Those source rules and official assertions are behavior directly implemented
|
||||
or asserted by pinned Go. The single-explicit-package and raw-file cases are
|
||||
derived from `len(pkgArgs) != 0` and the explicit-file loading path. The bare
|
||||
implicit exclusion is derived from empty `pkgArgs`. The compile-only and
|
||||
publication-only exclusions are derived from their uncleared dependency
|
||||
failure preventing the root actor under the pinned work executor
|
||||
([`cmd/go/internal/work/exec.go`, lines 134–205](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/exec.go#L134-L205)).
|
||||
|
||||
#### Fresh four-axis audit and direct pre-fix measurements
|
||||
|
||||
The bounded audit considered all four permanent axes and selected only this
|
||||
test-command status gap:
|
||||
|
||||
- **Go-like build:** pinned unresolved-symbol handling and its undeclared-main
|
||||
case are owned by `(*ErrorReporter).errorUnresolved`
|
||||
([`cmd/link/internal/ld/errors.go`, lines 29–65](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/errors.go#L29-L65)),
|
||||
with official assertions in `TestUndefinedRelocErrors`
|
||||
([`cmd/link/internal/ld/ld_test.go`, lines 19–45](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/ld_test.go#L19-L45))
|
||||
and `issue10978/main.go`
|
||||
([lines 5–27](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/testdata/issue10978/main.go#L5-L27)).
|
||||
Direct Cstage and WWstage no-main builds both exited 1 with identical linker
|
||||
diagnostics and no output. This candidate was aligned.
|
||||
- **Go-like test:** an explicit failing directory made both stages exit 1 with
|
||||
empty stderr and byte-identical 207-byte stdout (SHA-256
|
||||
`3d0d44d5d9c4d4d95446382807ee092a611f19b43dc3013cf8df76f135cb5c46`),
|
||||
ending at its package failure rather than a standalone marker. A failing then
|
||||
successful `-j 2` request had byte-identical 384-byte stdout (SHA-256
|
||||
`4623567924df78375fcca84ff797b7cb89d06d3fd5382704d8b9ebb79916b9d9`)
|
||||
ending at the successful `ok` result. A raw-file failure had byte-identical
|
||||
121-byte stdout (SHA-256
|
||||
`8db356ffc5b3a6d9df3308bfba8301329c5c07ce30c5ccd5cc5ee22a6c43331c`)
|
||||
ending at harness accounting. Explicit missing-import build failure likewise
|
||||
lacked the final marker. These directly measured pre-fix WW facts establish
|
||||
the selected external difference. Bare implicit and `-c` failures already
|
||||
omitted the marker and were aligned exclusions.
|
||||
- **Go-like package:** `MultiplePackageError` and package scanning implement
|
||||
conflicting selected declarations
|
||||
([`go/build/build.go`, lines 538–548 and 931–967](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L538-L548));
|
||||
`TestMultiplePackageImport` asserts the rule
|
||||
([`go/build/build_test.go`, lines 105–133](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build_test.go#L105-L133)).
|
||||
Both WW stages rejected an `alpha`/`beta` directory identically before tools.
|
||||
This candidate was aligned.
|
||||
- **Go-like import:** `unusedImports` and `errorUnusedPkg` implement the unused
|
||||
renamed-import diagnostic
|
||||
([`cmd/compile/internal/types2/resolver.go`, lines 706–740](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L706-L740));
|
||||
official `importdecl0` asserts ordinary and renamed forms
|
||||
([lines 9–27](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/importdecl0/importdecl0a.go#L9-L27)).
|
||||
After normalizing only PID-bearing scratch roots, both WW stages produced the
|
||||
same unused-renamed-import diagnostic and no output. This candidate was
|
||||
aligned.
|
||||
|
||||
The command results in that list are directly measured WW behavior; the linked
|
||||
source and tests are behavior directly implemented or asserted by pinned Go.
|
||||
The conclusion that only test presentation changes while build, package, and
|
||||
import identity remain fixed is behavior derived from the pinned action and
|
||||
final-status placement.
|
||||
|
||||
#### Ownership, final behavior, and preserved boundaries
|
||||
|
||||
The directory owner is `internal/wwpackage.packagecommand`: its private
|
||||
explicit-target bit affects only final status, and `pkgemitplan`/`pkgemitgroup`
|
||||
record attributable build/run failure while preserving canonical result order.
|
||||
After all package captures, package results, install attempts, and temporary-root
|
||||
cleanup, the coordinator emits one final line. Attributable setup/load rejection
|
||||
uses the same status helper. The two public drivers own the raw-file equivalent:
|
||||
they remember the producer or process result, finish their existing cleanup,
|
||||
then emit the line. A successful run followed only by install or cleanup failure
|
||||
does not acquire test-failure status.
|
||||
|
||||
Loading and source selection, canonical package and import identity, graph
|
||||
nodes, action construction, scheduling, compiler/assembler/linker invocation,
|
||||
generated main, child argv/environment/cwd/stdin, capture bytes, package
|
||||
diagnostics, and diagnostic precedence are unchanged. Runtime nonzero exit,
|
||||
signal, timeout, and executable-start failure keep their existing
|
||||
classification; only the command status line follows. One command-global bit
|
||||
is isolated from every product-local capture, so parallel completion order
|
||||
cannot duplicate or reorder it. If the coordinator itself is interrupted before
|
||||
final emission no post-termination output is promised; an observed child
|
||||
interruption is an ordinary run failure.
|
||||
|
||||
Artifact construction and bytes are unchanged. A failing running-retained test
|
||||
still preserves prior public bytes and creates no new executable; successful
|
||||
products in a mixed request retain their existing independent install results.
|
||||
Producer failure, publication rejection, transaction rollback, cleanup, and
|
||||
residue ownership are unchanged, and the marker creates no file. Physical
|
||||
directories and the private explicit-target signal do not enter package,
|
||||
import, graph, action, artifact, symbol, `.wwi`, publication, or persistence
|
||||
identity. No persisted byte or key changed, so build workdir format remains
|
||||
`18`, test workdir format remains `19`, and semantic storage format remains `3`.
|
||||
|
||||
The WW-native `explicit_test_failure_has_final_status` observer covers both
|
||||
stages for single and concurrent failing/succeeding packages, filters, list
|
||||
mode, raw files, setup/build and runtime failure, success, bare implicit and
|
||||
compile-only exclusions, running retained rollback, artifact-byte equality,
|
||||
diagnostic equality, exact cardinality/order, and `.new`/transaction cleanup.
|
||||
Existing directory execution observers cover signals, timeouts, child cleanup,
|
||||
and canonical-order behavior with the new final line, while build-mode controls
|
||||
prove that the other command axis remains silent.
|
||||
|
||||
## 12. Candidate architectures and hard-gate decision
|
||||
|
||||
Five candidates were developed as coherent systems, not as feature bins.
|
||||
|
||||
13
docs/spec.md
13
docs/spec.md
@@ -790,6 +790,19 @@ linker, and other build failures retain their diagnostic standard-error
|
||||
channel; build-action stdout and stderr remain separate. Parallel products own
|
||||
independent captures and are still emitted in canonical product order.
|
||||
|
||||
After those ordered results, an ordinary explicit `ww test` request that records
|
||||
an attributable setup, build, or execution failure emits exactly one standalone
|
||||
`FAIL\n` on standard output. The line is command-owned: it follows even a later
|
||||
successful package result, and a single explicit directory, recursive or dotted
|
||||
directory, or raw source file is sufficient. Runtime nonzero exit, signal,
|
||||
timeout, or executable-start failure all set that status. Filters, list mode,
|
||||
and the private execution of a retained test keep the same rule. Bare implicit
|
||||
current-directory `ww test`, `-c`, `-S`, build requests, CLI usage/shape or
|
||||
output preflight, publication-only failure, capture-only failure, cleanup-only
|
||||
failure, and later direct execution of a retained binary do not emit it. The
|
||||
marker changes no capture, diagnostic, executable, package/import/action
|
||||
identity, publication decision, or persistent byte.
|
||||
|
||||
The coordinator does not change its own cwd or environment. Parallel products
|
||||
receive independent child environments and each uses its own source directory.
|
||||
Relative ordinary files, `testdata`, and writes resolve there for every
|
||||
|
||||
@@ -322,6 +322,18 @@ has a distinct capture, while canonical group-order emission remains
|
||||
byte-stable across `-j` values. Build-plan stdout and stderr stay distinct, and
|
||||
loader/build diagnostics stay on stderr.
|
||||
|
||||
After canonical result emission, an ordinary test command with any explicit
|
||||
directory, recursive, dotted-directory, or raw-file target emits exactly one
|
||||
final standalone `FAIL\n` when attributable setup, build, or test execution
|
||||
failed. It follows later successful package output and applies to filters, list
|
||||
mode, signals, timeouts, executable-start failures, and running retained tests.
|
||||
The bare implicit-current-directory form, `-c`, `-S`, build requests, CLI
|
||||
usage/shape or output preflight, publication-only, capture-only, cleanup-only,
|
||||
and direct retained-binary execution do not receive the command marker.
|
||||
Directory coordination and both raw-file drivers own only this final
|
||||
presentation state; test captures, result trailers, diagnostics, scheduling,
|
||||
rollback, artifact bytes, and persisted action identity remain unchanged.
|
||||
|
||||
The physical directory remains distinct from exact dotted package identity and
|
||||
from production, internal, external, recompiled, support, and generated-main
|
||||
action identity. Only product execution uses it. Compiler, assembler, archiver,
|
||||
|
||||
@@ -372,6 +372,17 @@ fn pkgputln(fd: i32, s: str) void = {
|
||||
pkgput(fd, "\n");
|
||||
};
|
||||
|
||||
// Go's final test-status action is command-owned: after an explicit ordinary
|
||||
// package request records a setup, build, or run failure, it prints one final
|
||||
// FAIL after the ordered package results. Implicit current-directory testing
|
||||
// and compile-only testing deliberately do not use this action.
|
||||
fn pkgteststatusfail(explicitstatus: bool, compileonly: bool) int = {
|
||||
if (explicitstatus && !compileonly) {
|
||||
pkgputln(os.STDOUT_FILENO, "FAIL");
|
||||
};
|
||||
return 1;
|
||||
};
|
||||
|
||||
fn pkgputhexbyte(fd: i32, value: u8) void = {
|
||||
let digits: str = "0123456789abcdef";
|
||||
let encoded: [2]u8;
|
||||
@@ -1704,7 +1715,8 @@ fn pkgemitinstall(g: *pkggroup) bool = {
|
||||
return true;
|
||||
};
|
||||
|
||||
fn pkgemitgroup(g: *pkggroup, compileonly: bool) bool = {
|
||||
fn pkgemitgroup(g: *pkggroup, compileonly: bool,
|
||||
statusfailed: *bool) bool = {
|
||||
if (g.notests) {
|
||||
pkgput(os.STDOUT_FILENO, "? ");
|
||||
pkgput(os.STDOUT_FILENO, g.dir);
|
||||
@@ -1728,6 +1740,7 @@ fn pkgemitgroup(g: *pkggroup, compileonly: bool) bool = {
|
||||
pkgput(os.STDOUT_FILENO, "\n");
|
||||
};
|
||||
if (!pkgrunok(g)) {
|
||||
*statusfailed = true;
|
||||
pkgreportcommand(os.STDOUT_FILENO, "test", g, &g.runres);
|
||||
return false;
|
||||
};
|
||||
@@ -1740,7 +1753,7 @@ fn pkgemitgroup(g: *pkggroup, compileonly: bool) bool = {
|
||||
// A directory build capture is emitted once, followed by its independently
|
||||
// executed products in their existing byte-sorted group order.
|
||||
fn pkgemitplan(p: *pkgplan, groups: []pkggroup,
|
||||
compileonly: bool, buildonly: bool) i32 = {
|
||||
compileonly: bool, buildonly: bool, statusfailed: *bool) i32 = {
|
||||
let buildcaptures: bool = pkgemitfile(p.buildout, os.STDOUT_FILENO);
|
||||
buildcaptures = pkgemitfile(p.builderr, os.STDERR_FILENO) && buildcaptures;
|
||||
if (!buildcaptures) {
|
||||
@@ -1755,11 +1768,14 @@ fn pkgemitplan(p: *pkgplan, groups: []pkggroup,
|
||||
pkgreportcommand(os.STDERR_FILENO, "build", &groups[i],
|
||||
&p.buildres);
|
||||
};
|
||||
if (!buildonly && !compileonly) { *statusfailed = true; };
|
||||
failed += 1;
|
||||
} else if (buildonly) {
|
||||
void;
|
||||
} else {
|
||||
if (!pkgemitgroup(&groups[i], compileonly)) { failed += 1; };
|
||||
if (!pkgemitgroup(&groups[i], compileonly, statusfailed)) {
|
||||
failed += 1;
|
||||
};
|
||||
if (!compileonly && !pkgemitinstall(&groups[i])) { failed += 1; };
|
||||
};
|
||||
i += 1;
|
||||
@@ -1774,6 +1790,7 @@ export fn packagecommand(args: []str) int = {
|
||||
let jobs: i32 = 1;
|
||||
let afterdash: bool = false;
|
||||
let buildrootseen: bool = false;
|
||||
let explicitstatus: bool = false;
|
||||
if (args.len == PKG_COUNT_MAX) {
|
||||
pkgputln(os.STDERR_FILENO,
|
||||
"wwtest package: package graph is too large");
|
||||
@@ -1943,6 +1960,12 @@ export fn packagecommand(args: []str) int = {
|
||||
i += 2;
|
||||
continue;
|
||||
};
|
||||
if (strings.compare(a, "--ww-explicit-test-target") == 0) {
|
||||
if (explicitstatus || buildonly) { pkgusage(); return 2; };
|
||||
explicitstatus = true;
|
||||
i += 1;
|
||||
continue;
|
||||
};
|
||||
if (strings.compare(a, "--ww-operation") == 0) {
|
||||
if (i + 1 >= args.len || strings.compare(args[i + 1], "build") != 0
|
||||
|| buildonly) {
|
||||
@@ -2052,7 +2075,10 @@ export fn packagecommand(args: []str) int = {
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
if (ds.errors != 0) { return 1; };
|
||||
if (ds.errors != 0) {
|
||||
if (ds.fatal) { return 1; };
|
||||
return pkgteststatusfail(explicitstatus, compileonly);
|
||||
};
|
||||
pkgsortstrings(directroots.paths);
|
||||
directroots.paths = pkgdedup(directroots.paths);
|
||||
i = 0;
|
||||
@@ -2063,7 +2089,7 @@ export fn packagecommand(args: []str) int = {
|
||||
&canonicaloom)) {
|
||||
if (canonicaloom) { return 1; };
|
||||
pkgfailpath(ds.paths[i], "cannot canonicalize package directory");
|
||||
return 1;
|
||||
return pkgteststatusfail(explicitstatus, compileonly);
|
||||
};
|
||||
let canonicalsource: str;
|
||||
if (!pkgstring(&canonicalsource, canonicaldir, "/",
|
||||
@@ -2115,7 +2141,7 @@ export fn packagecommand(args: []str) int = {
|
||||
let pn: str;
|
||||
if (!pkgread(ds.paths[i], &body) || !pkgclause(body, &pn)) {
|
||||
pkgfailpath(ds.paths[i], "invalid or missing package clause");
|
||||
return 1;
|
||||
return pkgteststatusfail(explicitstatus, compileonly);
|
||||
};
|
||||
if (!pkgstring(&s.pkg, pn)) { return 1; };
|
||||
append(srcs, s);
|
||||
@@ -2221,7 +2247,8 @@ export fn packagecommand(args: []str) int = {
|
||||
} else {
|
||||
pkgfailpath(srcs[j].path,
|
||||
"test package must match production package or <package>_test");
|
||||
return 1;
|
||||
return pkgteststatusfail(explicitstatus,
|
||||
compileonly);
|
||||
};
|
||||
};
|
||||
j += 1;
|
||||
@@ -2242,7 +2269,8 @@ export fn packagecommand(args: []str) int = {
|
||||
&& strings.compare(srcs[j].pkg, secondpkg) != 0) {
|
||||
pkgfailpath(srcs[j].path,
|
||||
"test package must match production package or <package>_test");
|
||||
return 1;
|
||||
return pkgteststatusfail(explicitstatus,
|
||||
compileonly);
|
||||
};
|
||||
};
|
||||
j += 1;
|
||||
@@ -2253,7 +2281,8 @@ export fn packagecommand(args: []str) int = {
|
||||
if (family.len == 0) {
|
||||
pkgfailpath(f.path,
|
||||
"external test package has empty base name");
|
||||
return 1;
|
||||
return pkgteststatusfail(explicitstatus,
|
||||
compileonly);
|
||||
};
|
||||
hasexternal = true;
|
||||
externalpkg = firstpkg;
|
||||
@@ -2284,7 +2313,7 @@ export fn packagecommand(args: []str) int = {
|
||||
} else {
|
||||
pkgfailpath(f.path,
|
||||
"test package must match production package or <package>_test");
|
||||
return 1;
|
||||
return pkgteststatusfail(explicitstatus, compileonly);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -2511,6 +2540,7 @@ export fn packagecommand(args: []str) int = {
|
||||
let planout: str = outname;
|
||||
if (buildnull) { planout = ""; };
|
||||
let failed: i32 = 0;
|
||||
let statusfailed: bool = false;
|
||||
i = 0;
|
||||
for (i < plans.len) {
|
||||
if (!pkgsetplanpaths(&plans[i], groups, tmproot, i,
|
||||
@@ -2675,7 +2705,8 @@ export fn packagecommand(args: []str) int = {
|
||||
|
||||
i = 0;
|
||||
for (i < plans.len) {
|
||||
failed += pkgemitplan(&plans[i], groups, compileonly, buildonly);
|
||||
failed += pkgemitplan(&plans[i], groups, compileonly, buildonly,
|
||||
&statusfailed);
|
||||
i += 1;
|
||||
};
|
||||
if (!pkgremoveall(tmproot)) {
|
||||
@@ -2683,6 +2714,9 @@ export fn packagecommand(args: []str) int = {
|
||||
pkgputln(os.STDERR_FILENO, tmproot);
|
||||
failed += 1;
|
||||
};
|
||||
if (statusfailed && explicitstatus && !compileonly) {
|
||||
pkgputln(os.STDOUT_FILENO, "FAIL");
|
||||
};
|
||||
if (failed != 0) { return 1; };
|
||||
return 0;
|
||||
};
|
||||
|
||||
@@ -273,11 +273,11 @@ fn execpackagetests(selfdir: *u8, argv: **u8, argc: i32, start: i32,
|
||||
if (prog == nil) { return 1; };
|
||||
};
|
||||
|
||||
if (start < 0 || start > argc || argc - start > SEP_COUNT_MAX - 10) {
|
||||
if (start < 0 || start > argc || argc - start > SEP_COUNT_MAX - 11) {
|
||||
sepfailsize();
|
||||
return 1;
|
||||
};
|
||||
let cap: i32 = argc - start + 10;
|
||||
let cap: i32 = argc - start + 11;
|
||||
let execargv: []*u8;
|
||||
let argvallocation: ([]*u8 | nomem) = sepallocptrs(cap);
|
||||
match (argvallocation) {
|
||||
@@ -300,6 +300,9 @@ fn execpackagetests(selfdir: *u8, argv: **u8, argc: i32, start: i32,
|
||||
};
|
||||
execargv[n] = "--ww-driver".ptr; n += 1;
|
||||
execargv[n] = selfpath; n += 1;
|
||||
if (!buildonly && !adddot) {
|
||||
execargv[n] = "--ww-explicit-test-target".ptr; n += 1;
|
||||
};
|
||||
if (rootidentity != nil) {
|
||||
execargv[n] = "--ww-root-identity".ptr; n += 1;
|
||||
execargv[n] = rootidentity; n += 1;
|
||||
@@ -9243,6 +9246,9 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
|
||||
cerr("ww: cannot remove temporary directory\n");
|
||||
};
|
||||
};
|
||||
if (compileonly == 0 && emitasm == 0) {
|
||||
os.write(1, "FAIL\n".ptr, 5u64);
|
||||
};
|
||||
return 1;
|
||||
};
|
||||
if (compileonly != 0 || emitasm != 0) {
|
||||
@@ -9292,6 +9298,7 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
|
||||
if (toolbin != nil) {
|
||||
os.free(toolbin: *void, cstrlen(toolbin) + 1u64);
|
||||
};
|
||||
let testfailed: bool = rc != 0;
|
||||
if (rc == 0 && deferredinstall
|
||||
&& sepinstalltestoutput(outp, outstem) != 0) { rc = 1; };
|
||||
if (owntmp) {
|
||||
@@ -9305,6 +9312,7 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
|
||||
if (rc == 0) { rc = 1; };
|
||||
};
|
||||
};
|
||||
if (testfailed) { os.write(1, "FAIL\n".ptr, 5u64); };
|
||||
return rc;
|
||||
};
|
||||
|
||||
@@ -9804,6 +9812,9 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
if (resolved == nil) {
|
||||
cerr("ww test: cannot find ");
|
||||
os.write(2, target, cstrlen(target)); cerr("\n");
|
||||
if (targetindex >= 0 && compileonly == 0 && emitasm == 0) {
|
||||
os.write(1, "FAIL\n".ptr, 5u64);
|
||||
};
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1159,6 +1159,7 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
assert(outc.stderr.len == 0);
|
||||
assert(has(outc.stdout, strings.concat("FAIL ", p7,
|
||||
" [p7] (test exit 1)\n")));
|
||||
assert(strings.hassuffix(outc.stdout, "\nFAIL\n"));
|
||||
|
||||
let timeoutc: []str = [driver("ww"), "test", "-timeout-ms=50", "-I",
|
||||
tree, "-I", failtree, p8];
|
||||
@@ -1180,6 +1181,7 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
assert(outc.stderr.len == 0);
|
||||
assert(has(outc.stdout, strings.concat("FAIL ", p8,
|
||||
" [p8] (test exit 1)\n")));
|
||||
assert(strings.hassuffix(outc.stdout, "\nFAIL\n"));
|
||||
|
||||
let signalc: []str = [driver("ww"), "test", "-I", tree, "-I",
|
||||
failtree, p9];
|
||||
@@ -1199,6 +1201,7 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
assert(outc.stderr.len == 0);
|
||||
assert(has(outc.stdout, strings.concat("FAIL ", p9,
|
||||
" [p9] (test exit 1)\n")));
|
||||
assert(strings.hassuffix(outc.stdout, "\nFAIL\n"));
|
||||
|
||||
// Source-class rejection precedes every captured build or test child, so a
|
||||
// nonempty caller input cannot create a partial persistent generation.
|
||||
@@ -1216,7 +1219,7 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
stdinpath, (60i64 * (time.second: i64)): time.duration, &outw);
|
||||
expectexit(&outc, 1);
|
||||
expectexit(&outw, 1);
|
||||
assert(outc.stdout.len == 0 && outw.stdout.len == 0);
|
||||
assert(same(outc.stdout, "FAIL\n") && same(outw.stdout, "FAIL\n"));
|
||||
assert(same(outc.stderr, outw.stderr));
|
||||
assert(has(outc.stderr,
|
||||
"test package must match production package or <package>_test\n"));
|
||||
@@ -1360,6 +1363,7 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
(90i64 * (time.second: i64)): time.duration, &outc);
|
||||
expectexit(&outc, 1);
|
||||
cwdassertpackage(outc.stdout, "p7-fail", p7, "p7-data", "p7-testdata");
|
||||
assert(strings.hassuffix(outc.stdout, "\nFAIL\n"));
|
||||
assert(same(retainedbytes, readfile(retained)));
|
||||
runcommandinputenvdir(root, "path-retain-fail-ww", retainfailw, wenv,
|
||||
caller, stdinpath,
|
||||
@@ -1735,6 +1739,7 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
(90i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stderr.len == 0);
|
||||
assert(strings.hassuffix(out.stdout, "\nFAIL\n"));
|
||||
assert(has(out.stdout, "partial-success\nok "));
|
||||
assert(!has(out.stdout, "partial-successok "));
|
||||
assert(has(out.stdout, "partial-failure\nFAIL "));
|
||||
@@ -1792,6 +1797,133 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
clean(root);
|
||||
};
|
||||
|
||||
@test fn explicit_test_failure_has_final_status() void = {
|
||||
let root: str = fresh();
|
||||
let fail: str = strings.concat(root, "/fail");
|
||||
let pass: str = strings.concat(root, "/pass");
|
||||
let setup: str = strings.concat(root, "/setup");
|
||||
mkdirall(fail); mkdirall(pass); mkdirall(setup);
|
||||
let failfile: str = strings.concat(fail, "/fail_test.ww");
|
||||
writefile(failfile, strings.concat(
|
||||
"package final_fail;\n",
|
||||
"@test fn fails() void = { assert(false); };\n"));
|
||||
writefile(strings.concat(pass, "/pass_test.ww"), strings.concat(
|
||||
"package final_pass;\n",
|
||||
"@test fn passes() void = { assert(true); };\n"));
|
||||
writefile(strings.concat(setup, "/bad_test.ww"), strings.concat(
|
||||
"package final_setup;\nimport nowhere;\n",
|
||||
"@test fn unreachable() void = { void; };\n"));
|
||||
|
||||
let stages: []str = ["ww", "ww_ww"];
|
||||
let tags: []str = ["c", "ww"];
|
||||
let multiout: []str = ["", ""];
|
||||
let multierr: []str = ["", ""];
|
||||
let rawout: []str = ["", ""];
|
||||
let setuperr: []str = ["", ""];
|
||||
let out: commandout;
|
||||
let i: i32 = 0;
|
||||
for (i < stages.len) {
|
||||
let multiav: []str = [driver(stages[i]), "test", "-j", "2",
|
||||
fail, pass];
|
||||
runcommand(root, strings.concat("final-multi-", tags[i]), multiav,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stderr.len == 0);
|
||||
assert(strings.hassuffix(out.stdout, "\nFAIL\n"));
|
||||
assert(occurrences(out.stdout, "\nFAIL\n") == 1);
|
||||
assert(has(out.stdout, strings.concat("ok ", pass,
|
||||
" [final_pass]\nFAIL\n")));
|
||||
multiout[i] = strings.dup(out.stdout);
|
||||
multierr[i] = strings.dup(out.stderr);
|
||||
|
||||
let filterav: []str = [driver(stages[i]), "test", "-run", "fails",
|
||||
fail];
|
||||
runcommand(root, strings.concat("final-filter-", tags[i]), filterav,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stderr.len == 0);
|
||||
assert(strings.hassuffix(out.stdout, "\nFAIL\n"));
|
||||
|
||||
let listav: []str = [driver(stages[i]), "test", "-list", setup];
|
||||
runcommand(root, strings.concat("final-list-", tags[i]), listav,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(same(out.stdout, "FAIL\n"));
|
||||
assert(has(out.stderr, "cannot find package nowhere\n"));
|
||||
|
||||
let rawav: []str = [driver(stages[i]), "test", failfile];
|
||||
runcommand(root, strings.concat("final-raw-", tags[i]), rawav,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stderr.len == 0);
|
||||
assert(strings.hassuffix(out.stdout, "\nFAIL\n"));
|
||||
assert(!has(out.stdout, strings.concat("FAIL ", fail, " [")));
|
||||
rawout[i] = strings.dup(out.stdout);
|
||||
|
||||
let setupav: []str = [driver(stages[i]), "test", setup];
|
||||
runcommand(root, strings.concat("final-setup-", tags[i]), setupav,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(same(out.stdout, "FAIL\n"));
|
||||
assert(has(out.stderr, "cannot find package nowhere\n"));
|
||||
assert(has(out.stderr, strings.concat("FAIL ", setup,
|
||||
" [final_setup] (build exit 1)\n")));
|
||||
setuperr[i] = strings.dup(out.stderr);
|
||||
|
||||
let missing: str = strings.concat(root, "/missing");
|
||||
let missingav: []str = [driver(stages[i]), "test", missing];
|
||||
runcommand(root, strings.concat("final-missing-", tags[i]), missingav,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(same(out.stdout, "FAIL\n"));
|
||||
assert(same(out.stderr, strings.concat("ww test: cannot find ",
|
||||
missing, "\n")));
|
||||
|
||||
let implicitav: []str = [driver(stages[i]), "test"];
|
||||
runcommanddir(root, strings.concat("final-implicit-", tags[i]), fail,
|
||||
implicitav, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stderr.len == 0);
|
||||
assert(!strings.hassuffix(out.stdout, "\nFAIL\n"));
|
||||
|
||||
let compileav: []str = [driver(stages[i]), "test", "-c", setup];
|
||||
runcommand(root, strings.concat("final-compile-", tags[i]), compileav,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0);
|
||||
assert(has(out.stderr, "cannot find package nowhere\n"));
|
||||
|
||||
let successav: []str = [driver(stages[i]), "test", pass];
|
||||
runcommand(root, strings.concat("final-success-", tags[i]), successav,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stderr.len == 0);
|
||||
assert(!strings.hassuffix(out.stdout, "\nFAIL\n"));
|
||||
|
||||
let retained: str = strings.concat(root, "/retained-", tags[i], ".test");
|
||||
writefile(retained, "prior-test-binary\n");
|
||||
let retainav: []str = [driver(stages[i]), "test", "-o", retained,
|
||||
fail];
|
||||
runcommand(root, strings.concat("final-retained-", tags[i]), retainav,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stderr.len == 0);
|
||||
assert(strings.hassuffix(out.stdout, "\nFAIL\n"));
|
||||
assert(same(readfile(retained), "prior-test-binary\n"));
|
||||
assert(!os.exists(strings.concat(retained, ".new")));
|
||||
assert(!directoryhasfragment(root, ".wwtxn."));
|
||||
i += 1;
|
||||
};
|
||||
assert(same(multiout[0], multiout[1]));
|
||||
assert(same(multierr[0], multierr[1]));
|
||||
assert(same(rawout[0], rawout[1]));
|
||||
assert(same(setuperr[0], setuperr[1]));
|
||||
assert(same(readfile(strings.concat(root, "/retained-c.test")),
|
||||
readfile(strings.concat(root, "/retained-ww.test"))));
|
||||
assert(!directoryhasnew(root));
|
||||
clean(root);
|
||||
};
|
||||
|
||||
@test fn imported_dependency_tests_do_not_leak() void = {
|
||||
let root: str = fresh();
|
||||
let base: str = packagepath("dependency");
|
||||
@@ -4967,7 +5099,7 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
runcommand(root, strings.concat("multidir-failure-", stages[i]), av,
|
||||
(120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0);
|
||||
assert(same(out.stdout, "FAIL\n"));
|
||||
assert(has(out.stderr, "has no exported declaration 'hidden'"));
|
||||
assert(has(out.stderr, strings.concat("FAIL ", alpha,
|
||||
" [alpha] (build exit 1)\n")));
|
||||
@@ -5033,7 +5165,7 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
runcommand(root, strings.concat("isolated-external-", tags[i]),
|
||||
externalav, (120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0);
|
||||
assert(same(out.stdout, "FAIL\n"));
|
||||
assert(has(out.stderr,
|
||||
"has no exported declaration 'hidden'"));
|
||||
assert(has(out.stderr,
|
||||
@@ -5049,7 +5181,7 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
runcommand(root, strings.concat("isolated-same-", tags[i]), sameav,
|
||||
(120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0);
|
||||
assert(same(out.stdout, "FAIL\n"));
|
||||
assert(has(out.stderr, "undefined: missing"));
|
||||
assert(has(out.stderr, strings.concat("FAIL ", badsame,
|
||||
" [badsame] (build exit 1)\n")));
|
||||
@@ -8168,12 +8300,15 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
(120i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
let foldfullwant: str = foldwants[fi];
|
||||
let foldstdoutwant: str = "";
|
||||
if (same(foldmodes[fi], "test")) {
|
||||
foldfullwant = strings.concat(foldwants[fi], "FAIL ",
|
||||
foldtargets[fi], " [", foldpackages[fi],
|
||||
"] (build exit 1)\n");
|
||||
foldstdoutwant = "FAIL\n";
|
||||
};
|
||||
assert(out.stdout.len == 0 && same(out.stderr, foldfullwant));
|
||||
assert(same(out.stdout, foldstdoutwant)
|
||||
&& same(out.stderr, foldfullwant));
|
||||
assert(readfile(compilertrace).len == 0);
|
||||
assert(readfile(assemblertrace).len == 0);
|
||||
assert(readfile(linkertrace).len == 0);
|
||||
|
||||
Reference in New Issue
Block a user