test: prove captured action stdin isolation
This commit is contained in:
@@ -5928,6 +5928,117 @@ Checked command-global allocation-failure parity remains owned by
|
||||
crosses the former fixed environment-size boundary and verifies that no
|
||||
partial execution environment or staged `.new` state is published.
|
||||
|
||||
### 11.25 Implemented null standard input for captured actions
|
||||
|
||||
Every process launched through WW's captured asynchronous executor now receives
|
||||
an explicit fd 0. An empty `exec.command.stdinpath`, which is the production
|
||||
default, opens the null device read-only; a nonempty value opens that exact path.
|
||||
Consequently every coordinator-executed directory test product observes
|
||||
immediate EOF instead of inheriting and consuming the invoking terminal, pipe,
|
||||
or file. Captured directory build plans and the compiler, assembler, and linker
|
||||
processes that inherit their stdio receive the same noninteractive boundary.
|
||||
|
||||
#### Pinned Go evidence and pre-fix WW behavior
|
||||
|
||||
The authority is official Go 1.26.5 at commit
|
||||
`c19862e5f8415b4f24b189d065ed739517c548ba`:
|
||||
|
||||
- `runTestActor.Act` constructs an `exec.Cmd`, assigns its package directory,
|
||||
environment, stdout, stderr, cancellation, and wait delay, and invokes
|
||||
`Run` without assigning `Stdin`
|
||||
([`cmd/go/internal/test/test.go`, lines 1661–1697](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1661-L1697)).
|
||||
- `Cmd.Stdin` specifies that a nil value reads from `os.DevNull`;
|
||||
`childStdin` opens that device and retains the file for the child; and
|
||||
`Start` installs it as the first child file before process creation
|
||||
([`os/exec/exec.go`, lines 193–206, 531–538, and 710–738](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/os/exec/exec.go#L193-L206)).
|
||||
- The ordinary build-command path has the same default. `Shell.runOut` creates
|
||||
an `exec.Cmd`, assigns output, directory, and environment, and runs it without
|
||||
assigning `Stdin`
|
||||
([`cmd/go/internal/work/shell.go`, lines 600–663](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/shell.go#L600-L663)).
|
||||
- Official `os/exec` tests define a `cat` helper that copies stdin to EOF and
|
||||
require that helper to terminate successfully when run with no `Stdin`
|
||||
assignment
|
||||
([`os/exec/exec_test.go`, lines 201–204 and 416–459](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/os/exec/exec_test.go#L416-L459)).
|
||||
The command testdata separately exercises deliberately supplied stdin-pipe
|
||||
lifetime and closure for orphaned test descendants
|
||||
([`cmd/go/testdata/script/test_timeout_stdin.txt`, lines 1–21 and 39–88](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_timeout_stdin.txt#L1-L21));
|
||||
that script is adjacent stream-lifetime evidence, while the default null-fd
|
||||
conclusion comes directly from the implementation chain above.
|
||||
|
||||
Before this slice, `lib/os/exec.start` redirected only stdout and stderr. A
|
||||
directory driver invoked with a nonempty stdin file passed the same open file
|
||||
description through the top-level inherited-stdio handoff, the package
|
||||
coordinator, its captured builder, and the generated product. Serial products
|
||||
could consume caller data; parallel products raced on the shared file offset;
|
||||
a test that waited for input could wait on an interactive caller. Direct
|
||||
measurement with a one-byte pipe made the same directory `@test` fail under
|
||||
both Cstage and WWstage because its first read returned that byte. The raw
|
||||
single-file route also read the byte and failed, but that route intentionally
|
||||
remains inherited-stdio compatibility behavior.
|
||||
|
||||
#### Descriptor ownership, action boundaries, and concurrency
|
||||
|
||||
`exec.start` validates `stdinpath`, selects `/dev/null` for the empty value, and
|
||||
opens the input before creating either output capture. `safefd` moves all three
|
||||
standard streams above fd 2 when a caller had closed a standard descriptor.
|
||||
After fork, the child maps the owned input to fd 0 before mapping the captures
|
||||
to fd 1 and fd 2; setup failures travel through the existing close-on-exec
|
||||
marker. The parent closes its input copy immediately after fork. Every
|
||||
pre-fork error path closes every successfully acquired descriptor.
|
||||
|
||||
The package coordinator does not read or mutate its own fd 0. Each captured
|
||||
build or run child opens an independent null descriptor, so `-j N` products
|
||||
share neither readable caller data nor an input offset. Production, internal,
|
||||
external, recompiled-for-test, support, and generated-main actions still form
|
||||
the same graph and the one directory product still owns one process. Package
|
||||
and test-only dependency initialization observes EOF inside that process.
|
||||
Filters, list mode, no-match execution, failure, and timeout use the same
|
||||
boundary.
|
||||
|
||||
Standard input is request-time process metadata only. It does not enter
|
||||
canonical dotted identity, declared-name binding, actions, units, exports,
|
||||
symbols, archives, generated main, executable bytes, product names, storage
|
||||
keys, or diagnostics. The source path accepted by `stdinpath` is an executor
|
||||
resource, not a package or filesystem-identity input.
|
||||
|
||||
#### Inherited-stdio routes, failure, persistence, and proof
|
||||
|
||||
`exec.runstdio` remains unchanged. The top-level driver therefore preserves
|
||||
inherited stdin for raw single-file tests and runs, and a published test binary
|
||||
invoked directly receives its invoker's fd 0. Directory `ww test -c`, including
|
||||
`-c -o`, starts no product; the compiled binary acquires no embedded stdin
|
||||
policy. No-selected-test packages likewise start no product. Directory build
|
||||
and compile-only plans are captured actions and therefore noninteractive, but
|
||||
their output, cwd, environment, graph, and publication rules are unchanged.
|
||||
|
||||
Failure to open an explicit input path or the default null device is a
|
||||
pre-fork `termination.ERROR` with positive errno. Because input opens first,
|
||||
neither output capture exists. A child-side `dup2` or close failure is reported
|
||||
through the setup marker, distinguished from exit 127, and follows the existing
|
||||
process-group cleanup path. Test failures, timeouts, post-build directory
|
||||
removal, sibling isolation, transaction rollback, and scratch removal retain
|
||||
their prior contracts.
|
||||
|
||||
No test-result cache exists. Caller stdin bytes never affect source actions or
|
||||
persistent artifacts, and changing only the explicit proof input causes no
|
||||
compile or assemble work beyond the established warm final relink. Build
|
||||
workdir format remains `18`, test workdir format remains `19`, and semantic
|
||||
storage remains `3` because no persisted byte schema changed.
|
||||
|
||||
The focused native owner remains
|
||||
`directory_test_execution_working_directory` in
|
||||
`test/package/package_test.ww`. It now drives every relevant command with a
|
||||
known nonempty input file and requires EOF across all directory action/test
|
||||
variants, production and test-only dependency initialization, serial and
|
||||
parallel products, filters/list/no-match, recursive and equivalent roots,
|
||||
failure, timeout, persistent cold/warm/data-only runs, and post-build child
|
||||
setup failure. Tool wrappers require EOF without changing cwd, argv, locale, or
|
||||
`TMPDIR`. Direct published and raw single-file binaries must instead read the
|
||||
supplied data. The observer also proves input-open failure creates no captures,
|
||||
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.
|
||||
|
||||
## 12. Candidate architectures and hard-gate decision
|
||||
|
||||
Five candidates were developed as coherent systems, not as feature bins.
|
||||
|
||||
Reference in New Issue
Block a user