ww: put selected toolchain first in test PATH
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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 87–94](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 1661–1668](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 29–45](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 1231–1265](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 20–44 and 66–84](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 1–41](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 459–478](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 15–26 and 39–45](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 311–355](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 706–740](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 5–31](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.
|
||||
|
||||
19
docs/spec.md
19
docs/spec.md
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user