ww build: discard exact null outputs

This commit is contained in:
2026-08-20 23:17:23 +09:00
parent 84f5e68709
commit 71a9da2058
7 changed files with 751 additions and 31 deletions

View File

@@ -7059,7 +7059,8 @@ do_build(int argc, char **argv)
} }
char out[PATH_MAX]; char out[PATH_MAX];
const char *objstem = NULL; const char *objstem = NULL;
if (outflag[0]) { int discard_output = strcmp(outflag, "/dev/null") == 0;
if (outflag[0] && !discard_output) {
/* -o sets both the binary path and the intermediate stem so /* -o sets both the binary path and the intermediate stem so
* artifacts land beside the requested output (T3). */ * artifacts land beside the requested output (T3). */
memcpy(out, outflag, strlen(outflag) + 1); memcpy(out, outflag, strlen(outflag) + 1);
@@ -7075,6 +7076,39 @@ do_build(int argc, char **argv)
basename_no_ext(resolved, out, sizeof out); basename_no_ext(resolved, out, sizeof out);
} }
const char *root_identity = !literal && is_dir ? src : NULL; const char *root_identity = !literal && is_dir ? src : NULL;
if (discard_output) {
char tmpdir[PATH_MAX], tmp[PATH_MAX];
int dn = snprintf(tmpdir, sizeof tmpdir, "/tmp/ww_build_%d", getpid());
if (dn < 0 || (size_t)dn >= sizeof tmpdir
|| mkdir(tmpdir, 0700) != 0) {
fputs("ww: cannot create temporary directory\n", stderr);
free(incs);
return 1;
}
int tn = snprintf(tmp, sizeof tmp, "%s/main", tmpdir);
if (tn < 0 || (size_t)tn >= sizeof tmp) {
(void)rmdir(tmpdir);
free(incs);
return 1;
}
/* Go's null output removes installation, while command linking and
* library compilation still need request-private product paths. */
int rc = build_one_sep(resolved, is_dir, root_identity, tmp, tmp, incs,
&linkflags, 0, 0, 0, SEP_VARIANT_PRODUCTION, NULL,
emit_asm, 0, workdir);
int cleanfail = 0;
if (unlink(tmp) != 0 && errno != ENOENT) {
fputs("ww: cannot remove temporary output\n", stderr);
cleanfail = 1;
}
if (rmdir(tmpdir) != 0) {
fputs("ww: cannot remove temporary directory\n", stderr);
cleanfail = 1;
}
free(incs);
if (cleanfail && rc == 0) rc = 1;
return rc;
}
int rc = build_one_sep(resolved, is_dir, root_identity, out, objstem, incs, int rc = build_one_sep(resolved, is_dir, root_identity, out, objstem, incs,
&linkflags, outflag[0] != '\0', 0, 0, SEP_VARIANT_PRODUCTION, NULL, &linkflags, outflag[0] != '\0', 0, 0, SEP_VARIANT_PRODUCTION, NULL,
emit_asm, 1, workdir); emit_asm, 1, workdir);
@@ -7491,6 +7525,7 @@ do_test(int argc, char **argv)
} }
} }
} }
int discard_output = strcmp(outstem, "/dev/null") == 0;
if (pattern != NULL) { if (pattern != NULL) {
struct stat first; struct stat first;
if (stat(target, &first) != 0 || !S_ISREG(first.st_mode)) { if (stat(target, &first) != 0 || !S_ISREG(first.st_mode)) {
@@ -7580,8 +7615,8 @@ do_test(int argc, char **argv)
} }
char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX]; char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX];
const char *outp; const char *outp;
int owntmp = !outstem[0] && !workdir[0]; int owntmp = (!outstem[0] || discard_output) && !workdir[0];
if (outstem[0]) outp = outstem; if (outstem[0] && !discard_output) outp = outstem;
else if (workdir[0]) { else if (workdir[0]) {
/* The workdir owns the persistent test binary the same /* The workdir owns the persistent test binary the same
* way it owns the package artifacts. */ * way it owns the package artifacts. */
@@ -7602,12 +7637,13 @@ do_test(int argc, char **argv)
if (tn < 0 || (size_t)tn >= sizeof tmp) return 1; if (tn < 0 || (size_t)tn >= sizeof tmp) return 1;
outp = tmp; outp = tmp;
} }
/* No-o redirects internal scratch to /tmp rather than beside the /* No retained output keeps scratch private; exact /dev/null follows
* source. An explicit -o names the caller-owned artifact stem. */ * Go's no-install test action rather than naming an artifact stem. */
int br = build_one_sep(resolved, is_dir, NULL, outp, int br = build_one_sep(resolved, is_dir, NULL, outp,
outstem[0] ? outstem : tmp, incs, NULL, 0, 0, 1, outstem[0] && !discard_output ? outstem : tmp,
incs, NULL, 0, 0, 1,
SEP_VARIANT_PRODUCTION, NULL, emit_asm, SEP_VARIANT_PRODUCTION, NULL, emit_asm,
outstem[0] ? 1 : 0, workdir); outstem[0] && !discard_output ? 1 : 0, workdir);
if (br != 0) { if (br != 0) {
if (owntmp && unlink(outp) != 0 && errno != ENOENT) if (owntmp && unlink(outp) != 0 && errno != ENOENT)
fputs("ww: cannot remove temporary output\n", stderr); fputs("ww: cannot remove temporary output\n", stderr);
@@ -7651,8 +7687,8 @@ do_test(int argc, char **argv)
} }
char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX]; char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX];
const char *outp; const char *outp;
int owntmp = !outstem[0] && !workdir[0]; int owntmp = (!outstem[0] || discard_output) && !workdir[0];
if (outstem[0]) outp = outstem; if (outstem[0] && !discard_output) outp = outstem;
else if (workdir[0]) { else if (workdir[0]) {
int tn = snprintf(tmp, sizeof tmp, "%s/main", workdir); int tn = snprintf(tmp, sizeof tmp, "%s/main", workdir);
if (tn < 0 || (size_t)tn >= sizeof tmp) { if (tn < 0 || (size_t)tn >= sizeof tmp) {
@@ -7671,10 +7707,11 @@ do_test(int argc, char **argv)
if (tn < 0 || (size_t)tn >= sizeof tmp) return 1; if (tn < 0 || (size_t)tn >= sizeof tmp) return 1;
outp = tmp; outp = tmp;
} }
/* See module-mode note: no-o scratch is redirected to /tmp. */ /* See module-mode note: an unretained output uses private scratch. */
int br = build_one_sep(target, 0, NULL, outp, outstem[0] ? outstem : tmp, int br = build_one_sep(target, 0, NULL, outp,
outstem[0] && !discard_output ? outstem : tmp,
incs, NULL, 0, 0, 1, SEP_VARIANT_PRODUCTION, NULL, emit_asm, incs, NULL, 0, 0, 1, SEP_VARIANT_PRODUCTION, NULL, emit_asm,
outstem[0] ? 1 : 0, workdir); outstem[0] && !discard_output ? 1 : 0, workdir);
if (br != 0) { if (br != 0) {
if (owntmp && unlink(outp) != 0 && errno != ENOENT) if (owntmp && unlink(outp) != 0 && errno != ENOENT)
fputs("ww: cannot remove temporary output\n", stderr); fputs("ww: cannot remove temporary output\n", stderr);

View File

@@ -6405,6 +6405,134 @@ injected late-link request rollback over old files and modes; occupied-stage
rejection; simultaneous builds with different umasks; diagnostic parity; rejection; simultaneous builds with different umasks; diagnostic parity;
artifact and binary byte identity; and absence of `.new` or umask-probe residue. artifact and binary byte identity; and absence of `.new` or umask-probe residue.
### 11.29 Implemented exact null-output discard for builds
Exact `ww build -o /dev/null` now removes output installation while preserving
the ordinary load and action graph. It is not a request to create an archive,
executable, interface, or scratch tree at the null-device pathname.
#### Pinned Go evidence and fact classification
The sole authority is official Go 1.26.5 at commit
`c19862e5f8415b4f24b189d065ed739517c548ba`:
- `runBuild` loads packages and reports load errors first, then recognizes the
null output and clears `BuildO` before any output-directory, multi-package,
or install-action branch. It finally constructs ordinary `ModeBuild` actions
for every selected package
([`cmd/go/internal/work/build.go`, lines 459558](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build.go#L459-L558)).
- `AutoAction` maps a main package in that ordinary mode to a link action and a
non-main package to a compile action
([`cmd/go/internal/work/action.go`, lines 450455](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L450-L455)).
- On the pinned Unix target, `IsNull` accepts exact `os.DevNull`, which is
`/dev/null`; its only additional spelling rule is the Windows-only
case-insensitive `NUL` exception
([`cmd/go/internal/base/path.go`, lines 8192](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/base/path.go#L81-L92)).
- The test builder treats a nonempty `-o` as a binary-retention request, exempts
the null device from multi-package output rejection, and makes a null target
use the private build action instead of an install action
([`cmd/go/internal/test/test.go`, lines 631646, 771804, and 12591294](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1259-L1294)).
- Official `devnull.txt` requires `go test -c -o $devnull` and a non-main
package `go build -o $devnull` to succeed without changing the device
([lines 325](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/devnull.txt#L3-L25)).
`build_dash_o_dev_null.txt` requires a command-line source build to succeed
without its default executable
([lines 112](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_dash_o_dev_null.txt#L1-L12)).
`build_cache_link.txt` requires a cold null build to compile and link and an
unchanged warm null build to skip compilation but link again
([lines 422](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_cache_link.txt#L4-L22)).
`TestRemoveDevNull` requires cleanup never to remove the device
([`cmd/go/internal/work/build_test.go`, lines 2235](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build_test.go#L22-L35)).
The load-before-output order, exact spelling, absence of installation, normal
command link, normal non-command compile, cold/warm link behavior, raw-source
behavior, and device preservation are directly implemented or asserted by the
pinned sources above. Acceptance of multiple mixed package roots and an empty
matched set is derived from clearing `BuildO` before the output-cardinality
branches and then iterating the ordinary package action list. That conclusion
applies to WW's manifest-free local package set without importing Go's module,
cache, or distribution model. No installed host Go behavior is authority.
#### Direct pre-fix measurements
Fresh public-driver probes measured both stages before production edits:
- raw source, one command directory, and one non-main library directory each
failed with exit 1 and `ww: cannot create scratch /dev/null.sepwork`;
- a two-command-plus-library request failed with exit 2 and
`wwtest package: cannot use -o with multiple packages`;
- a persistent command request reached the linker but failed through
`/dev/null.new`: Cstage reported `w6l: cannot open /dev/null.new`, WWstage
reported `w6l: cannot open output`, and both left the caller workdir empty;
- raw `ww test -c -o /dev/null FILE` and its running form failed at the same
adjacent-scratch acquisition, while directory `test -c` and running test
requests already built privately, discarded the retained copy, and preserved
their compile-only versus run distinction;
- a blank import of a missing package produced the same positioned
`cannot find package missing.pkg` diagnostic in Cstage and WWstage before
output setup; and
- `/dev/null` remained the same character device, mode, device/inode, and size
throughout the failed probes.
Those are directly measured WW facts. The externally observable gap was thus
the build/raw-driver interpretation of exact null as an artifact stem, plus the
coordinator's ordinary multi-output rejection, rather than a loader, compiler,
linker, or device-write defect.
#### Ownership, actions, publication, and identity
`internal/wwpackage.packagecommand` owns the shared package-request output
policy. After argument parsing and before output planning it records exact Unix
null discard. Loading, source classification, package/import validation,
canonical grouping, graph construction, and diagnostic precedence remain
unchanged. The coordinator suppresses default names, output-directory setup,
ordinary non-directory fan-out rejection, caller publication, and the
visibility-only `-S` workdir requirement, then gives every selected group a
request-private plan product. Commands still link; libraries still compile and
archive; mixed and repeated roots still use their canonical graph/action
deduplication. A recursive pattern matching no package emits its ordinary
warning and has no output-cardinality error.
The direct Cstage and WWstage drivers own raw or single-directory requests that
do not enter the coordinator. For exact null they allocate a private command
product, run the unchanged separate-compilation pipeline, and remove that
product and its scratch on every return. Their raw-test paths use the existing
unretained private test binary rather than setting `/dev/null` as output and
object stem. `-c` still suppresses execution; a running request still reports
ordinary pass, assertion, signal, and harness outcomes. Directory tests retain
their previously established private-runnable/null-publication behavior.
There is no caller-visible stage or destination to commit, occupy, replace, or
chmod. Producer failure or signal removes private plan state; persistent action
rollback preserves every prior unit, interface, assembly, object, archive,
tool record, and stamp. Successful persistent requests commit semantic actions
normally, unchanged actions are warm-reused, source changes invalidate their
owners, and command links still run for each request. Separate simultaneous
Cstage and WWstage requests own disjoint private products and workdirs. Exact
lookalikes remain normal caller-owned outputs and retain their existing
fan-out, `.sepwork`, permission, occupied-stage, transaction, and diagnostic
rules.
Output disposition remains request metadata. Dotted package identity, declared
name, physical source directory, import binding, graph edges, action/storage
keys, compiler/assembler/linker semantic argv, symbols, `.wwi`, unit and
artifact bytes, and persistent invalidation are unchanged. No persisted byte
contract changed: build workdir format remains `18`, test workdir format remains
`19`, and semantic storage remains `3`.
The WW-native owner `exact_null_output_discards_build_products` covers Cstage
and WWstage command, library, mixed-root, raw-build, assembly-only, and raw-test
routes; load/import precedence; empty-pattern warning; exact lookalike
rejection/publication; cold, warm, and invalidated persistence; normal link
actions and captured runnable bytes; injected linker failure and signal;
persistent rollback; concurrent stage isolation; device preservation; private
path and `.new` cleanup; semantic-artifact and captured-binary byte identity;
and diagnostic/output parity. Existing directory-test owners cover test-product
parallelism, timeout, child-setup failure, retained-output transactions, and
runtime cwd/environment/stdin. Build runtime behavior is inapplicable, and
caller-output rollback, occupied caller stages, and output permissions are
inapplicable to the exact discard branch because it creates no public inode.
## 12. Candidate architectures and hard-gate decision ## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins. Five candidates were developed as coherent systems, not as feature bins.

View File

@@ -319,6 +319,19 @@ ImportPath = ident { "." ident } .
build therefore reuses unchanged semantic actions while refreshing the build therefore reuses unchanged semantic actions while refreshing the
caller-visible output with the current invocation's mode. Assembly-only caller-visible output with the current invocation's mode. Assembly-only
builds publish no executable or archive. builds publish no executable or archive.
- On the Unix target, exact `-o /dev/null` is a build-output discard request,
not an ordinary output filename. Package loading, import validation, graph
construction, compilation, assembly, archiving, command linking, failure,
and persistent invalidation proceed normally; only installation of a
caller-visible command, archive, interface, default basename, or adjacent
scratch tree is omitted. One such request may select any number of command
and non-command packages, and an empty recursive match is successful after
its normal warning. `-S` likewise needs no caller workdir merely to retain
discarded assembly. Raw-file and directory roots use the same rule. A
non-exact spelling is an ordinary output and keeps all established fan-out,
rejection, permission, scratch, and publication behavior. Output discard is
request metadata; it does not change package, import, graph, action,
artifact, symbol, `.wwi`, or persistence identity.
- Only names marked `export` (§5) are visible across module boundaries. - Only names marked `export` (§5) are visible across module boundaries.
Import paths remain unquoted and dotted. Grouped imports, quoted import paths, Import paths remain unquoted and dotted. Grouped imports, quoted import paths,
@@ -662,7 +675,11 @@ directory. An output ending in `/` or naming an existing directory receives
that basename and may have missing parent directories created. One that basename and may have missing parent directories created. One
non-directory output may name only one package. Multiple packages whose non-directory output may name only one package. Multiple packages whose
visible import leaves would produce the same test-binary name reject before visible import leaves would produce the same test-binary name reject before
tools or output creation; exact `/dev/null` is the discard exception. Declared tools or output creation; exact `/dev/null` is the discard exception. It keeps
the ordinary private link and, unless `-c` is present, the ordinary execution,
but installs no retained copy. The raw single-file compatibility route uses
the same private-output rule and never treats `/dev/null` as an explicit
artifact or adjacent-scratch stem. Declared
package names, test variants, source filenames, physical directories, output package names, test variants, source filenames, physical directories, output
paths, and retained binary names remain presentation or loader metadata and do paths, and retained binary names remain presentation or loader metadata and do
not become canonical package or action identity. not become canonical package or action identity.

View File

@@ -227,7 +227,10 @@ naming an existing directory receives one such name per selected package and
missing parents are created. A single non-directory output is legal for one missing parents are created. A single non-directory output is legal for one
package only. Multi-package non-directory output and duplicate visible binary package only. Multi-package non-directory output and duplicate visible binary
names reject before tools; exact `/dev/null` discards every copy and permits names reject before tools; exact `/dev/null` discards every copy and permits
duplicate names. A package with no selected test source validates production duplicate names. The private test link still runs unless `-c` suppresses it;
the raw single-file compatibility route also links into driver-owned private
storage instead of using `/dev/null` as an artifact or `.sepwork` stem. A
package with no selected test source validates production
but publishes nothing and does not create an otherwise unneeded output but publishes nothing and does not create an otherwise unneeded output
directory. Successful `-c` output is silent apart from no-test reporting. directory. Successful `-c` output is silent apart from no-test reporting.

View File

@@ -1844,6 +1844,8 @@ export fn packagecommand(args: []str) int = {
}; };
if (roots.len == 0) { append(roots, "."); }; if (roots.len == 0) { append(roots, "."); };
pkgsortstrings(roots); pkgsortstrings(roots);
let buildnull: bool = buildonly && explicitout
&& strings.compare(outname, "/dev/null") == 0;
if (compileonly && !buildonly if (compileonly && !buildonly
&& (list || filters.len != 0 || timeoutarg.len != 0)) { && (list || filters.len != 0 || timeoutarg.len != 0)) {
pkgusage(); pkgusage();
@@ -1946,11 +1948,11 @@ export fn packagecommand(args: []str) int = {
ds.paths = pkgdedup(ds.paths); ds.paths = pkgdedup(ds.paths);
if (ds.paths.len == 0) { if (ds.paths.len == 0) {
if (buildonly) { if (buildonly) {
if (explicitout && pkgoutputdir(outname)) { if (explicitout && !buildnull && pkgoutputdir(outname)) {
pkgputln(os.STDERR_FILENO, "ww: no main packages to build"); pkgputln(os.STDERR_FILENO, "ww: no main packages to build");
return 1; return 1;
}; };
if (explicitout) { if (explicitout && !buildnull) {
pkgputln(os.STDERR_FILENO, "ww: no packages to build"); pkgputln(os.STDERR_FILENO, "ww: no packages to build");
return 1; return 1;
}; };
@@ -2177,11 +2179,11 @@ export fn packagecommand(args: []str) int = {
}; };
if (groups.len == 0) { if (groups.len == 0) {
if (buildonly) { if (buildonly) {
if (explicitout && pkgoutputdir(outname)) { if (explicitout && !buildnull && pkgoutputdir(outname)) {
pkgputln(os.STDERR_FILENO, "ww: no main packages to build"); pkgputln(os.STDERR_FILENO, "ww: no main packages to build");
return 1; return 1;
}; };
if (explicitout) { if (explicitout && !buildnull) {
pkgputln(os.STDERR_FILENO, "ww: no packages to build"); pkgputln(os.STDERR_FILENO, "ww: no packages to build");
return 1; return 1;
}; };
@@ -2192,7 +2194,7 @@ export fn packagecommand(args: []str) int = {
}; };
pkgsortgroups(groups); pkgsortgroups(groups);
let defaultout: bool = false; let defaultout: bool = false;
if (buildonly && outname.len == 0 && folders.len == 1 if (buildonly && !buildnull && outname.len == 0 && folders.len == 1
&& groups.len == 1 && strings.compare(groups[0].pkg, "main") == 0) { && groups.len == 1 && strings.compare(groups[0].pkg, "main") == 0) {
outname = pkgbase(groups[0].dir); outname = pkgbase(groups[0].dir);
defaultout = true; defaultout = true;
@@ -2203,7 +2205,8 @@ export fn packagecommand(args: []str) int = {
pkgputln(os.STDERR_FILENO, "\" already exists and is a directory"); pkgputln(os.STDERR_FILENO, "\" already exists and is a directory");
return 1; return 1;
}; };
let outputdir: bool = buildonly && explicitout && pkgoutputdir(outname); let outputdir: bool = buildonly && explicitout && !buildnull
&& pkgoutputdir(outname);
if (outputdir) { if (outputdir) {
let mainpackages: i32 = 0; let mainpackages: i32 = 0;
i = 0; i = 0;
@@ -2330,7 +2333,8 @@ export fn packagecommand(args: []str) int = {
}; };
// A non-directory caller-owned build name cannot fan out. A directory // A non-directory caller-owned build name cannot fan out. A directory
// build output publishes each selected command under its directory leaf. // build output publishes each selected command under its directory leaf.
if (buildonly && outname.len != 0 && !outputdir && groups.len > 1) { if (buildonly && !buildnull && outname.len != 0
&& !outputdir && groups.len > 1) {
pkgputln(os.STDERR_FILENO, pkgputln(os.STDERR_FILENO,
"wwtest package: cannot use -o with multiple packages"); "wwtest package: cannot use -o with multiple packages");
return 2; return 2;
@@ -2338,7 +2342,7 @@ export fn packagecommand(args: []str) int = {
// Recursive/multi-root -S needs a caller-owned artifact tree. Without -w // Recursive/multi-root -S needs a caller-owned artifact tree. Without -w
// every assembly file would otherwise live only in the coordinator's // every assembly file would otherwise live only in the coordinator's
// temporary plan and disappear on successful return. // temporary plan and disappear on successful return.
if (buildonly && emitasm && workroot.len == 0) { if (buildonly && emitasm && workroot.len == 0 && !buildnull) {
pkgputln(os.STDERR_FILENO, pkgputln(os.STDERR_FILENO,
"wwtest package: recursive -S needs -w"); "wwtest package: recursive -S needs -w");
return 2; return 2;
@@ -2360,7 +2364,7 @@ export fn packagecommand(args: []str) int = {
plan.end = groups.len; plan.end = groups.len;
plan.state = PKGQUEUED; plan.state = PKGQUEUED;
plan.buildonly = buildonly; plan.buildonly = buildonly;
plan.publish = buildonly && explicitout && outname.len != 0 plan.publish = buildonly && explicitout && !buildnull && outname.len != 0
&& groups.len == 1 && groups.len == 1
&& strings.compare(groups[0].pkg, "main") != 0; && strings.compare(groups[0].pkg, "main") != 0;
plan.emitasm = emitasm; plan.emitasm = emitasm;
@@ -2380,11 +2384,13 @@ export fn packagecommand(args: []str) int = {
}; };
let tmproot: str = temp.dir(); let tmproot: str = temp.dir();
let planout: str = outname;
if (buildnull) { planout = ""; };
let failed: i32 = 0; let failed: i32 = 0;
i = 0; i = 0;
for (i < plans.len) { for (i < plans.len) {
if (!pkgsetplanpaths(&plans[i], groups, tmproot, i, if (!pkgsetplanpaths(&plans[i], groups, tmproot, i,
compileonly, buildonly, outname, outputdir, createdir, compileonly, buildonly, planout, outputdir, createdir,
workroot)) { workroot)) {
if (!pkgremoveall(tmproot)) { if (!pkgremoveall(tmproot)) {
pkgput(os.STDERR_FILENO, pkgput(os.STDERR_FILENO,

View File

@@ -8502,7 +8502,9 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
}; };
let out: *u8 = nil; let out: *u8 = nil;
let objstem: *u8 = nil; let objstem: *u8 = nil;
if (outflag != nil && outflag[0u64] != 0u8) { let discardoutput: bool = outflag != nil
&& cstreqlit(outflag, "/dev/null");
if (outflag != nil && outflag[0u64] != 0u8 && !discardoutput) {
// -o sets both the binary path and the intermediate stem so // -o sets both the binary path and the intermediate stem so
// artifacts land beside the requested output (T3). // artifacts land beside the requested output (T3).
out = outflag; out = outflag;
@@ -8531,6 +8533,34 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
lf.nlibs = nlibs; lf.nlibs = nlibs;
let rootidentity: *u8 = nil; let rootidentity: *u8 = nil;
if (!requestedliteral && isdir != 0) { rootidentity = src; }; if (!requestedliteral && isdir != 0) { rootidentity = src; };
if (discardoutput) {
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
tmp.len = os.PATH_MAX;
makedrivertmp(tmp.ptr, "ww_build_");
if (os.mkdir(pathstr(tmp.ptr), 448i32) != 0) {
cerr("ww: cannot create temporary directory\n");
return 1;
};
let outp: *u8 = joinpathlit(tmp.ptr, "main");
// Go's null output removes installation, while command linking and
// library compilation still need request-private product paths.
let rc: i32 = buildonesep(selfdir, resolved, isdir, rootidentity,
outp, outp, incs.ptr, &lf,
0i32, 0i32, 0i32, SEP_VARIANT_PRODUCTION, nil,
emitasm, 0i32, workdir);
let cleanbad: bool = false;
let cleanrc: i32 = os.remove(pathstr(outp));
if (cleanrc != 0 && cleanrc != -2i32) {
cerr("ww: cannot remove temporary output\n");
cleanbad = true;
};
if (os.rmdir(pathstr(tmp.ptr)) != 0) {
cerr("ww: cannot remove temporary directory\n");
cleanbad = true;
};
if (cleanbad && rc == 0) { rc = 1; };
return rc;
};
let publishpackage: i32 = 0; let publishpackage: i32 = 0;
if (outflag != nil && outflag[0u64] != 0u8) { publishpackage = 1; }; if (outflag != nil && outflag[0u64] != 0u8) { publishpackage = 1; };
return buildonesep(selfdir, resolved, isdir, rootidentity, return buildonesep(selfdir, resolved, isdir, rootidentity,
@@ -8774,14 +8804,16 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
emitasm: i32, outstem: *u8, workdir: *u8, pattern: *u8) i32 = { emitasm: i32, outstem: *u8, workdir: *u8, pattern: *u8) i32 = {
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!; let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
tmp.len = os.PATH_MAX; tmp.len = os.PATH_MAX;
// -o redirects the binary + its caller-owned sepwork intermediates // A retained -o redirects the binary and sepwork intermediates to its stem;
// (objstem, T3) to <stem>; without -o both are driver-owned /tmp paths. // exact /dev/null needs the no-install path used by Go's test builder.
let outp: *u8 = nil; let outp: *u8 = nil;
let objstem: *u8 = nil; let objstem: *u8 = nil;
// owntmp: the driver owns (and must clean) the /tmp workspace; with // A retained output or -w has caller-owned storage; every other binary is
// -o or -w the binary lands in a caller-owned location instead. // request-private and must be removed here.
let owntmp: bool = false; let owntmp: bool = false;
if (outstem != nil) { let retainout: bool = outstem != nil
&& !cstreqlit(outstem, "/dev/null");
if (retainout) {
outp = outstem; outp = outstem;
objstem = outstem; objstem = outstem;
} else { if (workdir != nil) { } else { if (workdir != nil) {
@@ -8806,7 +8838,7 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
lf.libs = nil; lf.libs = nil;
lf.nlibs = 0; lf.nlibs = 0;
let keep: i32 = 0; let keep: i32 = 0;
if (outstem != nil) { keep = 1; }; if (retainout) { keep = 1; };
let bres: i32 = buildonesep(selfdir, src, 0, nil, outp, objstem, incs, &lf, let bres: i32 = buildonesep(selfdir, src, 0, nil, outp, objstem, incs, &lf,
0i32, 0i32, 1i32, SEP_VARIANT_PRODUCTION, nil, 0i32, 0i32, 1i32, SEP_VARIANT_PRODUCTION, nil,
emitasm, keep, workdir); emitasm, keep, workdir);

View File

@@ -542,6 +542,16 @@ fn directoryisempty(path: str) bool = {
return true; return true;
}; };
fn discardedlinkcleaned(marker: str) void = {
let output: str = readfile(marker);
assert(output.len != 0);
assert(!has(output, "/dev/null"));
let slash: i32 = output.len - 1;
for (slash >= 0 && output[slash] != '/') { slash -= 1; };
assert(slash > 0);
assert(!os.exists(strings.sub(output, 0, slash)));
};
fn byteshasat(s: str, off: i32, want: str) bool = { fn byteshasat(s: str, off: i32, want: str) bool = {
if (off < 0 || off + want.len > s.len) { return false; }; if (off < 0 || off + want.len > s.len) { return false; };
let i: i32 = 0; let i: i32 = 0;
@@ -12990,3 +13000,490 @@ fn runtimepath(relative: str) str = {
assert(!directoryhasnew(parallelwwwork)); assert(!directoryhasnew(parallelwwwork));
clean(root); clean(root);
}; };
@test fn exact_null_output_discards_build_products() void = {
let root: str = fresh();
let source: str = strings.concat(root, "/source");
let base: str = strings.concat(source, "/base");
let alpha: str = strings.concat(source, "/alpha");
let beta: str = strings.concat(source, "/beta");
let library: str = strings.concat(source, "/library");
let bad: str = strings.concat(source, "/bad");
let absent: str = strings.concat(source, "/absent");
mkdirall(base); mkdirall(alpha); mkdirall(beta);
mkdirall(library); mkdirall(bad); mkdirall(absent);
writefile(strings.concat(base, "/base.ww"), strings.concat(
"package base;\n",
"export fn value() i32 = { return 4; };\n"));
let alphafile: str = strings.concat(alpha, "/main.ww");
let alphaoriginal: str = strings.concat(
"package main;\nimport base;\n",
"fn main() i32 = { return base.value(); };\n");
let alphachanged: str = strings.concat(
"package main;\nimport base;\n",
"fn main() i32 = { return base.value() + 1; };\n");
let alphafailing: str = strings.concat(
"package main;\nimport base;\n",
"fn main() i32 = { return base.value() + 2; };\n");
writefile(alphafile, alphaoriginal);
writefile(strings.concat(beta, "/main.ww"),
"package main;\nfn main() i32 = { return 6; };\n");
writefile(strings.concat(library, "/library.ww"), strings.concat(
"package odd;\n",
"export fn value() i32 = { return 7; };\n"));
writefile(strings.concat(bad, "/main.ww"), strings.concat(
"package main;\nimport _ missing.pkg;\n",
"fn main() i32 = { return 0; };\n"));
let raw: str = strings.concat(source, "/raw.ww");
writefile(raw, "package main;\nfn main() i32 = { return 9; };\n");
let compilerwrapper: str = strings.concat(root, "/null-w6c.sh");
let assemblerwrapper: str = strings.concat(root, "/null-w6a.sh");
let linkerwrapper: str = strings.concat(root, "/null-w6l.sh");
writeexecutable(compilerwrapper, strings.concat(
"#!/bin/sh\n",
"printf '%s\\n' \"$*\" >> \"$WW_NULL_CTRACE\"\n",
"exec \"$WW_NULL_REAL_C\" \"$@\"\n"));
writeexecutable(assemblerwrapper, strings.concat(
"#!/bin/sh\n",
"printf '%s\\n' \"$*\" >> \"$WW_NULL_ATRACE\"\n",
"exec \"$WW_NULL_REAL_A\" \"$@\"\n"));
writeexecutable(linkerwrapper, strings.concat(
"#!/bin/sh\n",
"printf '%s\\n' \"$*\" >> \"$WW_NULL_LTRACE\"\n",
"out= take=\n",
"for arg do\n",
" if test \"$take\" = yes; then out=$arg; take=; continue; fi\n",
" if test \"$arg\" = -o; then take=yes; fi\n",
"done\n",
"printf '%s' \"$out\" > \"$WW_NULL_LAST_OUTPUT\"\n",
"case \"$WW_NULL_LINK_MODE\" in\n",
" fail) printf 'injected null-output linker failure\\n' >&2; exit 97;;\n",
" signal) kill -TERM \"$$\";;\n",
"esac\n",
"\"$WW_NULL_REAL_L\" \"$@\"\n",
"rc=$?\n",
"if test \"$rc\" -eq 0 && test -n \"$WW_NULL_CAPTURE\"; then\n",
" cp \"$out\" \"$WW_NULL_CAPTURE\" || exit 98\n",
"fi\n",
"exit \"$rc\"\n"));
let nullbefore: os.filestat;
match (os.stat(&nullbefore, "/dev/null")) {
case void => void;
case let e: os.oserror => abort("cannot stat /dev/null");
};
assert(((nullbefore.mode as u32) & 61440u32)
== (os.mode.CHR as u32));
let stages: []str = ["ww", "ww_ww"];
let compilers: []str = ["w6c", "w6c_ww"];
let assemblers: []str = ["w6a", "w6a_ww"];
let linkers: []str = ["w6l", "w6l_ww"];
let tags: []str = ["c", "ww"];
let coldref: str = "";
let changedref: str = "";
let rawref: str = "";
let lookalikeref: str = "";
let faildiagref: str = "";
let signaldiagref: str = "";
let missingdiagref: str = "";
let emptydiagref: str = "";
let rejectdiagref: str = "";
let testcompileref: str = "";
let testoutrefs: []str = ["", "", ""];
let testerrrefs: []str = ["", "", ""];
let baseenv: []str = os.getenvs();
let suffixes: []str = [".unit.ww", ".wwi", ".s", ".o", ".a"];
let out: commandout;
let si: i32 = 0;
for (si < stages.len) {
rewritefile(alphafile, alphaoriginal);
let work: str = strings.concat(root, "/work-", tags[si]);
mkdirall(work);
let ctrace: str = strings.concat(root, "/", tags[si], "-c.trace");
let atrace: str = strings.concat(root, "/", tags[si], "-a.trace");
let ltrace: str = strings.concat(root, "/", tags[si], "-l.trace");
let lastout: str = strings.concat(root, "/", tags[si], "-last.out");
let capture: str = strings.concat(root, "/", tags[si], "-capture");
writefile(ctrace, ""); writefile(atrace, "");
writefile(ltrace, ""); writefile(lastout, "");
let env: []str = alloc([], (baseenv.len + 12): u64)!;
let ei: i32 = 0;
for (ei < baseenv.len) {
if (!strings.hasprefix(baseenv[ei], "WW_W6C=")
&& !strings.hasprefix(baseenv[ei], "WW_W6A=")
&& !strings.hasprefix(baseenv[ei], "WW_W6L=")
&& !strings.hasprefix(baseenv[ei], "WW_NULL_")) {
append(env, baseenv[ei]);
};
ei += 1;
};
append(env, strings.concat("WW_W6C=", compilerwrapper));
append(env, strings.concat("WW_W6A=", assemblerwrapper));
append(env, strings.concat("WW_W6L=", linkerwrapper));
append(env, strings.concat("WW_NULL_CTRACE=", ctrace));
append(env, strings.concat("WW_NULL_ATRACE=", atrace));
append(env, strings.concat("WW_NULL_LTRACE=", ltrace));
append(env, strings.concat("WW_NULL_REAL_C=",
driver(compilers[si])));
append(env, strings.concat("WW_NULL_REAL_A=",
driver(assemblers[si])));
append(env, strings.concat("WW_NULL_REAL_L=",
driver(linkers[si])));
let linkmodeindex: i32 = env.len;
append(env, "WW_NULL_LINK_MODE=");
let captureindex: i32 = env.len;
append(env, strings.concat("WW_NULL_CAPTURE=", capture));
append(env, strings.concat("WW_NULL_LAST_OUTPUT=", lastout));
// Exact null is no-install policy. Loading, dependency production,
// assembly, archiving, and the command link still occur normally.
let coldav: []str = [driver(stages[si]), "build", "-w", work,
"-I", source, "-o", "/dev/null", alpha];
runcommandenvdir(root, strings.concat("null-cold-", tags[si]), coldav,
env, root, (120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0);
assert(occurrences(readfile(ctrace), "\n") == 2);
assert(occurrences(readfile(atrace), "\n") == 3);
assert(occurrences(readfile(ltrace), "\n") == 1);
assert(has(readfile(ctrace), "/base.unit.new"));
assert(has(readfile(ctrace), "/alpha.unit.new"));
discardedlinkcleaned(lastout);
let coldbytes: str = readfile(capture);
if (si == 0) { coldref = strings.dup(coldbytes); }
else { assert(same(coldref, coldbytes)); };
let runav: []str = [capture];
runcommand(root, strings.concat("null-cold-run-", tags[si]), runav,
time.second, &out);
expectexit(&out, 4);
let originalunit: str = readfile(strings.concat(work,
"/alpha.unit.ww"));
// Unchanged actions are warm-reused, but the command links again into
// fresh private storage because null output is not a result cache.
rewritefile(ctrace, ""); rewritefile(atrace, "");
rewritefile(ltrace, ""); rewritefile(lastout, "");
rewritefile(capture, "");
runcommandenvdir(root, strings.concat("null-warm-", tags[si]), coldav,
env, root, (120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(readfile(ctrace).len == 0 && readfile(atrace).len == 0);
assert(occurrences(readfile(ltrace), "\n") == 1);
assert(same(coldbytes, readfile(capture)));
discardedlinkcleaned(lastout);
// Source change invalidates the owning action and linked bytes while
// preserving the same action names and persistent representation.
rewritefile(alphafile, alphachanged);
rewritefile(ctrace, ""); rewritefile(atrace, "");
rewritefile(ltrace, ""); rewritefile(lastout, "");
rewritefile(capture, "");
runcommandenvdir(root, strings.concat("null-change-", tags[si]), coldav,
env, root, (120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(occurrences(readfile(ctrace), "\n") == 1);
assert(occurrences(readfile(atrace), "\n") == 1);
assert(occurrences(readfile(ltrace), "\n") == 1);
assert(!same(originalunit, readfile(strings.concat(work,
"/alpha.unit.ww"))));
let changedbytes: str = readfile(capture);
assert(!same(coldbytes, changedbytes));
if (si == 0) { changedref = strings.dup(changedbytes); }
else { assert(same(changedref, changedbytes)); };
discardedlinkcleaned(lastout);
let kept: []str = alloc([], suffixes.len: u64)!;
let xi: i32 = 0;
for (xi < suffixes.len) {
append(kept, strings.dup(readfile(strings.concat(work,
"/alpha", suffixes[xi]))));
xi += 1;
};
// A late producer failure and a producer signal cannot publish and
// cannot commit an invalidated persistent generation.
rewritefile(alphafile, alphafailing);
rewritefile(ctrace, ""); rewritefile(atrace, "");
rewritefile(ltrace, ""); rewritefile(lastout, "");
env[linkmodeindex] = "WW_NULL_LINK_MODE=fail";
runcommandenvdir(root, strings.concat("null-fail-", tags[si]), coldav,
env, root, (120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(has(out.stderr, "injected null-output linker failure\n"));
assert(has(out.stderr, "ww: w6l failed\n"));
assert(occurrences(readfile(ltrace), "\n") == 1);
discardedlinkcleaned(lastout);
xi = 0;
for (xi < suffixes.len) {
assert(same(kept[xi], readfile(strings.concat(work,
"/alpha", suffixes[xi]))));
xi += 1;
};
assert(!directoryhasnew(work));
if (si == 0) { faildiagref = strings.dup(out.stderr); }
else { assert(same(faildiagref, out.stderr)); };
rewritefile(ctrace, ""); rewritefile(atrace, "");
rewritefile(ltrace, ""); rewritefile(lastout, "");
env[linkmodeindex] = "WW_NULL_LINK_MODE=signal";
runcommandenvdir(root, strings.concat("null-signal-", tags[si]), coldav,
env, root, (120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(has(out.stderr, "ww: w6l failed\n"));
discardedlinkcleaned(lastout);
xi = 0;
for (xi < suffixes.len) {
assert(same(kept[xi], readfile(strings.concat(work,
"/alpha", suffixes[xi]))));
xi += 1;
};
assert(!directoryhasnew(work));
if (si == 0) { signaldiagref = strings.dup(out.stderr); }
else { assert(same(signaldiagref, out.stderr)); };
env[linkmodeindex] = "WW_NULL_LINK_MODE=";
// Import loading and empty-pattern handling precede output planning.
// The exact null exception changes neither diagnostic nor tool order.
rewritefile(ctrace, ""); rewritefile(atrace, "");
rewritefile(ltrace, ""); rewritefile(lastout, "");
let badav: []str = [driver(stages[si]), "build", "-I", source,
"-o", "/dev/null", bad];
runcommandenvdir(root, strings.concat("null-import-", tags[si]), badav,
env, root, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(has(out.stderr, "cannot find package missing.pkg\n"));
assert(readfile(ctrace).len == 0 && readfile(atrace).len == 0
&& readfile(ltrace).len == 0 && readfile(lastout).len == 0);
if (si == 0) { missingdiagref = strings.dup(out.stderr); }
else { assert(same(missingdiagref, out.stderr)); };
let emptyroot: str = strings.concat(source, "/absent/...");
let emptyav: []str = [driver(stages[si]), "build", "-o",
"/dev/null", emptyroot];
runcommandenvdir(root, strings.concat("null-empty-", tags[si]), emptyav,
env, root, time.second, &out);
expectexit(&out, 0);
assert(has(out.stderr, "matched no packages\n"));
assert(readfile(ctrace).len == 0 && readfile(ltrace).len == 0);
if (si == 0) { emptydiagref = strings.dup(out.stderr); }
else { assert(same(emptydiagref, out.stderr)); };
// One discard request may contain commands and a non-main library.
// Every action runs, only commands link, and no basename is published.
rewritefile(alphafile, alphaoriginal);
rewritefile(ctrace, ""); rewritefile(atrace, "");
rewritefile(ltrace, ""); rewritefile(lastout, "");
let multiav: []str = [driver(stages[si]), "build", "-I", source,
"-o", "/dev/null", alpha, beta, library];
runcommandenvdir(root, strings.concat("null-multi-", tags[si]), multiav,
env, root, (120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0);
assert(occurrences(readfile(ctrace), "\n") == 4);
assert(occurrences(readfile(atrace), "\n") == 6);
assert(occurrences(readfile(ltrace), "\n") == 2);
assert(!os.exists(strings.concat(root, "/alpha")));
assert(!os.exists(strings.concat(root, "/beta")));
assert(!os.exists(strings.concat(root, "/library")));
assert(!os.exists("/dev/null.new"));
assert(!os.exists("/dev/null.sepwork"));
// Assembly-only keeps the same loading/action exception, has no link,
// and needs no persistent workdir solely to make discarded files visible.
rewritefile(ctrace, ""); rewritefile(atrace, "");
rewritefile(ltrace, ""); rewritefile(lastout, "");
let asmav: []str = [driver(stages[si]), "build", "-S", "-I", source,
"-o", "/dev/null", alpha, beta, library];
runcommandenvdir(root, strings.concat("null-asm-", tags[si]), asmav,
env, root, (120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(occurrences(readfile(ctrace), "\n") == 4);
assert(readfile(atrace).len == 0 && readfile(ltrace).len == 0);
// The direct raw-file route links privately too. A wrapper capture proves
// the linked bytes are runnable before the driver's cleanup.
rewritefile(ctrace, ""); rewritefile(atrace, "");
rewritefile(ltrace, ""); rewritefile(lastout, "");
rewritefile(capture, "");
let rawav: []str = [driver(stages[si]), "build", "-o",
"/dev/null", raw];
runcommandenvdir(root, strings.concat("null-raw-", tags[si]), rawav,
env, root, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(occurrences(readfile(ltrace), "\n") == 1);
discardedlinkcleaned(lastout);
let rawbytes: str = readfile(capture);
if (si == 0) { rawref = strings.dup(rawbytes); }
else { assert(same(rawref, rawbytes)); };
let rawrun: []str = [capture];
runcommand(root, strings.concat("null-raw-run-", tags[si]), rawrun,
time.second, &out);
expectexit(&out, 9);
// Null recognition is exact. A regular lookalike is retained with its
// established adjacent scratch; a multi-root lookalike still rejects.
let lookalike: str = strings.concat(root, "/null-like-", tags[si]);
rewritefile(ctrace, ""); rewritefile(atrace, "");
rewritefile(ltrace, ""); rewritefile(lastout, "");
env[captureindex] = "WW_NULL_CAPTURE=";
let lookav: []str = [driver(stages[si]), "build", "-o",
lookalike, raw];
runcommandenvdir(root, strings.concat("null-look-", tags[si]), lookav,
env, root, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(os.exists(lookalike));
assert(os.exists(strings.concat(lookalike, ".sepwork")));
let lookbytes: str = readfile(lookalike);
if (si == 0) { lookalikeref = strings.dup(lookbytes); }
else { assert(same(lookalikeref, lookbytes)); };
let rejectout: str = strings.concat(root, "/multi-like-", tags[si]);
rewritefile(ctrace, ""); rewritefile(atrace, "");
rewritefile(ltrace, "");
let rejectav: []str = [driver(stages[si]), "build", "-I", source,
"-o", rejectout, alpha, beta];
runcommandenvdir(root, strings.concat("null-reject-", tags[si]),
rejectav, env, root, time.second, &out);
expectexit(&out, 2);
assert(has(out.stderr, "cannot use -o with multiple packages\n"));
assert(readfile(ctrace).len == 0 && readfile(atrace).len == 0
&& readfile(ltrace).len == 0);
if (si == 0) { rejectdiagref = strings.dup(out.stderr); }
else { assert(same(rejectdiagref, out.stderr)); };
env[captureindex] = strings.concat("WW_NULL_CAPTURE=", capture);
// Raw test compilation has the same private-output rule. Running forms
// preserve ordinary success, assertion, and signal outcomes.
let compileav: []str = [driver(stages[si]), "test", "-c", "-o",
"/dev/null", runtimepath("success_test.ww")];
rewritefile(ctrace, ""); rewritefile(atrace, "");
rewritefile(ltrace, ""); rewritefile(lastout, "");
rewritefile(capture, "");
runcommandenvdir(root, strings.concat("null-test-c-", tags[si]),
compileav, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0);
assert(occurrences(readfile(ltrace), "\n") == 1);
discardedlinkcleaned(lastout);
let testbytes: str = readfile(capture);
if (si == 0) { testcompileref = strings.dup(testbytes); }
else { assert(same(testcompileref, testbytes)); };
let runtargets: []str = ["success_test.ww", "fail_test.ww",
"signal_test.ww"];
let exitcodes: []i32 = [0, 1, 1];
let needles: []str = ["alpha_pass ... ok\n",
"assertion_failure ... FAIL (exit 1)\n",
"signal_is_not_exit ... FAIL (signal 15)\n"];
let ri: i32 = 0;
for (ri < runtargets.len) {
let testav: []str = [driver(stages[si]), "test"];
append(testav, "-o"); append(testav, "/dev/null");
append(testav, runtimepath(runtargets[ri]));
rewritefile(ctrace, ""); rewritefile(atrace, "");
rewritefile(ltrace, ""); rewritefile(lastout, "");
runcommandenvdir(root, strings.concat("null-test-run-", tags[si],
"-", boundarypkgname(ri)), testav, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, exitcodes[ri]);
assert(has(out.stdout, needles[ri]));
assert(occurrences(readfile(ltrace), "\n") == 1);
discardedlinkcleaned(lastout);
if (si == 0) {
testoutrefs[ri] = strings.dup(out.stdout);
testerrrefs[ri] = strings.dup(out.stderr);
} else {
assert(same(testoutrefs[ri], out.stdout));
assert(same(testerrrefs[ri], out.stderr));
};
ri += 1;
};
assert(!os.exists("/dev/null.new"));
assert(!os.exists("/dev/null.sepwork"));
si += 1;
};
// Persisted semantic artifacts remain byte-identical across stages. Output
// disposition is request metadata and does not enter these identities.
let actions: []str = ["base", "alpha"];
let ai: i32 = 0;
for (ai < actions.len) {
let xi: i32 = 0;
for (xi < suffixes.len) {
assert(same(readfile(strings.concat(root, "/work-c/",
actions[ai], suffixes[xi])), readfile(strings.concat(root,
"/work-ww/", actions[ai], suffixes[xi]))));
xi += 1;
};
ai += 1;
};
// Independent Cstage and WWstage discard requests can execute together.
// Their private products and persistent generations remain isolated.
let pcwork: str = strings.concat(root, "/parallel-c-work");
let pwwork: str = strings.concat(root, "/parallel-ww-work");
mkdirall(pcwork); mkdirall(pwwork);
let pcav: []str = [driver("ww"), "build", "-w", pcwork,
"-I", source, "-o", "/dev/null", alpha, beta, library];
let pwav: []str = [driver("ww_ww"), "build", "-w", pwwork,
"-I", source, "-o", "/dev/null", alpha, beta, library];
let pc: exec.command;
pc.path = pcav[0]; pc.argv = pcav; pc.env = os.getenvs(); pc.dir = root;
pc.stdoutpath = strings.concat(root, "/parallel-c.stdout");
pc.stderrpath = strings.concat(root, "/parallel-c.stderr");
pc.deadline = time.add(time.now(time.clock.monotonic),
(120i64 * (time.second: i64)): time.duration);
pc.grace = (100i64 * (time.millisecond: i64)): time.duration;
let pw: exec.command;
pw.path = pwav[0]; pw.argv = pwav; pw.env = os.getenvs(); pw.dir = root;
pw.stdoutpath = strings.concat(root, "/parallel-ww.stdout");
pw.stderrpath = strings.concat(root, "/parallel-ww.stderr");
pw.deadline = time.add(time.now(time.clock.monotonic),
(120i64 * (time.second: i64)): time.duration);
pw.grace = (100i64 * (time.millisecond: i64)): time.duration;
let pcp: exec.process;
let pwp: exec.process;
exec.start(&pcp, &pc); exec.start(&pwp, &pw);
let pcdone: bool = false;
let pwdone: bool = false;
for (!pcdone || !pwdone) {
if (!pcdone) { pcdone = exec.poll(&pcp); };
if (!pwdone) { pwdone = exec.poll(&pwp); };
if (!pcdone || !pwdone) {
time.sleep(time.millisecond, time.clock.monotonic);
};
};
assert(pcp.result.errno == 0 && pcp.result.cleanuperrno == 0);
assert(pwp.result.errno == 0 && pwp.result.cleanuperrno == 0);
assert(pcp.result.termination == exec.termination.EXIT
&& pcp.result.code == 0);
assert(pwp.result.termination == exec.termination.EXIT
&& pwp.result.code == 0);
assert(readfile(pc.stdoutpath).len == 0 && readfile(pc.stderrpath).len == 0);
assert(readfile(pw.stdoutpath).len == 0 && readfile(pw.stderrpath).len == 0);
assert(!directoryhasnew(pcwork) && !directoryhasnew(pwwork));
ai = 0;
let parallel: []str = ["base", "alpha", "beta", "library"];
for (ai < parallel.len) {
let xi: i32 = 0;
for (xi < suffixes.len) {
assert(same(readfile(strings.concat(pcwork, "/", parallel[ai],
suffixes[xi])), readfile(strings.concat(pwwork, "/",
parallel[ai], suffixes[xi]))));
xi += 1;
};
ai += 1;
};
let nullafter: os.filestat;
match (os.stat(&nullafter, "/dev/null")) {
case void => void;
case let e: os.oserror => abort("cannot restat /dev/null");
};
assert((nullafter.mode as u32) == (nullbefore.mode as u32));
assert(nullafter.inode == nullbefore.inode);
assert(nullafter.sz == nullbefore.sz);
assert(!os.exists("/dev/null.new"));
assert(!os.exists("/dev/null.sepwork"));
clean(root);
};