ww: preserve original environment for directory tests
This commit is contained in:
@@ -5850,25 +5850,24 @@ coordinator publication paths are absolute, so the child directory cannot
|
||||
reinterpret them. No runtime coordinator-global `chdir` was added; its cwd and
|
||||
`PWD` remain unchanged.
|
||||
|
||||
Each started product also receives a newly allocated run environment. It
|
||||
retains inherited entries in order except exact `TMPDIR=`, `LC_ALL=`, uppercase
|
||||
`PATH=`, and uppercase `PWD=` entries, then appends `LC_ALL=C`, the
|
||||
product-local absolute `TMPDIR`, the selected toolchain `PATH`, and
|
||||
`PWD=<pkggroup.dir>`. Removing prior `PWD` and `PATH` entries reproduces Go's
|
||||
observable last-wins result because WW's executor intentionally preserves
|
||||
duplicates and WW `os.getenv` returns the first one. Case-distinct and malformed
|
||||
entries remain untouched. The product observes exactly one uppercase `PWD`, at
|
||||
the appended position, and one effective uppercase `PATH`; section 11.32 owns
|
||||
the latter rule.
|
||||
Each started product also receives a newly allocated run environment. Section
|
||||
11.36 supersedes this slice's former test-process locale and temporary-directory
|
||||
policy: the vector is now a Go-like original-environment snapshot. It keeps the
|
||||
first occurrence of each normal case-sensitive key, omits later normal
|
||||
duplicates and raw empty entries, preserves nonempty malformed entries, excludes
|
||||
inherited uppercase `PATH` and `PWD`, and then appends the selected toolchain
|
||||
`PATH` and `PWD=<pkggroup.dir>`. Caller `LC_ALL` and `TMPDIR` therefore reach the
|
||||
user test; build-plan tools retain their separate pinned values.
|
||||
|
||||
The vector and its generated strings are product-local, dynamically sized,
|
||||
and published only after every checked allocation succeeds. Partial failure
|
||||
frees only initialized owned storage and never frees borrowed inherited
|
||||
strings. `exec.start` synchronously deep-copies the command before returning,
|
||||
after which the coordinator frees its run vector, generated `TMPDIR`, generated
|
||||
`PATH`, generated `PWD`, generated `-package` argument, and argv vector.
|
||||
Concurrent children therefore hold independent fork snapshots; no shared
|
||||
environment vector or process-global state is mutated.
|
||||
The vector, normalization table, and generated strings are product-local,
|
||||
dynamically sized, and published only after every checked allocation succeeds.
|
||||
Partial failure frees only initialized owned storage and never frees borrowed
|
||||
inherited strings. The normalization table is gone before launch. `exec.start`
|
||||
synchronously deep-copies the command before returning, after which the
|
||||
coordinator frees its run vector, generated `PATH`, generated `PWD`, generated
|
||||
`-package` argument, and argv vector. Concurrent children therefore hold
|
||||
independent fork snapshots; no shared environment vector or process-global
|
||||
state is mutated.
|
||||
|
||||
All dependency initialization occurs inside that product process. A production
|
||||
or test-only dependency reached by package `p` sees `p`'s directory. If the
|
||||
@@ -5931,8 +5930,8 @@ production and test-only dependency initialization; recompiled external
|
||||
self-import; direct/recursive/redundant/absolute/symlink roots; reversed roots
|
||||
and creation order; `-j 1`/parallel execution; filters/list/no-match; failure
|
||||
and timeout; no-test and build paths; `-c`, `-c -o`, running retained tests,
|
||||
direct binaries, and raw single files; exact tool cwd/argv/locale/TMPDIR/PATH;
|
||||
persistent data-only reuse and
|
||||
direct binaries, and raw single files; original test variables and exact tool
|
||||
cwd/argv/locale/TMPDIR/PATH; persistent data-only reuse and
|
||||
artifact/binary identity; and a deterministic post-build directory removal
|
||||
where the affected product reports `ENOENT` while its sibling succeeds.
|
||||
Checked command-global allocation-failure parity remains owned by
|
||||
@@ -7006,11 +7005,11 @@ The environment is synthesized at the three true test-process launch owners:
|
||||
`run_test_bin`, and WWstage `runsingletest` for raw single-file tests. Each
|
||||
uses the selected driver directory's canonical absolute spelling. The first
|
||||
effective uppercase inherited value is the suffix; absent and empty values
|
||||
have no suffix; ordinary uppercase duplicates are removed. Unrelated entries,
|
||||
including case-distinct and malformed names, retain their previous order and
|
||||
bytes. Directory products retain their established appended locale, temporary
|
||||
directory, and final `PWD`; raw tests retain caller cwd, `PWD`, stdin, and split
|
||||
streams.
|
||||
have no suffix; ordinary uppercase duplicates are removed. For directory
|
||||
products, section 11.36 owns unrelated entries: a Go-like original-environment
|
||||
snapshot retains caller locale and temporary-directory values, case-distinct
|
||||
and nonempty malformed entries, and the final package `PWD`. Raw tests retain
|
||||
caller cwd, `PWD`, other environment entries, stdin, and split streams.
|
||||
|
||||
The rule covers internal, external, and combined directory products;
|
||||
dependency initialization; filters and list mode; running retained tests; and
|
||||
@@ -7466,6 +7465,169 @@ large scheduling sets, failure rollback, stage parity, and artifact-byte
|
||||
identity. No persisted-byte contract changed: build workdir format remains
|
||||
`18`, test workdir format remains `19`, and semantic storage format remains `3`.
|
||||
|
||||
### 11.36 Implemented original environment for directory test processes
|
||||
|
||||
Every directory-owned test binary actually started by `ww test` now receives a
|
||||
Go-like snapshot of the caller environment instead of the package build plan's
|
||||
locale and temporary directory. On the supported Unix boundary, the snapshot
|
||||
keeps the first occurrence of every normal case-sensitive `key=value`, omits
|
||||
later normal duplicates and raw empty entries, and preserves nonempty malformed
|
||||
entries in order. The existing selected-toolchain `PATH` and package-directory
|
||||
`PWD` are then appended as the only test-command overrides. Caller `LC_ALL`,
|
||||
`TMPDIR`, empty-valued variables, case-distinct keys, and arbitrary variables
|
||||
therefore reach initialization and test code.
|
||||
|
||||
#### Pinned Go evidence and fact classification
|
||||
|
||||
The sole authority is official Go 1.26.5 at commit
|
||||
`c19862e5f8415b4f24b189d065ed739517c548ba`:
|
||||
|
||||
- `invoke` initializes `cfg.OrigEnv` from
|
||||
`toolchain.FilterEnv(os.Environ())` before command work
|
||||
([`cmd/go/main.go`, lines 290–305](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/main.go#L290-L305)).
|
||||
`FilterEnv` removes only Go's internal toolchain-switch count variable
|
||||
([`cmd/go/internal/toolchain/select.go`, lines 50–59 and 74–85](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/toolchain/select.go#L50-L85));
|
||||
WW has no corresponding switch state.
|
||||
- `OrigEnv` is the startup environment and user binaries during `go test` use
|
||||
it instead of build-tool `CmdEnv`
|
||||
([`cmd/go/internal/cfg/cfg.go`, lines 328–333](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/cfg/cfg.go#L328-L333)).
|
||||
- Unix `copyenv`, `Getenv`, and `Environ` retain the first occurrence of a
|
||||
normal case-sensitive key, clear later duplicates, omit cleared or empty
|
||||
entries from `Environ`, and leave nonempty malformed entries present
|
||||
([`syscall/env_unix.go`, lines 20–50](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/syscall/env_unix.go#L20-L50),
|
||||
[66–84](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/syscall/env_unix.go#L66-L84),
|
||||
and [135–145](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/syscall/env_unix.go#L135-L145)).
|
||||
- `(*runTestActor).Act` clips `cfg.OrigEnv`, applies `AppendPATH` and
|
||||
`AppendPWD`, assigns the result to the package-directory command, and runs it
|
||||
([`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)).
|
||||
The two appenders are defined at
|
||||
[`cmd/go/internal/base/env.go`, lines 15–45](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/base/env.go#L15-L45),
|
||||
and explicit-command duplicate removal prefers their later `PATH` and `PWD`
|
||||
values
|
||||
([`os/exec/exec.go`, lines 1231–1308](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/os/exec/exec.go#L1231-L1308)).
|
||||
- Official `test_env_term.txt` passes an explicitly empty caller `TERM` to a
|
||||
test and requires it to remain empty
|
||||
([lines 1–14](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_env_term.txt#L1-L14)).
|
||||
`test_cache_inputs.txt` changes caller `TESTKEY` and its `TestLookupEnv`
|
||||
requires the arbitrary variable to be present
|
||||
([lines 19–38](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_cache_inputs.txt#L19-L38)
|
||||
and [269–280](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_cache_inputs.txt#L269-L280)).
|
||||
|
||||
Those source rules and official assertions are behavior directly implemented or
|
||||
asserted by pinned Go. That an ordinary caller `LC_ALL`, `TMPDIR`, case-distinct
|
||||
key, or other variable survives unchanged is behavior derived from the pinned
|
||||
pipeline: none is removed or replaced after the original snapshot. First-value
|
||||
normalization, malformed-entry retention, raw-empty omission, and the separation
|
||||
from build-tool `CmdEnv` are directly implemented by the cited source.
|
||||
|
||||
#### Fresh four-axis audit and direct pre-fix measurements
|
||||
|
||||
The bounded audit considered all four permanent axes and selected only this
|
||||
test-runtime difference:
|
||||
|
||||
- **Go-like build:** pinned unresolved relocation handling gives missing
|
||||
`main.main` its dedicated link failure
|
||||
([`cmd/link/internal/ld/errors.go`, lines 45–65](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/errors.go#L45-L65)),
|
||||
asserted by `TestUndefinedRelocErrors`
|
||||
([`cmd/link/internal/ld/ld_test.go`, lines 20–45](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/ld_test.go#L20-L45)).
|
||||
Both WW stages rejected a selected `package main` without `fn main`, emitted
|
||||
identical linker diagnostics, and created no output. This candidate was
|
||||
aligned.
|
||||
- **Go-like test:** a direct `execve` arranger supplied duplicate `LC_ALL`,
|
||||
`TMPDIR`, arbitrary, and `PWD` variables; one empty-valued normal variable; a
|
||||
case-distinct key; repeated nonempty malformed entries; and one raw empty
|
||||
entry. Both stages exited `0` with empty stderr and byte-identical stdout
|
||||
(SHA-256
|
||||
`5b541239c8e9109c512b6ec7b8c59d4b18bf79391096fb14891d8de108786993`).
|
||||
The test reported caller arbitrary/empty/case-distinct values, but reported
|
||||
`LC_ALL` and `TMPDIR` as changed, two visible occurrences of the arbitrary
|
||||
normal key, and the raw empty entry still present. These are directly measured
|
||||
pre-fix WW facts and establish the selected difference.
|
||||
- **Go-like package:** `MultiplePackageError` and the directory scan reject
|
||||
conflicting selected declarations
|
||||
([`go/build/build.go`, lines 538–549](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L538-L549)
|
||||
and [939–967](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L939-L967));
|
||||
`TestMultiplePackageImport` plus official `testdata/multi` asserts the result
|
||||
([`go/build/build_test.go`, lines 105–124](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build_test.go#L105-L124)).
|
||||
Both WW stages rejected an `alpha`/`beta` source directory with byte-identical
|
||||
diagnostics and no output. This candidate was aligned.
|
||||
- **Go-like import:** `unusedImports` and `errorUnusedPkg` require a nonblank
|
||||
renamed import to be used
|
||||
([`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 the alias case
|
||||
([`internal/types/testdata/check/importdecl0/importdecl0a.go`, lines 5–31](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/importdecl0/importdecl0a.go#L5-L31)).
|
||||
Both WW stages rejected an unused local alias for dotted import `fmt` with
|
||||
the same semantic diagnostic and no output. This candidate was aligned.
|
||||
|
||||
The command observations above are directly measured WW behavior. The linked
|
||||
rules are behavior directly implemented or asserted by pinned Go. Applying the
|
||||
original-environment pipeline at WW's directory-product launcher while leaving
|
||||
its raw single-file compatibility route intact is behavior derived from the
|
||||
pinned launch boundary and WW's local input model.
|
||||
|
||||
#### Ownership, final behavior, and preserved boundaries
|
||||
|
||||
`internal/wwpackage.runenv`, called only by `pkgstartrun`, is the semantic owner.
|
||||
It walks the coordinator's inherited vector in order, uses a bounded fallible
|
||||
open-addressed key table to retain the first normal case-sensitive occurrence,
|
||||
omits raw empty entries, retains nonempty malformed entries, and excludes exact
|
||||
uppercase `PATH` and `PWD`. It then appends the existing canonical selected
|
||||
driver `PATH` and `PWD=<pkggroup.dir>`. The table is freed before launch;
|
||||
`exec.start` deep-copies the command, after which the product-local vector and
|
||||
its two generated strings are freed. Concurrent products share no writable
|
||||
environment storage and the coordinator process is never mutated.
|
||||
|
||||
`toolenv` remains the separate build-plan owner. Compiler, assembler, in-driver
|
||||
archiver, linker, support generation, generated-main construction, request
|
||||
scratch, cwd, argv, stdin, diagnostics, and failure precedence are unchanged;
|
||||
those tools still receive their established `LC_ALL=C` and request-private
|
||||
`TMPDIR`. The raw single-file route already preserved caller locale and
|
||||
temporary-directory values and remains outside this directory-owned
|
||||
normalization slice. A directly invoked retained binary still inherits its
|
||||
invoker's concrete environment without coordinator policy.
|
||||
|
||||
Loading and platform source selection are unchanged. Production, internal-test,
|
||||
external-test, recompiled-for-test, support, and generated-main graph/action
|
||||
identity remain separate and unchanged. Canonical dotted package/import
|
||||
identity, declared names, file import bindings, physical-directory metadata,
|
||||
symbols, `.wwi`, source units, assembly, objects, archives, executables, modes,
|
||||
and artifact bytes do not contain the run environment. Imported or dependency
|
||||
initialization code observes the corrected values only inside the selected
|
||||
product process; no physical path or environment value becomes package, import,
|
||||
graph, action, artifact, publication, or persistence identity.
|
||||
|
||||
`ww build`, `ww run`, directory `ww test -c` or `-S`, no-test products, and
|
||||
loading/compiler/linker rejection start no test process, allocate no run
|
||||
environment, and retain their prior diagnostics and outputs. A started test
|
||||
observes the corrected snapshot before success, assertion failure, signal,
|
||||
timeout, interruption, or child-created descendants. Those outcomes continue
|
||||
through the existing process-group, capture, ordered-result, cancellation, and
|
||||
cleanup owners. For a running retained request, private build and run still
|
||||
precede guarded installation: runtime failure publishes nothing and preserves
|
||||
prior bytes, while success installs the same private executable bytes. Producer
|
||||
failure, output guard failure, and cleanup-only failure retain their existing
|
||||
rollback and diagnostic precedence.
|
||||
|
||||
Persistent work records and artifact invalidation are unchanged; test results
|
||||
are never cached. Environment-only changes perform the established warm final
|
||||
link and always run the private test, without changing committed action bytes.
|
||||
No `.new`, `.install`, `.wwtxn.*`, capture, result, process, or request scratch
|
||||
survives its existing cleanup boundary. This runtime-only metadata change alters
|
||||
no persisted-byte contract, so build workdir format remains `18`, test workdir
|
||||
format remains `19`, and semantic storage format remains `3`.
|
||||
|
||||
The expanded WW-native `directory_test_execution_working_directory` observer
|
||||
proves both-stage equality for nonempty and empty-valued variables; caller
|
||||
`LC_ALL` and `TMPDIR`; first-wins normal duplicates; case-distinct and repeated
|
||||
malformed entries; raw-empty omission; package `PWD` and selected-toolchain
|
||||
`PATH`; production and test-only dependency initialization; internal, external,
|
||||
combined, and test-only products; filters/list/no-match; recursive/equivalent
|
||||
selection; serial/parallel scheduling; failure, signal, timeout, and child setup
|
||||
failure; retained success and rollback; cold/warm/data-only persistence;
|
||||
unchanged artifact and binary bytes; build/no-test/compile-only/rejection
|
||||
nonexecution; unchanged raw/direct compatibility; exact tool locale/TMPDIR; and
|
||||
stage, transaction, capture, and workdir cleanup.
|
||||
|
||||
## 12. Candidate architectures and hard-gate decision
|
||||
|
||||
Five candidates were developed as coherent systems, not as feature bins.
|
||||
|
||||
11
docs/spec.md
11
docs/spec.md
@@ -770,6 +770,17 @@ compatibility route. It does not apply to build tools, `ww build`, `ww run`,
|
||||
compile-only or assembly-only test requests, no-test products, or later direct
|
||||
execution of a retained binary.
|
||||
|
||||
An executed directory product otherwise receives the caller's original Unix
|
||||
environment, not the build plan's tool environment. The snapshot keeps the
|
||||
first occurrence of each normal case-sensitive `key=value`, omits later normal
|
||||
duplicates and raw empty entries, and retains every nonempty malformed entry in
|
||||
order. It then applies only the effective `PATH` above and the package `PWD`.
|
||||
Consequently caller `LC_ALL`, `TMPDIR`, empty-valued variables, case-distinct
|
||||
keys, and arbitrary variables remain visible to production or test-only
|
||||
initialization and test bodies. The compiler, assembler, archiver, linker, and
|
||||
coordinator scratch continue to use their separate build-plan locale and
|
||||
temporary 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
|
||||
|
||||
@@ -305,6 +305,16 @@ the private run and install only after success. Compile-only, assembly-only,
|
||||
no-test, rejected, and build requests start no test process and therefore have
|
||||
no test-PATH state.
|
||||
|
||||
For a directory product, the rest of the child vector is a Go-like snapshot of
|
||||
the caller environment rather than the build-tool environment: the first
|
||||
case-sensitive occurrence of every normal `key=value` survives, later normal
|
||||
duplicates and raw empty entries do not, and nonempty malformed entries remain
|
||||
in order. Only the effective `PATH` and package `PWD` are then appended.
|
||||
Caller `LC_ALL`, `TMPDIR`, empty values, case-distinct keys, and arbitrary
|
||||
variables therefore reach dependency initialization and selected tests.
|
||||
Build-plan compiler, assembler, archiver, linker, and scratch environments keep
|
||||
their existing pinned locale and request-private temporary directory.
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user