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.
|
||||
|
||||
13
docs/spec.md
13
docs/spec.md
@@ -657,14 +657,23 @@ remaining separate actions. Reachable dependency initialization consequently
|
||||
observes the tested product's directory; a dependency tested as its own product
|
||||
observes its own directory.
|
||||
|
||||
The same coordinator-executed product reads standard input from the null
|
||||
device. Its first read observes EOF regardless of the terminal, pipe, or file
|
||||
connected to the invoking command. Each parallel product owns a separate null
|
||||
descriptor, and production or test-only dependency initialization, filtering,
|
||||
listing, no-match execution, failure, and timeout all retain that boundary.
|
||||
Standard input is runtime process metadata and contributes no package, action,
|
||||
artifact, or persistence identity.
|
||||
|
||||
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
|
||||
executing filter or list path. `ww build`, directory `ww test -c` (including
|
||||
`-c -o`), and a no-selected-test directory execute no test child and receive no
|
||||
execution-directory effect. A published test binary invoked directly, and the
|
||||
raw single-file compatibility route, inherit the user's invocation cwd and
|
||||
environment; no package directory is embedded or forced by the binary.
|
||||
raw single-file compatibility route, inherit the user's invocation cwd,
|
||||
environment, and standard input; no package directory or input policy is
|
||||
embedded or forced by the binary.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -242,14 +242,22 @@ creates a separate process using the dependency directory. Equivalent direct,
|
||||
recursive, redundant, absolute, and root-symlink spellings converge before
|
||||
this runtime field is assigned.
|
||||
|
||||
Every such product also receives an independently opened null device as fd 0.
|
||||
Caller terminal, pipe, and file bytes remain with the coordinator; serial and
|
||||
parallel products observe immediate EOF rather than consuming a shared input
|
||||
offset. Production and test-only dependency initialization, filters, list mode,
|
||||
no-match execution, failure, and timeout use that same process boundary.
|
||||
|
||||
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,
|
||||
linker, support, and generated-main commands retain their build-plan cwd and
|
||||
environment. `ww build`, `ww test -c`, `-c -o`, and no-selected-test products
|
||||
start no test child. A published test binary run directly and the raw
|
||||
single-file compatibility path inherit the user's cwd/environment and contain
|
||||
no forced package-directory behavior.
|
||||
environment. Captured directory build plans begin with null stdin, which their
|
||||
inherited-stdio tool descendants retain. `ww build`, `ww test -c`, `-c -o`, and
|
||||
no-selected-test products start no test child. A published test binary run
|
||||
directly and the raw single-file compatibility path inherit the user's
|
||||
cwd/environment/stdin and contain no forced package-directory or input
|
||||
behavior.
|
||||
|
||||
Before package grouping, both directory drivers and the shared recursive
|
||||
coordinator apply Go 1.26.5's filename OS/architecture rule for WW's fixed
|
||||
@@ -319,15 +327,18 @@ independent temporary directories from an unrelated caller cwd and compares
|
||||
Cstage/WWstage output for production/internal/external/combined and every
|
||||
test-only shape; production and test-only dependency initialization;
|
||||
recompiled external self-import; duplicate inherited `PWD`; ordinary data,
|
||||
`testdata`, and relative writes; direct/recursive/redundant/absolute/symlink
|
||||
roots; reversed request and creation order; serial and parallel products;
|
||||
filters, list, and no-match execution; failure and timeout; build/no-test and
|
||||
compile-only paths; direct published binaries and raw single-file execution;
|
||||
build-tool cwd/argv/environment; persistent data-only reuse and artifact bytes;
|
||||
and deterministic post-build child-`chdir` failure isolated from a successful
|
||||
sibling. It pads the inherited environment beyond former fixed observer sizes,
|
||||
requires one appended product `PWD`, and sweeps the persistent workdir for
|
||||
staged residue. Command-global bounded-memory failure remains independently
|
||||
`testdata`, relative writes, and deliberately nonempty caller stdin;
|
||||
direct/recursive/redundant/absolute/symlink roots; reversed request and creation
|
||||
order; serial and parallel products; filters, list, and no-match execution;
|
||||
failure and timeout; build/no-test and compile-only paths; direct published
|
||||
binaries and raw single-file execution; build-tool cwd/argv/environment/stdin;
|
||||
persistent data-only reuse and artifact bytes; input-open failure before capture
|
||||
creation; source-class rejection with empty workdirs; and deterministic
|
||||
post-build child-`chdir` failure isolated from a successful sibling. It pads the
|
||||
inherited environment beyond former fixed observer sizes, requires one appended
|
||||
product `PWD`, requires EOF for captured actions and caller data for inherited-
|
||||
stdio routes, and sweeps the persistent workdir for staged residue.
|
||||
Command-global bounded-memory failure remains independently
|
||||
owned by `allocation_failure_is_command_global`.
|
||||
|
||||
The same package owner contains the focused
|
||||
@@ -499,12 +510,14 @@ target once within one invocation, but two independent `make bootstrap`
|
||||
invocations are not safe to run concurrently and remain mutually exclusive.
|
||||
|
||||
`lib/os/exec` is the sole reusable WW subprocess mechanism. Fixture and package
|
||||
coordinators use its captured asynchronous path. The WW driver directly uses
|
||||
`os.exec.runstdio` for inherited-stdio, inherited-environment, leader-only
|
||||
compiler, assembler, linker, cleanup, run, and single-file-test calls. The
|
||||
local WW `procrun` implementation is deleted. The C bootstrap retains its C
|
||||
process implementation because it cannot consume a WW standard-library
|
||||
module.
|
||||
coordinators use its captured asynchronous path. An empty captured-command
|
||||
`stdinpath` opens the null device; an explicit path supplies controlled input,
|
||||
and either descriptor is installed before exec with checked setup reporting.
|
||||
The WW driver directly uses `os.exec.runstdio` for inherited-stdio,
|
||||
inherited-environment, leader-only compiler, assembler, linker, cleanup, run,
|
||||
and single-file-test calls. The local WW `procrun` implementation is deleted.
|
||||
The C bootstrap retains its C process implementation because it cannot consume
|
||||
a WW standard-library module.
|
||||
|
||||
WW has tokens and an opaque type for future CSP/channel work, but no mature
|
||||
production channel operations, task runtime, or scheduler. No channel,
|
||||
|
||||
Reference in New Issue
Block a user