ww: preserve original environment for directory tests

This commit is contained in:
2026-08-21 12:57:55 +09:00
parent bcc3aa9336
commit 8a5fc8977f
5 changed files with 424 additions and 67 deletions

View File

@@ -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 reinterpret them. No runtime coordinator-global `chdir` was added; its cwd and
`PWD` remain unchanged. `PWD` remain unchanged.
Each started product also receives a newly allocated run environment. It Each started product also receives a newly allocated run environment. Section
retains inherited entries in order except exact `TMPDIR=`, `LC_ALL=`, uppercase 11.36 supersedes this slice's former test-process locale and temporary-directory
`PATH=`, and uppercase `PWD=` entries, then appends `LC_ALL=C`, the policy: the vector is now a Go-like original-environment snapshot. It keeps the
product-local absolute `TMPDIR`, the selected toolchain `PATH`, and first occurrence of each normal case-sensitive key, omits later normal
`PWD=<pkggroup.dir>`. Removing prior `PWD` and `PATH` entries reproduces Go's duplicates and raw empty entries, preserves nonempty malformed entries, excludes
observable last-wins result because WW's executor intentionally preserves inherited uppercase `PATH` and `PWD`, and then appends the selected toolchain
duplicates and WW `os.getenv` returns the first one. Case-distinct and malformed `PATH` and `PWD=<pkggroup.dir>`. Caller `LC_ALL` and `TMPDIR` therefore reach the
entries remain untouched. The product observes exactly one uppercase `PWD`, at user test; build-plan tools retain their separate pinned values.
the appended position, and one effective uppercase `PATH`; section 11.32 owns
the latter rule.
The vector and its generated strings are product-local, dynamically sized, The vector, normalization table, and generated strings are product-local,
and published only after every checked allocation succeeds. Partial failure dynamically sized, and published only after every checked allocation succeeds.
frees only initialized owned storage and never frees borrowed inherited Partial failure frees only initialized owned storage and never frees borrowed
strings. `exec.start` synchronously deep-copies the command before returning, inherited strings. The normalization table is gone before launch. `exec.start`
after which the coordinator frees its run vector, generated `TMPDIR`, generated synchronously deep-copies the command before returning, after which the
`PATH`, generated `PWD`, generated `-package` argument, and argv vector. coordinator frees its run vector, generated `PATH`, generated `PWD`, generated
Concurrent children therefore hold independent fork snapshots; no shared `-package` argument, and argv vector. Concurrent children therefore hold
environment vector or process-global state is mutated. independent fork snapshots; no shared environment vector or process-global
state is mutated.
All dependency initialization occurs inside that product process. A production All dependency initialization occurs inside that product process. A production
or test-only dependency reached by package `p` sees `p`'s directory. If the 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 self-import; direct/recursive/redundant/absolute/symlink roots; reversed roots
and creation order; `-j 1`/parallel execution; filters/list/no-match; failure 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, 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; direct binaries, and raw single files; original test variables and exact tool
persistent data-only reuse and cwd/argv/locale/TMPDIR/PATH; persistent data-only reuse and
artifact/binary identity; and a deterministic post-build directory removal artifact/binary identity; and a deterministic post-build directory removal
where the affected product reports `ENOENT` while its sibling succeeds. where the affected product reports `ENOENT` while its sibling succeeds.
Checked command-global allocation-failure parity remains owned by 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 `run_test_bin`, and WWstage `runsingletest` for raw single-file tests. Each
uses the selected driver directory's canonical absolute spelling. The first uses the selected driver directory's canonical absolute spelling. The first
effective uppercase inherited value is the suffix; absent and empty values effective uppercase inherited value is the suffix; absent and empty values
have no suffix; ordinary uppercase duplicates are removed. Unrelated entries, have no suffix; ordinary uppercase duplicates are removed. For directory
including case-distinct and malformed names, retain their previous order and products, section 11.36 owns unrelated entries: a Go-like original-environment
bytes. Directory products retain their established appended locale, temporary snapshot retains caller locale and temporary-directory values, case-distinct
directory, and final `PWD`; raw tests retain caller cwd, `PWD`, stdin, and split and nonempty malformed entries, and the final package `PWD`. Raw tests retain
streams. caller cwd, `PWD`, other environment entries, stdin, and split streams.
The rule covers internal, external, and combined directory products; The rule covers internal, external, and combined directory products;
dependency initialization; filters and list mode; running retained tests; and 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 identity. No persisted-byte contract changed: build workdir format remains
`18`, test workdir format remains `19`, and semantic storage format remains `3`. `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 290305](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 5059 and 7485](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 328333](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 2050](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/syscall/env_unix.go#L20-L50),
[6684](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/syscall/env_unix.go#L66-L84),
and [135145](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 16611697](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 1545](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 12311308](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 114](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 1938](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_cache_inputs.txt#L19-L38)
and [269280](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 4565](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 2045](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 538549](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L538-L549)
and [939967](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 105124](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 706740](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 531](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 ## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins. Five candidates were developed as coherent systems, not as feature bins.

View File

@@ -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 compile-only or assembly-only test requests, no-test products, or later direct
execution of a retained binary. 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 The same coordinator-executed product reads standard input from the null
device. Its first read observes EOF regardless of the terminal, pipe, or file 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 connected to the invoking command. Each parallel product owns a separate null

View File

@@ -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, rejected, and build requests start no test process and therefore have
no test-PATH state. 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. 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 Caller terminal, pipe, and file bytes remain with the coordinator; serial and
parallel products observe immediate EOF rather than consuming a shared input parallel products observe immediate EOF rather than consuming a shared input

View File

@@ -116,6 +116,11 @@ fn pkgallocstrs(cap: i32) ([]str | nomem) = {
return value; return value;
}; };
fn pkgalloci32(cap: i32) ([]i32 | nomem) = {
let value: []i32 = alloc([], cap: u64)?;
return value;
};
fn pkgallocbytes(cap: i32) ([]u8 | nomem) = { fn pkgallocbytes(cap: i32) ([]u8 | nomem) = {
let value: []u8 = alloc([], cap: u64)?; let value: []u8 = alloc([], cap: u64)?;
return value; return value;
@@ -268,11 +273,81 @@ fn pkgfreestrs(values: []str) void = {
}; };
}; };
// Go's AppendPATH and AppendPWD append, then os/exec keeps the last duplicate fn pkgenvkeylen(entry: str) i32 = {
// values. WW's executor preserves duplicates, so discard earlier exact entries let i: i32 = 0;
// and materialize the selected driver's sibling directory first in PATH. for (i < entry.len) {
fn runenv(tmpdir: str, pwd: str, builder: str, out: *[]str, tmpowned: *str, if (entry[i] == '=') { return i; };
pathowned: *str, pwdowned: *str) bool = { i += 1;
};
return -1;
};
fn pkgenvkeyequal(a: str, an: i32, b: str, bn: i32) bool = {
if (an != bn) { return false; };
let i: i32 = 0;
for (i < an) {
if (a[i] != b[i]) { return false; };
i += 1;
};
return true;
};
fn pkgenvkeyis(entry: str, n: i32, name: str) bool = {
return pkgenvkeyequal(entry, n, name, name.len);
};
fn pkgenvhash(entry: str, n: i32) u64 = {
let h: u64 = 14695981039346656037u64;
let i: i32 = 0;
for (i < n) {
h = h ^ (entry[i]: u64);
h = h * 1099511628211u64;
i += 1;
};
return h;
};
fn pkgenvtablecap(entries: i32) i32 = {
if (entries == 0) { return 0; };
if (entries > PKG_COUNT_MAX / 2) { return -1; };
let need: i32 = entries * 2;
let cap: i32 = PKG_INITIAL_CAP;
for (cap < need) {
if (cap > PKG_COUNT_MAX / 2) { return -1; };
cap *= 2;
};
return cap;
};
// Go snapshots user-test variables through Unix os.Environ before appending
// PATH and PWD. WW's raw environment walker and executor do no such duplicate
// normalization, so the launch owner must materialize that snapshot itself.
fn pkgenvfirst(slots: []i32, inherited: []str, at: i32, n: i32) bool = {
let slot: i32 = (pkgenvhash(inherited[at], n)
% (slots.len: u64)): i32;
let probes: i32 = 0;
for (probes < slots.len) {
let prior: i32 = slots[slot];
if (prior < 0) {
slots[slot] = at;
return true;
};
let priorn: i32 = pkgenvkeylen(inherited[prior]);
if (pkgenvkeyequal(inherited[at], n,
inherited[prior], priorn)) {
return false;
};
slot += 1;
if (slot == slots.len) { slot = 0; };
probes += 1;
};
return false;
};
// Go's appended PATH and PWD survive os/exec's later-value selection. WW's
// executor preserves its concrete vector, so inherited copies are excluded.
fn runenv(pwd: str, builder: str, out: *[]str, pathowned: *str,
pwdowned: *str) bool = {
let toolbin: str; let toolbin: str;
let oom: bool = false; let oom: bool = false;
if (!pkgcanonicaldir(pkgdirname(builder), &toolbin, &oom)) { if (!pkgcanonicaldir(pkgdirname(builder), &toolbin, &oom)) {
@@ -283,35 +358,71 @@ fn runenv(tmpdir: str, pwd: str, builder: str, out: *[]str, tmpowned: *str,
return false; return false;
}; };
let inherited: []str = os.getenvs(); let inherited: []str = os.getenvs();
if (inherited.len > PKG_COUNT_MAX - 4) { if (inherited.len > PKG_COUNT_MAX - 2) {
pkgputln(os.STDERR_FILENO, pkgputln(os.STDERR_FILENO,
"wwtest package: package graph is too large"); "wwtest package: package graph is too large");
return false; return false;
}; };
let allocation: ([]str | nomem) = pkgallocstrs(inherited.len + 4); let allocation: ([]str | nomem) = pkgallocstrs(inherited.len + 2);
let env: []str; let env: []str;
match (allocation) { match (allocation) {
case let value: []str => env = value; case let value: []str => env = value;
case nomem => { case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory"); pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return false; return false;
};
}; };
}; let keyed: i32 = 0;
let i: i32 = 0; let i: i32 = 0;
for (i < inherited.len) { for (i < inherited.len) {
if (!strings.hasprefix(inherited[i], "TMPDIR=") let n: i32 = pkgenvkeylen(inherited[i]);
&& !strings.hasprefix(inherited[i], "LC_ALL=") if (n >= 0 && !pkgenvkeyis(inherited[i], n, "PATH")
&& !strings.hasprefix(inherited[i], "PATH=") && !pkgenvkeyis(inherited[i], n, "PWD")) {
&& !strings.hasprefix(inherited[i], "PWD=")) { keyed += 1;
};
i += 1;
};
let tablecap: i32 = pkgenvtablecap(keyed);
if (tablecap < 0) {
pkgputln(os.STDERR_FILENO,
"wwtest package: package graph is too large");
pkgfreestrs(env);
return false;
};
let slots: []i32;
slots.ptr = nil: *i32;
slots.len = 0;
slots.cap = 0;
if (tablecap != 0) {
let tableallocation: ([]i32 | nomem) = pkgalloci32(tablecap);
match (tableallocation) {
case let value: []i32 => slots = value;
case nomem => {
pkgputln(os.STDERR_FILENO,
"wwtest package: out of memory");
pkgfreestrs(env);
return false;
};
};
slots.len = tablecap;
i = 0;
for (i < slots.len) { slots[i] = -1; i += 1; };
};
i = 0;
for (i < inherited.len) {
let n: i32 = pkgenvkeylen(inherited[i]);
if (n < 0) {
if (inherited[i].len != 0) { append(env, inherited[i]); };
} else if (!pkgenvkeyis(inherited[i], n, "PATH")
&& !pkgenvkeyis(inherited[i], n, "PWD")
&& pkgenvfirst(slots, inherited, i, n)) {
append(env, inherited[i]); append(env, inherited[i]);
}; };
i += 1; i += 1;
}; };
append(env, "LC_ALL=C"); if (slots.ptr != nil) {
let tmpenv: str; os.free(slots.ptr: *void,
if (!pkgstring(&tmpenv, "TMPDIR=", tmpdir)) { (slots.cap: u64) * (size(i32): u64));
pkgfreestrs(env);
return false;
}; };
let pathenv: str; let pathenv: str;
let pathok: bool = false; let pathok: bool = false;
@@ -327,29 +438,24 @@ fn runenv(tmpdir: str, pwd: str, builder: str, out: *[]str, tmpowned: *str,
case void => pathok = pkgstring(&pathenv, "PATH=", toolbin); case void => pathok = pkgstring(&pathenv, "PATH=", toolbin);
}; };
if (!pathok) { if (!pathok) {
pkgfreeownedstr(tmpenv);
pkgfreestrs(env); pkgfreestrs(env);
return false; return false;
}; };
let pwdenv: str; let pwdenv: str;
if (!pkgstring(&pwdenv, "PWD=", pwd)) { if (!pkgstring(&pwdenv, "PWD=", pwd)) {
pkgfreeownedstr(tmpenv);
pkgfreeownedstr(pathenv); pkgfreeownedstr(pathenv);
pkgfreestrs(env); pkgfreestrs(env);
return false; return false;
}; };
append(env, tmpenv);
append(env, pathenv); append(env, pathenv);
append(env, pwdenv); append(env, pwdenv);
*out = env; *out = env;
*tmpowned = tmpenv;
*pathowned = pathenv; *pathowned = pathenv;
*pwdowned = pwdenv; *pwdowned = pwdenv;
return true; return true;
}; };
fn freerunenv(env: []str, tmpowned: str, pathowned: str, pwdowned: str) void = { fn freerunenv(env: []str, pathowned: str, pwdowned: str) void = {
pkgfreeownedstr(tmpowned);
pkgfreeownedstr(pathowned); pkgfreeownedstr(pathowned);
pkgfreeownedstr(pwdowned); pkgfreeownedstr(pwdowned);
pkgfreestrs(env); pkgfreestrs(env);
@@ -1624,11 +1730,9 @@ fn pkgstartrun(g: *pkggroup, filters: []str, timeoutarg: str,
let i: i32 = 0; let i: i32 = 0;
for (i < filters.len) { append(ra, filters[i]); i += 1; }; for (i < filters.len) { append(ra, filters[i]); i += 1; };
let env: []str; let env: []str;
let tmpowned: str;
let pathowned: str; let pathowned: str;
let pwdowned: str; let pwdowned: str;
if (!runenv(g.root, g.dir, builder, &env, &tmpowned, if (!runenv(g.dir, builder, &env, &pathowned, &pwdowned)) {
&pathowned, &pwdowned)) {
pkgfreeownedstr(packagearg); pkgfreeownedstr(packagearg);
pkgfreestrs(ra); pkgfreestrs(ra);
return false; return false;
@@ -1644,7 +1748,7 @@ fn pkgstartrun(g: *pkggroup, filters: []str, timeoutarg: str,
rcmd.deadline.nsec = 0i64; rcmd.deadline.nsec = 0i64;
rcmd.grace = 0i64: time.duration; rcmd.grace = 0i64: time.duration;
exec.start(h, &rcmd); exec.start(h, &rcmd);
freerunenv(env, tmpowned, pathowned, pwdowned); freerunenv(env, pathowned, pwdowned);
pkgfreeownedstr(packagearg); pkgfreeownedstr(packagearg);
pkgfreestrs(ra); pkgfreestrs(ra);
return true; return true;

View File

@@ -633,11 +633,16 @@ fn hexbytes(value: str) str = {
fn cwdtestenv(stage: str) []str = { fn cwdtestenv(stage: str) []str = {
let inherited: []str = os.getenvs(); let inherited: []str = os.getenvs();
let env: []str = alloc([], (inherited.len + 10): u64)!; let env: []str = alloc([], (inherited.len + 24): u64)!;
let i: i32 = 0; let i: i32 = 0;
for (i < inherited.len) { for (i < inherited.len) {
if (!strings.hasprefix(inherited[i], "PWD=") if (!strings.hasprefix(inherited[i], "PWD=")
&& !strings.hasprefix(inherited[i], "PATH=") && !strings.hasprefix(inherited[i], "PATH=")
&& !strings.hasprefix(inherited[i], "LC_ALL=")
&& !strings.hasprefix(inherited[i], "TMPDIR=")
&& !strings.hasprefix(inherited[i], "WW_CWD_ORIGINAL=")
&& !strings.hasprefix(inherited[i], "WW_CWD_EMPTY=")
&& !strings.hasprefix(inherited[i], "lc_all=")
&& !strings.hasprefix(inherited[i], "WW_W6C=") && !strings.hasprefix(inherited[i], "WW_W6C=")
&& !strings.hasprefix(inherited[i], "WW_W6A=") && !strings.hasprefix(inherited[i], "WW_W6A=")
&& !strings.hasprefix(inherited[i], "WW_W6L=") && !strings.hasprefix(inherited[i], "WW_W6L=")
@@ -649,6 +654,14 @@ fn cwdtestenv(stage: str) []str = {
}; };
append(env, "PATH=/ww-path-inherited-first"); append(env, "PATH=/ww-path-inherited-first");
append(env, "PWD=/ww-cwd-inherited-first"); append(env, "PWD=/ww-cwd-inherited-first");
append(env, "LC_ALL=C.UTF-8");
append(env, "TMPDIR=/ww-caller-tmp-first");
append(env, "WW_CWD_ORIGINAL=first");
append(env, "WW_CWD_EMPTY=");
append(env, "lc_all=case-kept");
append(env, "WW_CWD_MALFORMED");
append(env, "");
append(env, "WW_CWD_MALFORMED");
if (same(stage, "ww")) { if (same(stage, "ww")) {
append(env, strings.concat("WW_W6C=", driver("w6c"))); append(env, strings.concat("WW_W6C=", driver("w6c")));
append(env, strings.concat("WW_W6A=", driver("w6a"))); append(env, strings.concat("WW_W6A=", driver("w6a")));
@@ -660,6 +673,9 @@ fn cwdtestenv(stage: str) []str = {
append(env, strings.concat("WW_W6L=", driver("w6l_ww"))); append(env, strings.concat("WW_W6L=", driver("w6l_ww")));
}; };
append(env, strings.concat("WW_SRCLIB=", repo(), "/lib")); append(env, strings.concat("WW_SRCLIB=", repo(), "/lib"));
append(env, "LC_ALL=C");
append(env, "TMPDIR=/ww-caller-tmp-last");
append(env, "WW_CWD_ORIGINAL=last");
append(env, "PATH=/ww-path-inherited-last"); append(env, "PATH=/ww-path-inherited-last");
append(env, "PWD=/ww-cwd-inherited-last"); append(env, "PWD=/ww-cwd-inherited-last");
return env; return env;
@@ -679,6 +695,42 @@ fn cwdassertcallerpath(text: str, label: str) void = {
cwdassertpath(text, label, "/ww-path-inherited-first", "2"); cwdassertpath(text, label, "/ww-path-inherited-first", "2");
}; };
fn cwdassertenvvalues(text: str, label: str) void = {
assert(has(text, strings.concat(label, " LC_ALL=C.UTF-8\n")));
assert(has(text, strings.concat(label,
" TMPDIR=/ww-caller-tmp-first\n")));
assert(has(text, strings.concat(label,
" WW_CWD_ORIGINAL=first\n")));
assert(has(text, strings.concat(label, " WW_CWD_EMPTY=\n")));
assert(has(text, strings.concat(label, " lc_all=case-kept\n")));
};
fn cwdassertdirectoryenv(text: str, label: str) void = {
cwdassertenvvalues(text, label);
assert(has(text, strings.concat(label, " LC_ALL-count=1\n")));
assert(has(text, strings.concat(label, " TMPDIR-count=1\n")));
assert(has(text, strings.concat(label,
" WW_CWD_ORIGINAL-count=1\n")));
assert(has(text, strings.concat(label,
" WW_CWD_EMPTY-count=1\n")));
assert(has(text, strings.concat(label, " lc_all-count=1\n")));
assert(has(text, strings.concat(label, " malformed-count=2\n")));
assert(has(text, strings.concat(label, " empty-entry-count=0\n")));
};
fn cwdassertcallerenv(text: str, label: str) void = {
cwdassertenvvalues(text, label);
assert(has(text, strings.concat(label, " LC_ALL-count=2\n")));
assert(has(text, strings.concat(label, " TMPDIR-count=2\n")));
assert(has(text, strings.concat(label,
" WW_CWD_ORIGINAL-count=2\n")));
assert(has(text, strings.concat(label,
" WW_CWD_EMPTY-count=1\n")));
assert(has(text, strings.concat(label, " lc_all-count=1\n")));
assert(has(text, strings.concat(label, " malformed-count=2\n")));
assert(has(text, strings.concat(label, " empty-entry-count=1\n")));
};
fn cwdassertrecord(text: str, label: str, cwd: str, pwd: str, fn cwdassertrecord(text: str, label: str, cwd: str, pwd: str,
pwdcount: str, pwdlast: str, data: str, testdata: str, pwdcount: str, pwdlast: str, data: str, testdata: str,
input: str) void = { input: str) void = {
@@ -724,6 +776,7 @@ fn cwdassertpackage(text: str, label: str, dir: str, data: str,
testdata: str) void = { testdata: str) void = {
cwdassertrecord(text, label, dir, dir, "1", "yes", data, testdata, cwdassertrecord(text, label, dir, dir, "1", "yes", data, testdata,
"eof"); "eof");
cwdassertdirectoryenv(text, label);
cwdasserttestpath(text, label); cwdasserttestpath(text, label);
cwdassertmergedstreams(text, label, dir); cwdassertmergedstreams(text, label, dir);
}; };
@@ -795,10 +848,10 @@ fn cwdwritedata(dir: str, label: str) void = {
clean(root); clean(root);
}; };
// Directory-package execution is one product-level runtime property. This // Directory-package execution context is one product-level runtime property.
// fixture keeps every source tree disposable while covering the variant, // This fixture keeps every source tree disposable while covering the variant,
// dependency, routing, concurrency, persistence, and failure boundaries that // dependency, environment, routing, concurrency, persistence, and failure
// could otherwise accidentally derive a cwd from an action or request name. // boundaries that could otherwise leak build-plan or caller process state.
@test fn directory_test_execution_working_directory() void = { @test fn directory_test_execution_working_directory() void = {
let root: str = fresh(); let root: str = fresh();
let caller: str = strings.concat(root, "/caller"); let caller: str = strings.concat(root, "/caller");
@@ -872,12 +925,22 @@ fn cwdwritedata(dir: str, label: str) void = {
"};\n", "};\n",
"fn put(value: str) void = { putfd(os.STDOUT_FILENO, value); };\n", "fn put(value: str) void = { putfd(os.STDOUT_FILENO, value); };\n",
"fn puterr(value: str) void = { putfd(os.STDERR_FILENO, value); };\n", "fn puterr(value: str) void = { putfd(os.STDERR_FILENO, value); };\n",
"fn putcount(value: i32) void = {\n",
" if (value == 0) { put(\"0\\n\"); } else { if (value == 1) {\n",
" put(\"1\\n\"); } else { if (value == 2) { put(\"2\\n\");\n",
" } else { put(\"many\\n\"); }; }; };\n",
"};\n",
"export fn streams(label: str, context: str) void = {\n", "export fn streams(label: str, context: str) void = {\n",
" put(strings.concat(\"STREAM \", label, \" out-1 cwd=\", context, \"\\n\"));\n", " put(strings.concat(\"STREAM \", label, \" out-1 cwd=\", context, \"\\n\"));\n",
" puterr(strings.concat(\"STREAM \", label, \" err-1 cwd=\", context, \"\\n\"));\n", " puterr(strings.concat(\"STREAM \", label, \" err-1 cwd=\", context, \"\\n\"));\n",
" put(strings.concat(\"STREAM \", label, \" out-2 cwd=\", context, \"\\n\"));\n", " put(strings.concat(\"STREAM \", label, \" out-2 cwd=\", context, \"\\n\"));\n",
" puterr(strings.concat(\"STREAM \", label, \" err-2 cwd=\", context, \"\\n\"));\n", " puterr(strings.concat(\"STREAM \", label, \" err-2 cwd=\", context, \"\\n\"));\n",
"};\n"); "};\n");
let envsource: str = strings.concat(
" put(label); put(\" LC_ALL=\");\n match (os.getenv(\"LC_ALL\")) {\n case let value: str => put(value); case void => put(\"<unset>\");\n }; put(\"\\n\");\n put(label); put(\" TMPDIR=\");\n match (os.getenv(\"TMPDIR\")) {\n case let value: str => put(value); case void => put(\"<unset>\");\n }; put(\"\\n\");\n put(label); put(\" WW_CWD_ORIGINAL=\");\n match (os.getenv(\"WW_CWD_ORIGINAL\")) {\n case let value: str => put(value); case void => put(\"<unset>\");\n }; put(\"\\n\");\n put(label); put(\" WW_CWD_EMPTY=\");\n match (os.getenv(\"WW_CWD_EMPTY\")) {\n case let value: str => put(value); case void => put(\"<unset>\");\n }; put(\"\\n\");\n put(label); put(\" lc_all=\");\n match (os.getenv(\"lc_all\")) {\n case let value: str => put(value); case void => put(\"<unset>\");\n }; put(\"\\n\");\n",
" let lccount: i32 = 0; let tmpcount: i32 = 0;\n let originalcount: i32 = 0; let emptyvaluecount: i32 = 0;\n let lowercount: i32 = 0; let malformedcount: i32 = 0;\n let emptyentrycount: i32 = 0;\n i = 0; for (i < env.len) {\n if (strings.hasprefix(env[i], \"LC_ALL=\")) { lccount += 1; };\n if (strings.hasprefix(env[i], \"TMPDIR=\")) { tmpcount += 1; };\n if (strings.hasprefix(env[i], \"WW_CWD_ORIGINAL=\")) { originalcount += 1; };\n if (strings.hasprefix(env[i], \"WW_CWD_EMPTY=\")) { emptyvaluecount += 1; };\n if (strings.hasprefix(env[i], \"lc_all=\")) { lowercount += 1; };\n if (strings.compare(env[i], \"WW_CWD_MALFORMED\") == 0) { malformedcount += 1; };\n if (env[i].len == 0) { emptyentrycount += 1; };\n i += 1;\n };\n",
" put(label); put(\" PWD-count=\"); putcount(count);\n put(label); put(\" PWD-last=\");\n if (last == env.len - 1) { put(\"yes\\n\"); } else { put(\"no\\n\"); };\n put(label); put(\" PATH-count=\"); putcount(pathcount);\n",
" put(label); put(\" LC_ALL-count=\"); putcount(lccount);\n put(label); put(\" TMPDIR-count=\"); putcount(tmpcount);\n put(label); put(\" WW_CWD_ORIGINAL-count=\"); putcount(originalcount);\n put(label); put(\" WW_CWD_EMPTY-count=\"); putcount(emptyvaluecount);\n put(label); put(\" lc_all-count=\"); putcount(lowercount);\n put(label); put(\" malformed-count=\"); putcount(malformedcount);\n put(label); put(\" empty-entry-count=\"); putcount(emptyentrycount);\n");
writefile(strings.concat(probe, "/probe.ww"), strings.concat( writefile(strings.concat(probe, "/probe.ww"), strings.concat(
"package probe;\n", "package probe;\n",
"import os;\n", "import os;\n",
@@ -902,16 +965,7 @@ fn cwdwritedata(dir: str, label: str) void = {
" if (body.len == 0 || body[body.len - 1] != '\\n') { put(\"\\n\"); };\n", " if (body.len == 0 || body[body.len - 1] != '\\n') { put(\"\\n\"); };\n",
"};\n", "};\n",
"export fn run(label: str) void = {\n let buf: [4096]u8;\n let n: i64 = os.getcwd(&buf[0], size([4096]u8)); assert(n > 1i64);\n let cwd: str; cwd.ptr = &buf[0]; cwd.len = (n - 1i64): i32;\n streams(label, cwd);\n put(label); put(\" cwd=\"); put(cwd); put(\"\\n\");\n put(label); put(\" PWD=\");\n match (os.getenv(\"PWD\")) {\n case let value: str => put(value); case void => put(\"<unset>\");\n }; put(\"\\n\");\n put(label); put(\" PATH=\");\n match (os.getenv(\"PATH\")) {\n case let value: str => put(value); case void => put(\"<unset>\");\n }; put(\"\\n\");\n let env: []str = os.getenvs(); let count: i32 = 0; let pathcount: i32 = 0; let last: i32 = -1;\n let i: i32 = 0; for (i < env.len) {\n if (strings.hasprefix(env[i], \"PWD=\")) { count += 1; last = i; };\n if (strings.hasprefix(env[i], \"PATH=\")) { pathcount += 1; };\n i += 1;\n };\n", "export fn run(label: str) void = {\n let buf: [4096]u8;\n let n: i64 = os.getcwd(&buf[0], size([4096]u8)); assert(n > 1i64);\n let cwd: str; cwd.ptr = &buf[0]; cwd.len = (n - 1i64): i32;\n streams(label, cwd);\n put(label); put(\" cwd=\"); put(cwd); put(\"\\n\");\n put(label); put(\" PWD=\");\n match (os.getenv(\"PWD\")) {\n case let value: str => put(value); case void => put(\"<unset>\");\n }; put(\"\\n\");\n put(label); put(\" PATH=\");\n match (os.getenv(\"PATH\")) {\n case let value: str => put(value); case void => put(\"<unset>\");\n }; put(\"\\n\");\n let env: []str = os.getenvs(); let count: i32 = 0; let pathcount: i32 = 0; let last: i32 = -1;\n let i: i32 = 0; for (i < env.len) {\n if (strings.hasprefix(env[i], \"PWD=\")) { count += 1; last = i; };\n if (strings.hasprefix(env[i], \"PATH=\")) { pathcount += 1; };\n i += 1;\n };\n",
" put(label); put(\" PWD-count=\");\n", envsource,
" if (count == 0) { put(\"0\\n\"); } else { if (count == 1) {\n",
" put(\"1\\n\"); } else { if (count == 2) { put(\"2\\n\");\n",
" } else { put(\"many\\n\"); }; }; };\n",
" put(label); put(\" PWD-last=\");\n",
" if (last == env.len - 1) { put(\"yes\\n\"); } else { put(\"no\\n\"); };\n",
" put(label); put(\" PATH-count=\");\n",
" if (pathcount == 0) { put(\"0\\n\"); } else { if (pathcount == 1) {\n",
" put(\"1\\n\"); } else { if (pathcount == 2) { put(\"2\\n\");\n",
" } else { put(\"many\\n\"); }; }; };\n",
" emitfile(label, \"data\", \"data.txt\");\n", " emitfile(label, \"data\", \"data.txt\");\n",
" emitfile(label, \"testdata\", \"testdata/input.txt\");\n", " emitfile(label, \"testdata\", \"testdata/input.txt\");\n",
" let input: [1]u8;\n let inputn: i64 = os.read(os.STDIN_FILENO, &input[0], 1u64);\n put(label); put(\" stdin=\");\n if (inputn == 0i64) { put(\"eof\\n\"); } else {\n if (inputn == 1i64) { put(\"data\\n\"); } else { put(\"<error>\\n\"); };\n };\n", " let input: [1]u8;\n let inputn: i64 = os.read(os.STDIN_FILENO, &input[0], 1u64);\n put(label); put(\" stdin=\");\n if (inputn == 0i64) { put(\"eof\\n\"); } else {\n if (inputn == 1i64) { put(\"data\\n\"); } else { put(\"<error>\\n\"); };\n };\n",
@@ -1078,6 +1132,7 @@ fn cwdwritedata(dir: str, label: str) void = {
expectexit(&outc, 0); expectexit(&outc, 0);
cwdassertrecord(outc.stdout, "p4-internal-only", p4, p4, "1", "yes", cwdassertrecord(outc.stdout, "p4-internal-only", p4, p4, "1", "yes",
"p4-data", "p4-testdata", "eof"); "p4-data", "p4-testdata", "eof");
cwdassertdirectoryenv(outc.stdout, "p4-internal-only");
cwdassertpath(outc.stdout, "p4-internal-only", cwdassertpath(outc.stdout, "p4-internal-only",
strings.concat(repo(), "/out/bin"), "1"); strings.concat(repo(), "/out/bin"), "1");
assert(os.remove(strings.concat(p4, "/created.txt")) == 0); assert(os.remove(strings.concat(p4, "/created.txt")) == 0);
@@ -1224,6 +1279,8 @@ fn cwdwritedata(dir: str, label: str) void = {
assert(has(outc.stderr, assert(has(outc.stderr,
"test package must match production package or <package>_test\n")); "test package must match production package or <package>_test\n"));
assert(!has(outc.stdout, " PATH=") && !has(outw.stdout, " PATH=")); assert(!has(outc.stdout, " PATH=") && !has(outw.stdout, " PATH="));
assert(!has(outc.stdout, " LC_ALL=")
&& !has(outw.stdout, " LC_ALL="));
assert(directoryisempty(rejectcwork)); assert(directoryisempty(rejectcwork));
assert(directoryisempty(rejectwwork)); assert(directoryisempty(rejectwwork));
@@ -1239,6 +1296,8 @@ fn cwdwritedata(dir: str, label: str) void = {
assert(!has(out.stdout, "cmdno-init")); assert(!has(out.stdout, "cmdno-init"));
assert(!has(out.stdout, "cmdno-main")); assert(!has(out.stdout, "cmdno-main"));
assert(!has(out.stdout, " PATH=")); assert(!has(out.stdout, " PATH="));
assert(!has(out.stdout, " LC_ALL=")
&& !has(out.stdout, " TMPDIR="));
assert(!os.exists(strings.concat(plain, "/created.txt"))); assert(!os.exists(strings.concat(plain, "/created.txt")));
assert(!os.exists(strings.concat(cmdno, "/created.txt"))); assert(!os.exists(strings.concat(cmdno, "/created.txt")));
let commandbin: str = strings.concat(root, "/cmdno.bin"); let commandbin: str = strings.concat(root, "/cmdno.bin");
@@ -1251,6 +1310,8 @@ fn cwdwritedata(dir: str, label: str) void = {
assert(os.exists(commandbin)); assert(os.exists(commandbin));
assert(!has(out.stdout, "cmdno-init") && !has(out.stdout, "cmdno-main")); assert(!has(out.stdout, "cmdno-init") && !has(out.stdout, "cmdno-main"));
assert(!has(out.stdout, " PATH=")); assert(!has(out.stdout, " PATH="));
assert(!has(out.stdout, " LC_ALL=")
&& !has(out.stdout, " TMPDIR="));
assert(!os.exists(strings.concat(cmdno, "/created.txt"))); assert(!os.exists(strings.concat(cmdno, "/created.txt")));
// Compile-only paths do not run. A published binary has no embedded cwd or // Compile-only paths do not run. A published binary has no embedded cwd or
@@ -1267,6 +1328,8 @@ fn cwdwritedata(dir: str, label: str) void = {
assert(!os.exists(strings.concat(p2, "/created.txt"))); assert(!os.exists(strings.concat(p2, "/created.txt")));
assert(!has(out.stdout, "dep-init") && !has(out.stdout, "p2-external")); assert(!has(out.stdout, "dep-init") && !has(out.stdout, "p2-external"));
assert(!has(out.stdout, " PATH=")); assert(!has(out.stdout, " PATH="));
assert(!has(out.stdout, " LC_ALL=")
&& !has(out.stdout, " TMPDIR="));
assert(os.remove(defaultbin) == 0); assert(os.remove(defaultbin) == 0);
assert(os.remove(strings.concat(p1, "/created.txt")) == 0); assert(os.remove(strings.concat(p1, "/created.txt")) == 0);
@@ -1288,6 +1351,8 @@ fn cwdwritedata(dir: str, label: str) void = {
assert(same(readfile(p1cbin), readfile(p1wbin))); assert(same(readfile(p1cbin), readfile(p1wbin)));
assert(!has(outc.stdout, "dep-init") && !has(outw.stdout, "dep-init")); assert(!has(outc.stdout, "dep-init") && !has(outw.stdout, "dep-init"));
assert(!has(outc.stdout, " PATH=") && !has(outw.stdout, " PATH=")); assert(!has(outc.stdout, " PATH=") && !has(outw.stdout, " PATH="));
assert(!has(outc.stdout, " LC_ALL=")
&& !has(outw.stdout, " LC_ALL="));
let directbin: []str = [p1cbin, "-package=p1"]; let directbin: []str = [p1cbin, "-package=p1"];
runcommandinputenvdir(root, "cwd-published-direct", directbin, cenv, runcommandinputenvdir(root, "cwd-published-direct", directbin, cenv,
@@ -1300,6 +1365,8 @@ fn cwdwritedata(dir: str, label: str) void = {
cwdassertrecord(out.stdout, "p1-internal", caller, cwdassertrecord(out.stdout, "p1-internal", caller,
"/ww-cwd-inherited-first", "2", "no", "caller-data", "/ww-cwd-inherited-first", "2", "no", "caller-data",
"caller-testdata", "data"); "caller-testdata", "data");
cwdassertcallerenv(out.stdout, "dep-init");
cwdassertcallerenv(out.stdout, "p1-internal");
cwdassertcallerpath(out.stdout, "dep-init"); cwdassertcallerpath(out.stdout, "dep-init");
cwdassertcallerpath(out.stdout, "p1-internal"); cwdassertcallerpath(out.stdout, "p1-internal");
cwdassertsplitstreams(out.stdout, out.stderr, "dep-init", caller); cwdassertsplitstreams(out.stdout, out.stderr, "dep-init", caller);
@@ -1328,6 +1395,7 @@ fn cwdwritedata(dir: str, label: str) void = {
cwdassertrecord(outc.stdout, "p4-internal-only", caller, cwdassertrecord(outc.stdout, "p4-internal-only", caller,
"/ww-cwd-inherited-first", "2", "yes", "caller-data", "/ww-cwd-inherited-first", "2", "yes", "caller-data",
"caller-testdata", "data"); "caller-testdata", "data");
cwdassertcallerenv(outc.stdout, "p4-internal-only");
cwdasserttestpath(outc.stdout, "p4-internal-only"); cwdasserttestpath(outc.stdout, "p4-internal-only");
cwdassertsplitstreams(outc.stdout, outc.stderr, cwdassertsplitstreams(outc.stdout, outc.stderr,
"p4-internal-only", caller); "p4-internal-only", caller);
@@ -1666,6 +1734,8 @@ fn cwdwritedata(dir: str, label: str) void = {
"p2-data", "p2-testdata", "eof"); "p2-data", "p2-testdata", "eof");
cwdassertrecord(out.stdout, "p2-external", p2, p2, "1", "yes", cwdassertrecord(out.stdout, "p2-external", p2, p2, "1", "yes",
"p2-data", "p2-testdata", "eof"); "p2-data", "p2-testdata", "eof");
cwdassertdirectoryenv(out.stdout, "dep-init");
cwdassertdirectoryenv(out.stdout, "p2-external");
cwdassertpath(out.stdout, "dep-init", strings.concat(root, cwdassertpath(out.stdout, "dep-init", strings.concat(root,
":/ww-path-inherited-first"), "1"); ":/ww-path-inherited-first"), "1");
cwdassertpath(out.stdout, "p2-external", strings.concat(root, cwdassertpath(out.stdout, "p2-external", strings.concat(root,