ww: print final status for explicit test failures

This commit is contained in:
2026-08-21 07:39:54 +09:00
parent b5fb478613
commit 394e46ff2f
7 changed files with 388 additions and 20 deletions

View File

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

View File

@@ -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 10101061](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 10991124](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 11331169 and 11851366](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 14361521](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 16441774](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 22372259](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 22622284](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 36](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 113](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 219290](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 28952918](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L2895-L2918)),
which `GoFilesPackage` constructs
([lines 32443315](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 134205](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 2965](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 1945](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/ld_test.go#L19-L45))
and `issue10978/main.go`
([lines 527](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 538548 and 931967](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L538-L548));
`TestMultiplePackageImport` asserts the rule
([`go/build/build_test.go`, lines 105133](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 706740](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L706-L740));
official `importdecl0` asserts ordinary and renamed forms
([lines 927](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.

View File

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

View File

@@ -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,

View File

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

View File

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

View File

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