ww test: preserve combined product output order

Go 1.26.5 maps each test binary's stdout and stderr to the same product writer. Teach the captured executor to share one open file description for byte-equal output paths, then make directory test products emit that one ordered capture on coordinator stdout. Build captures and inherited-stdio routes remain unchanged.\n\nKeep the executor mechanism, package policy, native Cstage/WWstage proof, and normative contract together so every commit preserves the observable test-output behavior.
This commit is contained in:
2026-08-20 18:29:09 +09:00
parent b996099270
commit f28843f2f5
7 changed files with 310 additions and 48 deletions

View File

@@ -6039,6 +6039,125 @@ source-class rejection creates no persistent state, Cstage/WWstage diagnostics
and output match, compile-only binaries are equal, persisted artifact bytes do
not change, and no `.new` residue survives.
### 11.26 Implemented combined ordered test-product output
Every coordinator-executed directory-package test product now maps its standard
output and standard error to one product-local open capture. The coordinator
emits that capture on stdout after the product completes, preserving the order
in which writes from either descriptor reach the shared output. Runtime and
child-setup status lines are stdout product diagnostics. WW no longer drains
two captures and emits all stdout before all stderr.
#### Pinned Go evidence and pre-fix WW behavior
The authority is official Go 1.26.5 at commit
`c19862e5f8415b4f24b189d065ed739517c548ba`:
- `runTestActor.Act` selects one output writer for the test action: direct
stdout, JSON conversion, stdout plus a buffer, or a private buffer
([`cmd/go/internal/test/test.go`, lines 14361499](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1436-L1499)).
- It assigns that same writer to both `cmd.Stdout` and `cmd.Stderr`, then runs
the test binary
([`test.go`, lines 16611697](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1661-L1697)).
Success and runtime-failure status text is written through `cmd.Stdout`
([`test.go`, lines 17121769](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1712-L1769)).
- `os/exec.Cmd` documents its shared-writer rule; `childStderr` returns the
already prepared stdout child file when the writers compare equal; and
`Start` installs the returned files as descriptors 1 and 2
([`os/exec/exec.go`, lines 208225, 565606, and 710738](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/os/exec/exec.go#L565-L606)).
- Official command testdata has test mains write only to `os.Stderr`, requires
those bytes on `go test` stdout in buffered and streaming forms, and requires
command stderr to remain empty
([`cmd/go/testdata/script/test_fail_newline.txt`, lines 335 and 4265](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_fail_newline.txt#L3-L65)).
The adjacent orphan-I/O test forwards a descendant's stderr through the test
process and likewise requires the bytes on command stdout
([`test_timeout_stdin.txt`, lines 921 and 3982](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_timeout_stdin.txt#L9-L82)).
The output destination is explicit in the pinned implementation and official
testdata. The ordering conclusion is source-derived: equal writers reuse one
child file or pipe instead of two independently drained pipes. No installed
host Go behavior is authority.
Before this slice, `pkgstartrun` supplied distinct `test.stdout` and
`test.stderr` paths. `exec.start` opened independent files, and
`pkgemitgroup` later emitted the entire stdout file followed by the entire
stderr file on different coordinator descriptors. A direct both-stage probe
that wrote `OUT-1`, `ERR-1`, `OUT-2`, `ERR-2` by alternating syscalls therefore
reported `OUT-1`, `OUT-2` on stdout and `ERR-1`, `ERR-2` on stderr. Cstage and
WWstage had byte-identical pre-fix behavior.
#### Descriptor and product ownership
The reusable captured executor owns only the descriptor mechanism. When
`stdoutpath` and `stderrpath` are byte-equal, it opens the path once with the
existing exclusive mode and obtains a close-on-exec duplicate from the same
open file description. The child maps those owned descriptors to fd 1 and fd 2.
Distinct paths retain independent exclusive opens and their prior behavior.
Path equality here selects an executor resource; it creates no filesystem,
package, import, action, symbol, artifact, or persistence identity.
The package coordinator owns the policy. One `pkggroup` now allocates one
`runoutput`, supplies it for both child paths, reads it once, and writes it to
coordinator stdout. Production, internal, external, recompiled-for-test,
support, generated-main, and test-only actions retain their exact topology and
one directory product still owns one process. Dependency initialization and
test bodies share the product descriptors naturally; no source rewriting or
manual stream forwarding exists.
Individual writes by one process retain syscall order. Descendants inheriting
the descriptors share the same open output, with ordinary kernel scheduling for
concurrent writers. Different products never share a capture. `-j N` may run
products concurrently, but the coordinator still waits for completion and
emits complete captures in canonical group order, so serial and parallel
command byte streams remain identical.
#### Diagnostics, nonexecution, and inherited routes
Successful and failing test-binary bytes, including bytes written to fd 2, are
emitted on stdout. A nonzero product, signal-classified test, timeout, or child
setup failure appends the existing `FAIL DIR [package] (test ...)` status on
stdout. Loader, source, compiler, assembler, linker, build-action, allocation,
capture-read, and cleanup diagnostics keep their established stderr channel;
captured build-plan stdout and stderr remain separate.
`ww build`, directory `ww test -c` (including `-c -o`), and a directory with no
selected test source start no product and allocate no run capture. A published
test binary invoked directly and the raw single-file compatibility route bypass
the coordinator, inherit fd 1 and fd 2 independently, and retain the caller's
stream destinations. `exec.runstdio` is unchanged. An arbitrary
`exec.command` with distinct output paths is also unchanged.
#### Failure, persistence, cleanup, and proof
Input still opens before any output. A merged output open failure creates no
child and no second capture. Duplicate, fork, descriptor-map, `chdir`, and
`execve` failures use the existing checked setup marker and close every owned
descriptor. Product failure does not erase successfully committed compilation;
sibling products retain independent output, process groups, and cleanup. The
coordinator removes its product captures with the existing temporary root, and
rejection or rollback publishes no partial result or `.new` state.
Output routing is request-time process metadata. It changes no unit, export,
assembly, object, archive, generated main, binary, action/storage key, tool
record, stamp, or persistent byte. There is still no test-result cache. Build
workdir format remains `18`, test workdir format remains `19`, and semantic
storage remains `3`.
The executor-level native proof in `test/wwfixture/process/main.ww` requires
distinct captures to stay distinct and equal paths to preserve alternating
fd-1/fd-2 bytes through one file in both compiler stages, including when the
caller closed stdout and stderr. The package owner
`directory_test_execution_working_directory` alternates real writes through
production and test-only dependency initialization; production/internal,
external, combined, recompiled, and test-only products; filters, list, and
no-match execution; success, assertion failure, signal, timeout, and post-build
`chdir` failure; serial/parallel and equivalent-root requests; cold/warm/data-
only persistence; and direct/raw boundaries. It requires Cstage/WWstage output
and diagnostics to match, failure/setup trailers to use stdout, successful
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.
## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins.