ww: put selected toolchain first in test PATH

This commit is contained in:
2026-08-21 05:30:10 +09:00
parent 1b71250ad5
commit 9b6b1100f4
7 changed files with 586 additions and 96 deletions

View File

@@ -16,6 +16,7 @@
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <stdint.h>
#include <stdarg.h>
#ifndef PATH_MAX
@@ -82,6 +83,71 @@ run_argv(const char *prog, char *const argv[])
return 1;
}
extern char **environ;
struct test_environment {
char **values;
char *path;
};
/* Pinned cmd/go appends PATH=$GOROOT/bin:$PATH to each test command and
* os/exec keeps the appended duplicate. The selected WW driver's sibling
* directory is the local toolchain-bin analogue. Build tools retain the
* caller's environment; only the test binary receives this array. */
static int
make_test_environment(const char *toolbin, struct test_environment *out)
{
const char *oldpath = getenv("PATH");
if (oldpath == NULL) oldpath = "";
size_t dirn = strlen(toolbin), oldn = strlen(oldpath);
if (oldn > SIZE_MAX - 7 || dirn > SIZE_MAX - oldn - 7) return -1;
size_t pathn = 5 + dirn + (oldn != 0 ? 1 + oldn : 0);
char *path = malloc(pathn + 1);
if (path == NULL) return -1;
memcpy(path, "PATH=", 5);
memcpy(path + 5, toolbin, dirn);
size_t at = 5 + dirn;
if (oldn != 0) {
path[at++] = ':';
memcpy(path + at, oldpath, oldn);
at += oldn;
}
path[at] = '\0';
size_t nenv = 0;
while (environ[nenv] != NULL) {
if (nenv == SIZE_MAX - 2) { free(path); return -1; }
nenv++;
}
if (nenv > SIZE_MAX / sizeof(char *) - 2) {
free(path);
return -1;
}
char **values = calloc(nenv + 2, sizeof *values);
if (values == NULL) { free(path); return -1; }
size_t n = 0;
int inserted = 0;
for (size_t i = 0; i < nenv; i++) {
if (strncmp(environ[i], "PATH=", 5) == 0) {
if (!inserted) { values[n++] = path; inserted = 1; }
} else {
values[n++] = environ[i];
}
}
if (!inserted) values[n++] = path;
values[n] = NULL;
out->values = values;
out->path = path;
return 0;
}
static void
free_test_environment(struct test_environment *env)
{
free(env->values);
free(env->path);
}
/* run_test_bin — exec the built test binary with an optional name-filter
* pattern as argv[1] (lib/test run() reads it via os.args). fork+execv
* (not system()) so glob metacharacters in the pattern reach the binary
@@ -91,14 +157,28 @@ run_argv(const char *prog, char *const argv[])
static int
run_test_bin(const char *bin, const char *pattern)
{
char toolbin[PATH_MAX];
if (realpath(self_dir, toolbin) == NULL) {
fputs("ww: cannot prepare test environment\n", stderr);
return -1;
}
struct test_environment env = {0};
if (make_test_environment(toolbin, &env) != 0) {
fputs("ww: cannot prepare test environment\n", stderr);
return -1;
}
pid_t pid = fork();
if (pid < 0) { perror("ww: fork"); return -1; }
if (pid < 0) {
perror("ww: fork");
free_test_environment(&env);
return -1;
}
if (pid == 0) {
char *xargv[3];
xargv[0] = (char *)bin;
if (pattern) { xargv[1] = (char *)pattern; xargv[2] = NULL; }
else { xargv[1] = NULL; }
execv(bin, xargv);
execve(bin, xargv, env.values);
perror("ww: exec");
_exit(127);
}
@@ -108,6 +188,7 @@ run_test_bin(const char *bin, const char *pattern)
* test PASS. */
pid_t got;
do { got = waitpid(pid, &status, 0); } while (got < 0 && errno == EINTR);
free_test_environment(&env);
if (got < 0) { perror("ww: waitpid"); return -1; }
if (WIFEXITED(status)) return WEXITSTATUS(status);
return 1;

View File

@@ -5849,22 +5849,24 @@ 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=`, and
uppercase `PWD=` entries, then appends `LC_ALL=C`, the product-local absolute
`TMPDIR`, and `PWD=<pkggroup.dir>`. Removing prior `PWD` entries reproduces
Go's observable last-wins result because WW's executor intentionally preserves
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.
the appended position, and one effective uppercase `PATH`; section 11.32 owns
the latter rule.
The vector and both generated strings are product-local, dynamically sized,
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
`PWD`, generated `-package` argument, and argv vector. Concurrent children
therefore hold independent fork snapshots; no shared environment vector or
process-global state is mutated.
`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
@@ -5880,15 +5882,19 @@ emission remains byte-sorted and identical to `-j 1`.
`-c -o`, builds or publishes but never enters `pkgstartrun`; no execution cwd
or run environment is allocated. A published binary subsequently invoked by
the user bypasses the coordinator and inherits the user's cwd and environment.
The raw single-file test compatibility route is likewise unchanged. A
directory with no selected test source still creates no support, generated
main, link, run, result, or execution-context state.
The raw single-file test compatibility route retains its caller cwd, `PWD`,
stdin, and stream behavior, but section 11.32 applies the test-process `PATH`
rule at its driver-owned launch. A directory with no selected test source still
creates no support, generated main, link, run, result, or execution-context
state.
Build-plan commands retain `dir=""` and their existing tool environment.
Compiler, assembler, in-driver archiver, linker, support generation, generated
main construction, the Cstage driver, and the WWstage driver therefore retain
their exact prior cwd, argv, and environment. The runtime rule required no
`lib/os/exec`, compiler, checker, writer, assembler, linker, or driver change.
Compiler, assembler, in-driver archiver, linker, support generation, and
generated-main construction therefore retain their exact prior cwd, argv, and
environment. The directory cwd rule required no `lib/os/exec`, compiler,
checker, writer, assembler, linker, or driver change; the later test-only PATH
rule is isolated at launch and changes neither build-plan environment nor those
tools.
Independent Cstage/WWstage compile-only products remain byte-identical, and
changing only `data.txt` or `testdata` changes no unit, `.wwi`, assembly,
object, archive, generated-main, or binary bytes.
@@ -5916,13 +5922,15 @@ The focused native owner is
`directory_test_execution_working_directory` in
`test/package/package_test.ww`. It creates only disposable source trees and
compares Cstage and WWstage across duplicate and large environments; exact
`getcwd`, effective/count/position of `PWD`; ordinary data, `testdata`, and
`getcwd`, effective/count/position of `PWD`; absent, empty, nonempty, and
duplicate inherited `PATH`; ordinary data, `testdata`, and
relative writes; all production/internal/external/test-only shapes;
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`, direct binaries, and raw
single files; exact tool cwd/argv/locale/TMPDIR; persistent data-only reuse and
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
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
@@ -6904,6 +6912,141 @@ and retained-binary contracts. Existing request-transaction, timeout,
interruption, and concurrent-driver owners continue to cover those unchanged
dimensions.
### 11.32 Implemented selected toolchain first in test `PATH`
Every test binary actually started by `ww test` now receives one effective
uppercase `PATH` beginning with the canonical absolute directory of the
selected WW driver. An absent or empty inherited value produces only that
directory. A nonempty first effective inherited value follows it after `:`;
ordinary duplicate `PATH=` entries collapse to the one child value. This is
test-process metadata only: build tools and every request that starts no test
process retain their prior environment.
#### Pinned Go evidence and classification
The sole authority is official Go 1.26.5 at commit
`c19862e5f8415b4f24b189d065ed739517c548ba`:
- The `go test` command documentation states that `$GOROOT/bin` is placed at
the beginning of the test process's `PATH`, so an executed test resolves the
`go` command from the invoking toolchain
([`cmd/go/internal/test/test.go`, lines 8794](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L87-L94)).
- `(*runTestActor).Act` begins with `cfg.OrigEnv`, applies
`base.AppendPATH`, then `base.AppendPWD`, and assigns that environment to
each test command
([`cmd/go/internal/test/test.go`, lines 16611668](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1661-L1668)).
`AppendPATH` appends `PATH=$GOROOT/bin` for an empty effective inherited
value and `PATH=$GOROOT/bin:<old>` otherwise
([`cmd/go/internal/base/env.go`, lines 2945](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/base/env.go#L29-L45)).
- `Cmd.environ` removes duplicate environment keys while preferring the later
entry, making the appended value effective
([`os/exec/exec.go`, lines 12311265](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/os/exec/exec.go#L1231-L1265)).
On the supported Unix model, initial environment lookup uses the first
inherited occurrence
([`syscall/env_unix.go`, lines 2044 and 6684](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/syscall/env_unix.go#L20-L84)).
- Official `test_goroot_PATH.txt` tests both an empty `PATH` and a nonempty
directory containing no executable; the test must find the `go` executable
in the current toolchain's `$GOROOT/bin`
([lines 141](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_goroot_PATH.txt#L1-L41)).
Those source rules and official assertions are behavior directly implemented
or asserted by pinned Go. Using the selected WW driver's sibling directory as
the local toolchain-bin analogue is derived from that implementation: WW has
no `GOROOT`, and that directory already supplies the driver's default compiler,
assembler, linker, and package-test coordinator. Applying the rule to WW's raw
single-file compatibility route is also derived; it is a local input extension,
but it executes the same observable test and initialization code. Canonical
absolute spelling is runtime metadata only and prevents a relative driver path
from leaking the coordinator's later package cwd into `PATH`.
#### Fresh four-axis audit and direct pre-fix measurements
The bounded audit considered all four permanent axes and selected only this
test-runtime gap:
- **Build:** pinned `runBuild` loads all roots and checks package errors before
output/action construction
([`cmd/go/internal/work/build.go`, lines 459478](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build.go#L459-L478)); official
`build_json.txt` distinguishes load errors from compiler failures
([lines 1526 and 3945](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_json.txt#L15-L45)).
A direct missing-import probe made both WW stages exit `1` with the identical
source diagnostic and no producer or public output. This candidate was
aligned.
- **Test:** direct Cstage and WWstage directory probes, each invoked with
`PATH=/usr/bin:/bin`, both exited `0` while the test printed exactly that
unchanged value. `/home/kimchi/src/ww/out/bin` was absent. These are directly
measured pre-fix WW facts and establish the selected external difference.
- **Package:** pinned `types2.(*Checker).initFiles` rejects package name `_`
([`cmd/compile/internal/types2/check.go`, lines 311355](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/check.go#L311-L355)),
with official anchors in
[`internal/types/testdata/check/blank.go`](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/blank.go#L1-L5)
and [`test/blank1.go`](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/test/blank1.go#L1-L10).
Direct production, imported, and test-only probes were rejected identically
by both WW stages. This candidate was aligned.
- **Import:** pinned `unusedImports` requires every nonblank import binding 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` covers default, alias, dot, and blank forms
([lines 531](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/importdecl0/importdecl0a.go#L5-L31)).
A direct unused default-import probe produced identical Cstage/WWstage
diagnostics and no committed work. This candidate was aligned.
The build, package, and import observations above are directly measured WW
behavior; the linked rules are behavior directly implemented or asserted by
pinned Go. The conclusion that this slice crosses those axes only when code in
a successfully loaded test variant or initialized dependency observes `PATH`
is derived from the pinned launch placement.
#### Ownership, launch behavior, and preserved boundaries
The environment is synthesized at the three true test-process launch owners:
`internal/wwpackage.pkgstartrun` for directory products, Cstage
`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.
The rule covers internal, external, and combined directory products;
dependency initialization; filters and list mode; running retained tests; and
raw single-file tests. It is independently materialized for each concurrent
product. `ww build`, `ww run`, compiler/assembler/linker and generated-main
commands, compile-only and assembly-only tests, no-test products, rejected
requests, and later direct execution of a retained binary receive no test
environment transformation.
Loading, graph construction, compilation, assembly, linking, action keys, and
artifact production are unchanged. A load, compile, or link failure starts no
test process, so runtime `PATH` is inapplicable and the existing diagnostic
precedence remains. A started test observes the new environment before normal
success, assertion failure, signal, timeout, or interruption. Existing process
groups, cancellation, output capture, and cleanup own those outcomes; the
environment adds no global mutable state. A running retained request remains
private build, private run, then guarded install, so any unsuccessful run
publishes nothing and preserves prior bytes. Parallel products receive separate
environment arrays and retain existing result isolation.
Canonical physical driver directories do not become package, import, graph,
action, artifact, symbol, `.wwi`, publication, or persistence identity.
Compiled units, interfaces, archives, executables, modes, diagnostics, and
public-output disposition are unchanged. No stored key or byte changed, so
build workdir format remains `18`, test workdir format remains `19`, and
semantic storage format remains `3`.
The extended WW-native `directory_test_execution_working_directory` observer
proves Cstage/WWstage equality for absent, empty, nonempty, and duplicate
caller `PATH`; all directory test shapes and dependency initialization;
filters, listing, raw single-file, and running retained tests; relative selected
driver canonicalization; build/no-test/compile-only/rejection exclusions;
unchanged compiler, assembler, and linker environments; concurrent products;
runtime failure with prior retained-byte rollback; artifact-byte identity; and
transaction, stage, workdir, and generated-file cleanup. Existing signal,
timeout, interruption, concurrent-driver, and public-output transaction owners
cover the unchanged mechanisms at those boundaries.
## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins.

View File

@@ -757,6 +757,17 @@ remaining separate actions. Reachable dependency initialization consequently
observes the tested product's directory; a dependency tested as its own product
observes its own directory.
Every test binary actually started by `ww test` receives one effective uppercase
`PATH` whose first element is the canonical absolute directory of the selected
WW driver. An absent or empty caller `PATH` yields only that directory; a
nonempty effective caller value follows it after `:`. Normal duplicate `PATH=`
entries collapse to that one value, using the caller's first effective value as
the suffix. This applies to directory products, their dependency initialization,
filters and list mode, running retained tests, and the raw single-file
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.
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
@@ -781,9 +792,11 @@ 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,
environment, and three standard descriptors; no package directory, input, or
output policy is embedded or forced by the binary.
raw single-file compatibility route, inherit the user's invocation cwd and
three standard descriptors; no package directory, input, or output policy is
embedded or forced by the binary. Directly invoked retained binaries inherit the
entire caller environment. Raw single-file tests preserve every caller
environment field except the test-only `PATH` transformation above.
---

View File

@@ -291,6 +291,18 @@ 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 test binary actually executed by either driver stage also receives one
effective uppercase `PATH` beginning with the canonical absolute directory of
the selected WW driver. Missing and empty caller values produce only that
directory; a nonempty first effective caller value follows it after `:`.
Normal duplicate `PATH=` entries collapse to the one child value. Directory
products allocate this environment independently per concurrent child; the raw
single-file route performs the same PATH transformation while retaining its
caller cwd, `PWD`, stdin, and split streams. Running retained tests transform
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.
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
@@ -312,9 +324,10 @@ linker, support, and generated-main commands retain their build-plan cwd and
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.
directly inherits the user's cwd/environment/stdin. The raw single-file
compatibility path inherits caller cwd/stdin and all unrelated environment
entries, with only the test-PATH transformation above; neither route embeds a
package directory or input behavior in the binary.
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
@@ -401,17 +414,20 @@ The same package owner contains the focused
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,
recompiled external self-import; duplicate inherited `PWD`; absent, empty,
nonempty, and duplicate inherited `PATH`; ordinary data,
`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;
failure and timeout; build/no-test and compile-only paths; running retained
success and failed-run rollback; 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-
product `PWD`, one toolchain-first child `PATH`, requires EOF for captured
actions and caller data for inherited-
stdio routes, alternates fd-1/fd-2 write syscalls through every executing
variant and initializer, requires one ordered stdout stream on success,
assertion failure, signal, timeout, and setup failure, requires direct/raw
@@ -520,11 +536,12 @@ real directory test products always run, while no-selected-test directories do
not create a process. The byte-identity and bootstrap gates keep building on
fresh scratch. `make clean` reclaims every workdir under `out/`.
Directory-product cwd and `PWD` are request-time child-process metadata, not
persisted action inputs or results. A runtime directory-entry failure therefore
does not erase a successfully committed compilation generation. Changing only
ordinary fixture data causes no unit/export/assembly/object/archive/main/binary
change and no producer work beyond the same established warm final relink,
Directory-product cwd, `PWD`, and test `PATH` are request-time child-process
metadata, not persisted action inputs or results. A runtime directory-entry
failure therefore does not erase a successfully committed compilation
generation. Changing only ordinary fixture data causes no
unit/export/assembly/object/archive/main/binary change and no producer work
beyond the same established warm final relink,
while the next always-run product observes the new bytes. Build workdir format
remains 18, test workdir format remains 19, and semantic storage remains 3.

View File

@@ -268,17 +268,27 @@ fn pkgfreestrs(values: []str) void = {
};
};
// Go's AppendPWD appends, then os/exec keeps the last duplicate value. WW's
// executor preserves duplicates, so discard earlier exact PWD entries here.
fn runenv(tmpdir: str, pwd: str, out: *[]str, tmpowned: *str,
pwdowned: *str) bool = {
// Go's AppendPATH and AppendPWD append, then os/exec keeps the last duplicate
// values. WW's executor preserves duplicates, so discard earlier exact entries
// and materialize the selected driver's sibling directory first in PATH.
fn runenv(tmpdir: str, pwd: str, builder: str, out: *[]str, tmpowned: *str,
pathowned: *str, pwdowned: *str) bool = {
let toolbin: str;
let oom: bool = false;
if (!pkgcanonicaldir(pkgdirname(builder), &toolbin, &oom)) {
if (!oom) {
pkgputln(os.STDERR_FILENO,
"wwtest package: cannot determine toolchain directory");
};
return false;
};
let inherited: []str = os.getenvs();
if (inherited.len > PKG_COUNT_MAX - 3) {
if (inherited.len > PKG_COUNT_MAX - 4) {
pkgputln(os.STDERR_FILENO,
"wwtest package: package graph is too large");
return false;
};
let allocation: ([]str | nomem) = pkgallocstrs(inherited.len + 3);
let allocation: ([]str | nomem) = pkgallocstrs(inherited.len + 4);
let env: []str;
match (allocation) {
case let value: []str => env = value;
@@ -291,6 +301,7 @@ fn runenv(tmpdir: str, pwd: str, out: *[]str, tmpowned: *str,
for (i < inherited.len) {
if (!strings.hasprefix(inherited[i], "TMPDIR=")
&& !strings.hasprefix(inherited[i], "LC_ALL=")
&& !strings.hasprefix(inherited[i], "PATH=")
&& !strings.hasprefix(inherited[i], "PWD=")) {
append(env, inherited[i]);
};
@@ -302,22 +313,44 @@ fn runenv(tmpdir: str, pwd: str, out: *[]str, tmpowned: *str,
pkgfreestrs(env);
return false;
};
let pwdenv: str;
if (!pkgstring(&pwdenv, "PWD=", pwd)) {
let pathenv: str;
let pathok: bool = false;
match (os.getenv("PATH")) {
case let inheritedpath: str => {
if (inheritedpath.len == 0) {
pathok = pkgstring(&pathenv, "PATH=", toolbin);
} else {
pathok = pkgstring(&pathenv, "PATH=", toolbin, ":",
inheritedpath);
};
};
case void => pathok = pkgstring(&pathenv, "PATH=", toolbin);
};
if (!pathok) {
pkgfreeownedstr(tmpenv);
pkgfreestrs(env);
return false;
};
let pwdenv: str;
if (!pkgstring(&pwdenv, "PWD=", pwd)) {
pkgfreeownedstr(tmpenv);
pkgfreeownedstr(pathenv);
pkgfreestrs(env);
return false;
};
append(env, tmpenv);
append(env, pathenv);
append(env, pwdenv);
*out = env;
*tmpowned = tmpenv;
*pathowned = pathenv;
*pwdowned = pwdenv;
return true;
};
fn freerunenv(env: []str, tmpowned: str, pwdowned: str) void = {
fn freerunenv(env: []str, tmpowned: str, pathowned: str, pwdowned: str) void = {
pkgfreeownedstr(tmpowned);
pkgfreeownedstr(pathowned);
pkgfreeownedstr(pwdowned);
pkgfreestrs(env);
};
@@ -1240,8 +1273,8 @@ fn pkgmakedir(path: str) bool = {
};
// Resolve a directory to the kernel's symlink-free absolute spelling. The
// coordinator is single-threaded while planning, so the temporary cwd change
// cannot race a child process launch.
// coordinator is single-threaded; each temporary cwd change is restored before
// it plans further paths or launches another child.
fn pkgcanonicaldir(path: str, out: *str, oom: *bool) bool = {
*oom = false;
let beforeallocation: ([]u8 | nomem) = pkgallocbytes(os.PATH_MAX);
@@ -1553,7 +1586,7 @@ fn pkgstartbuild(p: *pkgplan, groups: []pkggroup, builder: str, includes: []str,
};
fn pkgstartrun(g: *pkggroup, filters: []str, timeoutarg: str,
list: bool, h: *exec.process) bool = {
list: bool, builder: str, h: *exec.process) bool = {
if (filters.len > PKG_COUNT_MAX - 4) {
pkgputln(os.STDERR_FILENO,
"wwtest package: package graph is too large");
@@ -1581,8 +1614,10 @@ fn pkgstartrun(g: *pkggroup, filters: []str, timeoutarg: str,
for (i < filters.len) { append(ra, filters[i]); i += 1; };
let env: []str;
let tmpowned: str;
let pathowned: str;
let pwdowned: str;
if (!runenv(g.root, g.dir, &env, &tmpowned, &pwdowned)) {
if (!runenv(g.root, g.dir, builder, &env, &tmpowned,
&pathowned, &pwdowned)) {
pkgfreeownedstr(packagearg);
pkgfreestrs(ra);
return false;
@@ -1598,7 +1633,7 @@ fn pkgstartrun(g: *pkggroup, filters: []str, timeoutarg: str,
rcmd.deadline.nsec = 0i64;
rcmd.grace = 0i64: time.duration;
exec.start(h, &rcmd);
freerunenv(env, tmpowned, pwdowned);
freerunenv(env, tmpowned, pathowned, pwdowned);
pkgfreeownedstr(packagearg);
pkgfreestrs(ra);
return true;
@@ -2561,7 +2596,7 @@ export fn packagecommand(args: []str) int = {
productcompleted += 1;
} else {
if (!pkgstartrun(g, filters, timeoutarg, list,
&runhandles[gi])) {
builder, &runhandles[gi])) {
g.runstartfailed = true;
g.state = PKGDONE;
productcompleted += 1;

View File

@@ -1632,6 +1632,88 @@ fn sepallocptrs(cap: i32) ([]*u8 | nomem) = {
return value;
};
type septestenv = struct {
values: []str,
path: str,
};
// Pinned cmd/go gives each test binary PATH=$GOROOT/bin:$PATH. The selected
// WW driver's sibling directory is the local toolchain-bin analogue. Remove
// normal inherited PATH duplicates because lib/os/exec deliberately preserves
// the concrete environment array while Go's os/exec keeps the appended value.
fn sepmaketestenv(toolbin: str, out: *septestenv) bool = {
let inherited: []str = os.getenvs();
if (inherited.len == SEP_COUNT_MAX) {
cerr("ww: cannot prepare test environment\n");
return false;
};
let oldpath: str = "";
match (os.getenv("PATH")) {
case let value: str => oldpath = value;
case void => void;
};
let total: i64 = 5i64 + (toolbin.len: i64);
if (oldpath.len != 0) { total += 1i64 + (oldpath.len: i64); };
if (total > SEP_COUNT_MAX: i64) {
cerr("ww: cannot prepare test environment\n");
return false;
};
let pathallocation: ([]u8 | nomem) = sepallocbytes(total: i32);
let pathbytes: []u8;
match (pathallocation) {
case let value: []u8 => pathbytes = value;
case nomem => {
cerr("ww: cannot prepare test environment\n");
return false;
};
};
let prefix: str = "PATH=";
let i: i32 = 0;
for (i < prefix.len) { append(pathbytes, prefix[i]); i += 1; };
i = 0;
for (i < toolbin.len) { append(pathbytes, toolbin[i]); i += 1; };
if (oldpath.len != 0) {
append(pathbytes, ':');
i = 0;
for (i < oldpath.len) { append(pathbytes, oldpath[i]); i += 1; };
};
let pathenv: str = strings.frombytes(pathbytes);
let envallocation: ([]str | nomem) = sepallocstrs(inherited.len + 1);
let env: []str;
match (envallocation) {
case let value: []str => env = value;
case nomem => {
os.free(pathenv.ptr: *void, pathenv.len: u64);
cerr("ww: cannot prepare test environment\n");
return false;
};
};
i = 0;
let inserted: bool = false;
for (i < inherited.len) {
if (strings.hasprefix(inherited[i], "PATH=")) {
if (!inserted) { append(env, pathenv); inserted = true; };
} else {
append(env, inherited[i]);
};
i += 1;
};
if (!inserted) { append(env, pathenv); };
out.values = env;
out.path = pathenv;
return true;
};
fn sepfreetestenv(env: *septestenv) void = {
if (env.path.ptr != nil && env.path.len != 0) {
os.free(env.path.ptr: *void, env.path.len: u64);
};
if (env.values.ptr != nil && env.values.cap != 0) {
os.free(env.values.ptr: *void,
(env.values.cap: u64) * (size(str): u64));
};
};
fn sepallocgraph(pkg: []seppkg, context: []sepcontext) (*sepgraph | nomem) = {
let emptyfolds: []sepfoldentry;
let value: *sepgraph = alloc(sepgraph{
@@ -9186,21 +9268,30 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
if (pattern != nil) {
append(execargv, pathstr(pattern));
};
let env: []str = os.getenvs();
let result: exec.result;
exec.runstdio(pathstr(outp), execargv, env, &result);
let rc: i32 = 1;
if (result.termination == exec.termination.EXIT) {
rc = result.code;
} else { if (result.termination == exec.termination.ERROR) {
if (result.code == 127) {
cerr("ww: execve failed\n");
rc = 127;
} else {
cerr("ww: process launch/wait failed\n");
rc = -1;
};
let env: septestenv;
let toolbin: *u8 = canonicaldir(pathstr(selfdir));
if (toolbin == nil) {
cerr("ww: cannot prepare test environment\n");
} else { if (sepmaketestenv(pathstr(toolbin), &env)) {
exec.runstdio(pathstr(outp), execargv, env.values, &result);
sepfreetestenv(&env);
if (result.termination == exec.termination.EXIT) {
rc = result.code;
} else { if (result.termination == exec.termination.ERROR) {
if (result.code == 127) {
cerr("ww: execve failed\n");
rc = 127;
} else {
cerr("ww: process launch/wait failed\n");
rc = -1;
};
}; };
}; };
if (toolbin != nil) {
os.free(toolbin: *void, cstrlen(toolbin) + 1u64);
};
if (rc == 0 && deferredinstall
&& sepinstalltestoutput(outp, outstem) != 0) { rc = 1; };
if (owntmp) {

View File

@@ -633,10 +633,11 @@ fn hexbytes(value: str) str = {
fn cwdtestenv(stage: str) []str = {
let inherited: []str = os.getenvs();
let env: []str = alloc([], (inherited.len + 8): u64)!;
let env: []str = alloc([], (inherited.len + 10): u64)!;
let i: i32 = 0;
for (i < inherited.len) {
if (!strings.hasprefix(inherited[i], "PWD=")
&& !strings.hasprefix(inherited[i], "PATH=")
&& !strings.hasprefix(inherited[i], "WW_W6C=")
&& !strings.hasprefix(inherited[i], "WW_W6A=")
&& !strings.hasprefix(inherited[i], "WW_W6L=")
@@ -646,6 +647,7 @@ fn cwdtestenv(stage: str) []str = {
};
i += 1;
};
append(env, "PATH=/ww-path-inherited-first");
append(env, "PWD=/ww-cwd-inherited-first");
if (same(stage, "ww")) {
append(env, strings.concat("WW_W6C=", driver("w6c")));
@@ -658,10 +660,25 @@ fn cwdtestenv(stage: str) []str = {
append(env, strings.concat("WW_W6L=", driver("w6l_ww")));
};
append(env, strings.concat("WW_SRCLIB=", repo(), "/lib"));
append(env, "PATH=/ww-path-inherited-last");
append(env, "PWD=/ww-cwd-inherited-last");
return env;
};
fn cwdassertpath(text: str, label: str, path: str, count: str) void = {
assert(has(text, strings.concat(label, " PATH=", path, "\n")));
assert(has(text, strings.concat(label, " PATH-count=", count, "\n")));
};
fn cwdasserttestpath(text: str, label: str) void = {
cwdassertpath(text, label, strings.concat(repo(),
"/out/bin:/ww-path-inherited-first"), "1");
};
fn cwdassertcallerpath(text: str, label: str) void = {
cwdassertpath(text, label, "/ww-path-inherited-first", "2");
};
fn cwdassertrecord(text: str, label: str, cwd: str, pwd: str,
pwdcount: str, pwdlast: str, data: str, testdata: str,
input: str) void = {
@@ -707,6 +724,7 @@ fn cwdassertpackage(text: str, label: str, dir: str, data: str,
testdata: str) void = {
cwdassertrecord(text, label, dir, dir, "1", "yes", data, testdata,
"eof");
cwdasserttestpath(text, label);
cwdassertmergedstreams(text, label, dir);
};
@@ -883,27 +901,17 @@ fn cwdwritedata(dir: str, label: str) void = {
" let body: str; body.ptr = bytes.ptr; body.len = bytes.len; put(body);\n",
" if (body.len == 0 || body[body.len - 1] != '\\n') { put(\"\\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",
" let env: []str = os.getenvs(); let count: 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",
" 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",
" 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, \"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",
@@ -1051,6 +1059,46 @@ fn cwdwritedata(dir: str, label: str) void = {
assert(same(readfile(strings.concat(caller, "/sentinel")),
"parent-sentinel\n"));
// The official Go script exercises both an absent and an explicitly empty
// caller PATH. Each produces only the selected toolchain directory, and the
// two stages expose identical process bytes.
assert(os.remove(strings.concat(p4, "/created.txt")) == 0);
let absentbase: []str = cwdtestenv("ww");
let absentenv: []str = alloc([], absentbase.len: u64)!;
i = 0;
for (i < absentbase.len) {
if (!strings.hasprefix(absentbase[i], "PATH=")) {
append(absentenv, absentbase[i]);
};
i += 1;
};
let absentav: []str = [driver("ww"), "test", "-I", tree, p4];
runcommandinputenvdir(root, "path-absent-c", absentav, absentenv, caller,
stdinpath, (60i64 * (time.second: i64)): time.duration, &outc);
expectexit(&outc, 0);
cwdassertrecord(outc.stdout, "p4-internal-only", p4, p4, "1", "yes",
"p4-data", "p4-testdata", "eof");
cwdassertpath(outc.stdout, "p4-internal-only",
strings.concat(repo(), "/out/bin"), "1");
assert(os.remove(strings.concat(p4, "/created.txt")) == 0);
let emptybase: []str = cwdtestenv("ww_ww");
let emptyenv: []str = alloc([], (emptybase.len + 1): u64)!;
i = 0;
for (i < emptybase.len) {
if (!strings.hasprefix(emptybase[i], "PATH=")) {
append(emptyenv, emptybase[i]);
};
i += 1;
};
append(emptyenv, "PATH=");
let emptyav: []str = [driver("ww_ww"), "test", "-I", tree, p4];
runcommandinputenvdir(root, "path-empty-ww", emptyav, emptyenv, caller,
stdinpath, (60i64 * (time.second: i64)): time.duration, &outw);
expectexit(&outw, 0);
assert(same(outc.stdout, outw.stdout));
assert(same(outc.stderr, outw.stderr));
assert(os.remove(strings.concat(p4, "/created.txt")) == 0);
// Filtering, listing, and a no-match filter all execute the same product
// binary. Package initialization therefore has the product cwd even when no
// selected test body runs.
@@ -1172,6 +1220,7 @@ fn cwdwritedata(dir: str, label: str) void = {
assert(same(outc.stderr, outw.stderr));
assert(has(outc.stderr,
"test package must match production package or <package>_test\n"));
assert(!has(outc.stdout, " PATH=") && !has(outw.stdout, " PATH="));
assert(directoryisempty(rejectcwork));
assert(directoryisempty(rejectwwork));
@@ -1186,6 +1235,7 @@ fn cwdwritedata(dir: str, label: str) void = {
assert(!has(out.stdout, "plain-init"));
assert(!has(out.stdout, "cmdno-init"));
assert(!has(out.stdout, "cmdno-main"));
assert(!has(out.stdout, " PATH="));
assert(!os.exists(strings.concat(plain, "/created.txt")));
assert(!os.exists(strings.concat(cmdno, "/created.txt")));
let commandbin: str = strings.concat(root, "/cmdno.bin");
@@ -1197,6 +1247,7 @@ fn cwdwritedata(dir: str, label: str) void = {
expectexit(&out, 0);
assert(os.exists(commandbin));
assert(!has(out.stdout, "cmdno-init") && !has(out.stdout, "cmdno-main"));
assert(!has(out.stdout, " PATH="));
assert(!os.exists(strings.concat(cmdno, "/created.txt")));
// Compile-only paths do not run. A published binary has no embedded cwd or
@@ -1212,6 +1263,7 @@ fn cwdwritedata(dir: str, label: str) void = {
assert(!os.exists(strings.concat(p2, "/p2.test")));
assert(!os.exists(strings.concat(p2, "/created.txt")));
assert(!has(out.stdout, "dep-init") && !has(out.stdout, "p2-external"));
assert(!has(out.stdout, " PATH="));
assert(os.remove(defaultbin) == 0);
assert(os.remove(strings.concat(p1, "/created.txt")) == 0);
@@ -1232,6 +1284,7 @@ fn cwdwritedata(dir: str, label: str) void = {
assert(!os.exists(strings.concat(p1, "/created.txt")));
assert(same(readfile(p1cbin), readfile(p1wbin)));
assert(!has(outc.stdout, "dep-init") && !has(outw.stdout, "dep-init"));
assert(!has(outc.stdout, " PATH=") && !has(outw.stdout, " PATH="));
let directbin: []str = [p1cbin, "-package=p1"];
runcommandinputenvdir(root, "cwd-published-direct", directbin, cenv,
@@ -1244,13 +1297,16 @@ fn cwdwritedata(dir: str, label: str) void = {
cwdassertrecord(out.stdout, "p1-internal", caller,
"/ww-cwd-inherited-first", "2", "no", "caller-data",
"caller-testdata", "data");
cwdassertcallerpath(out.stdout, "dep-init");
cwdassertcallerpath(out.stdout, "p1-internal");
cwdassertsplitstreams(out.stdout, out.stderr, "dep-init", caller);
cwdassertsplitstreams(out.stdout, out.stderr, "p1-internal", caller);
assert(os.exists(strings.concat(caller, "/created.txt")));
assert(os.remove(strings.concat(caller, "/created.txt")) == 0);
// Raw single-file compatibility is deliberately outside the directory
// coordinator rule and retains its caller execution context.
// Raw single-file compatibility retains its caller cwd/PWD/stdin context,
// but it is still a test process and therefore receives the selected
// toolchain directory first in PATH.
let rawc: []str = [driver("ww"), "test", "-I", tree,
strings.concat(p4, "/internal_test.ww")];
let raww: []str = [driver("ww_ww"), "test", "-I", tree,
@@ -1269,9 +1325,52 @@ fn cwdwritedata(dir: str, label: str) void = {
cwdassertrecord(outc.stdout, "p4-internal-only", caller,
"/ww-cwd-inherited-first", "2", "yes", "caller-data",
"caller-testdata", "data");
cwdasserttestpath(outc.stdout, "p4-internal-only");
cwdassertsplitstreams(outc.stdout, outc.stderr,
"p4-internal-only", caller);
assert(os.remove(strings.concat(caller, "/created.txt")) == 0);
// A retained running test receives the same environment before guarded
// install. A later runtime failure observes PATH but cannot replace the
// successful binary; both stages preserve identical prior bytes.
let retained: str = strings.concat(root, "/path-retained.test");
let retainc: []str = [driver("ww"), "test", "-o", retained,
"-I", tree, p1];
let retainw: []str = [driver("ww_ww"), "test", "-o", retained,
"-I", tree, p1];
runcommandinputenvdir(root, "path-retain-c", retainc, cenv, caller,
stdinpath, (90i64 * (time.second: i64)): time.duration, &outc);
expectexit(&outc, 0);
cwdassertpackage(outc.stdout, "dep-init", p1, "p1-data", "p1-testdata");
cwdassertpackage(outc.stdout, "p1-internal", p1,
"p1-data", "p1-testdata");
let retainedbytes: str = strings.dup(readfile(retained));
runcommandinputenvdir(root, "path-retain-ww", retainw, wenv, caller,
stdinpath, (90i64 * (time.second: i64)): time.duration, &outw);
expectexit(&outw, 0);
assert(same(outc.stdout, outw.stdout));
assert(same(outc.stderr, outw.stderr));
assert(same(retainedbytes, readfile(retained)));
let retainfailc: []str = [driver("ww"), "test", "-o", retained,
"-I", tree, "-I", failtree, p7];
let retainfailw: []str = [driver("ww_ww"), "test", "-o", retained,
"-I", tree, "-I", failtree, p7];
runcommandinputenvdir(root, "path-retain-fail-c", retainfailc, cenv,
caller, stdinpath,
(90i64 * (time.second: i64)): time.duration, &outc);
expectexit(&outc, 1);
cwdassertpackage(outc.stdout, "p7-fail", p7, "p7-data", "p7-testdata");
assert(same(retainedbytes, readfile(retained)));
runcommandinputenvdir(root, "path-retain-fail-ww", retainfailw, wenv,
caller, stdinpath,
(90i64 * (time.second: i64)): time.duration, &outw);
expectexit(&outw, 1);
assert(same(outc.stdout, outw.stdout));
assert(same(outc.stderr, outw.stderr));
assert(same(retainedbytes, readfile(retained)));
assert(!os.exists(strings.concat(retained, ".new")));
assert(!directoryhasfragment(root, "path-retained.test.wwtxn."));
cwdassertpackage(multiout, "dep-init", p2, "p2-data", "p2-testdata");
cwdassertpackage(multiout, "p2-external", p2,
"p2-data", "p2-testdata");
@@ -1344,8 +1443,8 @@ fn cwdwritedata(dir: str, label: str) void = {
"#!/bin/sh\n",
"printf 'compiler cwd=' >> \"$WW_CWD_TOOL_TRACE\"\n",
"/bin/pwd >> \"$WW_CWD_TOOL_TRACE\"\n",
"printf 'compiler env=LC_ALL:%s TMPDIR:%s\\n' \"$LC_ALL\" ",
"\"$TMPDIR\" >> \"$WW_CWD_TOOL_TRACE\"\n",
"printf 'compiler env=LC_ALL:%s TMPDIR:%s PATH:%s\\n' \"$LC_ALL\" ",
"\"$TMPDIR\" \"$PATH\" >> \"$WW_CWD_TOOL_TRACE\"\n",
"if IFS= read -r WW_CWD_STDIN; then\n",
" printf 'compiler stdin=data\\n' >> \"$WW_CWD_TOOL_TRACE\"\n",
"else printf 'compiler stdin=eof\\n' >> \"$WW_CWD_TOOL_TRACE\"; fi\n",
@@ -1358,8 +1457,8 @@ fn cwdwritedata(dir: str, label: str) void = {
"#!/bin/sh\n",
"printf 'assembler cwd=' >> \"$WW_CWD_TOOL_TRACE\"\n",
"/bin/pwd >> \"$WW_CWD_TOOL_TRACE\"\n",
"printf 'assembler env=LC_ALL:%s TMPDIR:%s\\n' \"$LC_ALL\" ",
"\"$TMPDIR\" >> \"$WW_CWD_TOOL_TRACE\"\n",
"printf 'assembler env=LC_ALL:%s TMPDIR:%s PATH:%s\\n' \"$LC_ALL\" ",
"\"$TMPDIR\" \"$PATH\" >> \"$WW_CWD_TOOL_TRACE\"\n",
"if IFS= read -r WW_CWD_STDIN; then\n",
" printf 'assembler stdin=data\\n' >> \"$WW_CWD_TOOL_TRACE\"\n",
"else printf 'assembler stdin=eof\\n' >> \"$WW_CWD_TOOL_TRACE\"; fi\n",
@@ -1372,8 +1471,8 @@ fn cwdwritedata(dir: str, label: str) void = {
"#!/bin/sh\n",
"printf 'linker cwd=' >> \"$WW_CWD_TOOL_TRACE\"\n",
"/bin/pwd >> \"$WW_CWD_TOOL_TRACE\"\n",
"printf 'linker env=LC_ALL:%s TMPDIR:%s\\n' \"$LC_ALL\" ",
"\"$TMPDIR\" >> \"$WW_CWD_TOOL_TRACE\"\n",
"printf 'linker env=LC_ALL:%s TMPDIR:%s PATH:%s\\n' \"$LC_ALL\" ",
"\"$TMPDIR\" \"$PATH\" >> \"$WW_CWD_TOOL_TRACE\"\n",
"if IFS= read -r WW_CWD_STDIN; then\n",
" printf 'linker stdin=data\\n' >> \"$WW_CWD_TOOL_TRACE\"\n",
"else printf 'linker stdin=eof\\n' >> \"$WW_CWD_TOOL_TRACE\"; fi\n",
@@ -1417,9 +1516,14 @@ fn cwdwritedata(dir: str, label: str) void = {
assert(has(coldtrace, strings.concat("assembler cwd=", caller, "\n")));
assert(has(coldtrace, strings.concat("linker cwd=", caller, "\n")));
assert(!has(coldtrace, strings.concat("cwd=", p1, "\n")));
assert(has(coldtrace, "compiler env=LC_ALL:C TMPDIR:/tmp/"));
assert(has(coldtrace, "assembler env=LC_ALL:C TMPDIR:/tmp/"));
assert(has(coldtrace, "linker env=LC_ALL:C TMPDIR:/tmp/"));
assert(has(coldtrace,
"compiler env=LC_ALL:C TMPDIR:/tmp/"));
assert(has(coldtrace,
"assembler env=LC_ALL:C TMPDIR:/tmp/"));
assert(has(coldtrace,
"linker env=LC_ALL:C TMPDIR:/tmp/"));
assert(has(coldtrace, " PATH:/ww-path-inherited-last\n"));
assert(!has(coldtrace, strings.concat(" PATH:", repo(), "/out/bin:")));
assert(has(coldtrace, "compiler stdin=eof\n"));
assert(has(coldtrace, "assembler stdin=eof\n"));
assert(has(coldtrace, "linker stdin=eof\n"));
@@ -1528,7 +1632,7 @@ fn cwdwritedata(dir: str, label: str) void = {
"\"$WW_CWD_REAL_DRIVER\" \"$@\"\n",
"status=$?\n",
"if [ \"$status\" -eq 0 ]; then\n",
" mv -- \"$WW_CWD_HIDE_DIR\" \"$WW_CWD_HIDE_DIR.gone\" || exit 125\n",
" /bin/mv -- \"$WW_CWD_HIDE_DIR\" \"$WW_CWD_HIDE_DIR.gone\" || exit 125\n",
" printf 'wrapper-hidden=%s\\n' \"$WW_CWD_HIDE_DIR\" >&2\n",
"fi\n",
"exit \"$status\"\n"));
@@ -1545,7 +1649,7 @@ fn cwdwritedata(dir: str, label: str) void = {
driver(hidestages[i])));
append(hideenv, strings.concat("WW_CWD_HIDE_DIR=", p1));
let hideav: []str = [driver("wwtest"), "package", "--ww-driver",
hidewrapper, "-j", "2", "-I", tree, p1, p2];
"../build-then-hide.sh", "-j", "2", "-I", tree, p1, p2];
runcommandinputenvdir(root, strings.concat("cwd-hide-", hidestages[i]),
hideav, hideenv, caller, stdinpath,
(120i64 * (time.second: i64)): time.duration, &out);
@@ -1554,10 +1658,16 @@ fn cwdwritedata(dir: str, label: str) void = {
assert(has(out.stdout, strings.concat("FAIL ", p1,
" [p1] (test harness error 2)\n")));
assert(!has(out.stdout, "p1-internal cwd="));
cwdassertpackage(out.stdout, "dep-init", p2,
"p2-data", "p2-testdata");
cwdassertpackage(out.stdout, "p2-external", p2,
"p2-data", "p2-testdata");
cwdassertrecord(out.stdout, "dep-init", p2, p2, "1", "yes",
"p2-data", "p2-testdata", "eof");
cwdassertrecord(out.stdout, "p2-external", p2, p2, "1", "yes",
"p2-data", "p2-testdata", "eof");
cwdassertpath(out.stdout, "dep-init", strings.concat(root,
":/ww-path-inherited-first"), "1");
cwdassertpath(out.stdout, "p2-external", strings.concat(root,
":/ww-path-inherited-first"), "1");
cwdassertmergedstreams(out.stdout, "dep-init", p2);
cwdassertmergedstreams(out.stdout, "p2-external", p2);
assert(os.rename(strings.concat(p1, ".gone"), p1) == 0);
assert(!os.exists(strings.concat(p1, ".gone")));
hidestdout[i] = strings.dup(out.stdout);