ww build: discard exact null outputs
This commit is contained in:
@@ -7059,7 +7059,8 @@ do_build(int argc, char **argv)
|
||||
}
|
||||
char out[PATH_MAX];
|
||||
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
|
||||
* artifacts land beside the requested output (T3). */
|
||||
memcpy(out, outflag, strlen(outflag) + 1);
|
||||
@@ -7075,6 +7076,39 @@ do_build(int argc, char **argv)
|
||||
basename_no_ext(resolved, out, sizeof out);
|
||||
}
|
||||
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,
|
||||
&linkflags, outflag[0] != '\0', 0, 0, SEP_VARIANT_PRODUCTION, NULL,
|
||||
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) {
|
||||
struct stat first;
|
||||
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];
|
||||
const char *outp;
|
||||
int owntmp = !outstem[0] && !workdir[0];
|
||||
if (outstem[0]) outp = outstem;
|
||||
int owntmp = (!outstem[0] || discard_output) && !workdir[0];
|
||||
if (outstem[0] && !discard_output) outp = outstem;
|
||||
else if (workdir[0]) {
|
||||
/* The workdir owns the persistent test binary the same
|
||||
* 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;
|
||||
outp = tmp;
|
||||
}
|
||||
/* No-o redirects internal scratch to /tmp rather than beside the
|
||||
* source. An explicit -o names the caller-owned artifact stem. */
|
||||
/* No retained output keeps scratch private; exact /dev/null follows
|
||||
* Go's no-install test action rather than naming an artifact stem. */
|
||||
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,
|
||||
outstem[0] ? 1 : 0, workdir);
|
||||
outstem[0] && !discard_output ? 1 : 0, workdir);
|
||||
if (br != 0) {
|
||||
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
||||
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];
|
||||
const char *outp;
|
||||
int owntmp = !outstem[0] && !workdir[0];
|
||||
if (outstem[0]) outp = outstem;
|
||||
int owntmp = (!outstem[0] || discard_output) && !workdir[0];
|
||||
if (outstem[0] && !discard_output) outp = outstem;
|
||||
else if (workdir[0]) {
|
||||
int tn = snprintf(tmp, sizeof tmp, "%s/main", workdir);
|
||||
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;
|
||||
outp = tmp;
|
||||
}
|
||||
/* See module-mode note: no-o scratch is redirected to /tmp. */
|
||||
int br = build_one_sep(target, 0, NULL, outp, outstem[0] ? outstem : tmp,
|
||||
/* See module-mode note: an unretained output uses private scratch. */
|
||||
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,
|
||||
outstem[0] ? 1 : 0, workdir);
|
||||
outstem[0] && !discard_output ? 1 : 0, workdir);
|
||||
if (br != 0) {
|
||||
if (owntmp && unlink(outp) != 0 && errno != ENOENT)
|
||||
fputs("ww: cannot remove temporary output\n", stderr);
|
||||
|
||||
@@ -6405,6 +6405,134 @@ injected late-link request rollback over old files and modes; occupied-stage
|
||||
rejection; simultaneous builds with different umasks; diagnostic parity;
|
||||
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 459–558](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 450–455](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 81–92](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 631–646, 771–804, and 1259–1294](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 3–25](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 1–12](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 4–22](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 22–35](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
|
||||
|
||||
Five candidates were developed as coherent systems, not as feature bins.
|
||||
|
||||
19
docs/spec.md
19
docs/spec.md
@@ -319,6 +319,19 @@ ImportPath = ident { "." ident } .
|
||||
build therefore reuses unchanged semantic actions while refreshing the
|
||||
caller-visible output with the current invocation's mode. Assembly-only
|
||||
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.
|
||||
|
||||
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
|
||||
non-directory output may name only one package. Multiple packages whose
|
||||
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
|
||||
paths, and retained binary names remain presentation or loader metadata and do
|
||||
not become canonical package or action identity.
|
||||
|
||||
@@ -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
|
||||
package only. Multi-package non-directory output and duplicate visible binary
|
||||
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
|
||||
directory. Successful `-c` output is silent apart from no-test reporting.
|
||||
|
||||
|
||||
@@ -1844,6 +1844,8 @@ export fn packagecommand(args: []str) int = {
|
||||
};
|
||||
if (roots.len == 0) { append(roots, "."); };
|
||||
pkgsortstrings(roots);
|
||||
let buildnull: bool = buildonly && explicitout
|
||||
&& strings.compare(outname, "/dev/null") == 0;
|
||||
if (compileonly && !buildonly
|
||||
&& (list || filters.len != 0 || timeoutarg.len != 0)) {
|
||||
pkgusage();
|
||||
@@ -1946,11 +1948,11 @@ export fn packagecommand(args: []str) int = {
|
||||
ds.paths = pkgdedup(ds.paths);
|
||||
if (ds.paths.len == 0) {
|
||||
if (buildonly) {
|
||||
if (explicitout && pkgoutputdir(outname)) {
|
||||
if (explicitout && !buildnull && pkgoutputdir(outname)) {
|
||||
pkgputln(os.STDERR_FILENO, "ww: no main packages to build");
|
||||
return 1;
|
||||
};
|
||||
if (explicitout) {
|
||||
if (explicitout && !buildnull) {
|
||||
pkgputln(os.STDERR_FILENO, "ww: no packages to build");
|
||||
return 1;
|
||||
};
|
||||
@@ -2177,11 +2179,11 @@ export fn packagecommand(args: []str) int = {
|
||||
};
|
||||
if (groups.len == 0) {
|
||||
if (buildonly) {
|
||||
if (explicitout && pkgoutputdir(outname)) {
|
||||
if (explicitout && !buildnull && pkgoutputdir(outname)) {
|
||||
pkgputln(os.STDERR_FILENO, "ww: no main packages to build");
|
||||
return 1;
|
||||
};
|
||||
if (explicitout) {
|
||||
if (explicitout && !buildnull) {
|
||||
pkgputln(os.STDERR_FILENO, "ww: no packages to build");
|
||||
return 1;
|
||||
};
|
||||
@@ -2192,7 +2194,7 @@ export fn packagecommand(args: []str) int = {
|
||||
};
|
||||
pkgsortgroups(groups);
|
||||
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) {
|
||||
outname = pkgbase(groups[0].dir);
|
||||
defaultout = true;
|
||||
@@ -2203,7 +2205,8 @@ export fn packagecommand(args: []str) int = {
|
||||
pkgputln(os.STDERR_FILENO, "\" already exists and is a directory");
|
||||
return 1;
|
||||
};
|
||||
let outputdir: bool = buildonly && explicitout && pkgoutputdir(outname);
|
||||
let outputdir: bool = buildonly && explicitout && !buildnull
|
||||
&& pkgoutputdir(outname);
|
||||
if (outputdir) {
|
||||
let mainpackages: i32 = 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
|
||||
// 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,
|
||||
"wwtest package: cannot use -o with multiple packages");
|
||||
return 2;
|
||||
@@ -2338,7 +2342,7 @@ export fn packagecommand(args: []str) int = {
|
||||
// Recursive/multi-root -S needs a caller-owned artifact tree. Without -w
|
||||
// every assembly file would otherwise live only in the coordinator's
|
||||
// temporary plan and disappear on successful return.
|
||||
if (buildonly && emitasm && workroot.len == 0) {
|
||||
if (buildonly && emitasm && workroot.len == 0 && !buildnull) {
|
||||
pkgputln(os.STDERR_FILENO,
|
||||
"wwtest package: recursive -S needs -w");
|
||||
return 2;
|
||||
@@ -2360,7 +2364,7 @@ export fn packagecommand(args: []str) int = {
|
||||
plan.end = groups.len;
|
||||
plan.state = PKGQUEUED;
|
||||
plan.buildonly = buildonly;
|
||||
plan.publish = buildonly && explicitout && outname.len != 0
|
||||
plan.publish = buildonly && explicitout && !buildnull && outname.len != 0
|
||||
&& groups.len == 1
|
||||
&& strings.compare(groups[0].pkg, "main") != 0;
|
||||
plan.emitasm = emitasm;
|
||||
@@ -2380,11 +2384,13 @@ export fn packagecommand(args: []str) int = {
|
||||
};
|
||||
|
||||
let tmproot: str = temp.dir();
|
||||
let planout: str = outname;
|
||||
if (buildnull) { planout = ""; };
|
||||
let failed: i32 = 0;
|
||||
i = 0;
|
||||
for (i < plans.len) {
|
||||
if (!pkgsetplanpaths(&plans[i], groups, tmproot, i,
|
||||
compileonly, buildonly, outname, outputdir, createdir,
|
||||
compileonly, buildonly, planout, outputdir, createdir,
|
||||
workroot)) {
|
||||
if (!pkgremoveall(tmproot)) {
|
||||
pkgput(os.STDERR_FILENO,
|
||||
|
||||
@@ -8502,7 +8502,9 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
};
|
||||
let out: *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
|
||||
// artifacts land beside the requested output (T3).
|
||||
out = outflag;
|
||||
@@ -8531,6 +8533,34 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
lf.nlibs = nlibs;
|
||||
let rootidentity: *u8 = nil;
|
||||
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;
|
||||
if (outflag != nil && outflag[0u64] != 0u8) { publishpackage = 1; };
|
||||
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 = {
|
||||
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
tmp.len = os.PATH_MAX;
|
||||
// -o redirects the binary + its caller-owned sepwork intermediates
|
||||
// (objstem, T3) to <stem>; without -o both are driver-owned /tmp paths.
|
||||
// A retained -o redirects the binary and sepwork intermediates to its stem;
|
||||
// exact /dev/null needs the no-install path used by Go's test builder.
|
||||
let outp: *u8 = nil;
|
||||
let objstem: *u8 = nil;
|
||||
// owntmp: the driver owns (and must clean) the /tmp workspace; with
|
||||
// -o or -w the binary lands in a caller-owned location instead.
|
||||
// A retained output or -w has caller-owned storage; every other binary is
|
||||
// request-private and must be removed here.
|
||||
let owntmp: bool = false;
|
||||
if (outstem != nil) {
|
||||
let retainout: bool = outstem != nil
|
||||
&& !cstreqlit(outstem, "/dev/null");
|
||||
if (retainout) {
|
||||
outp = outstem;
|
||||
objstem = outstem;
|
||||
} else { if (workdir != nil) {
|
||||
@@ -8806,7 +8838,7 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32,
|
||||
lf.libs = nil;
|
||||
lf.nlibs = 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,
|
||||
0i32, 0i32, 1i32, SEP_VARIANT_PRODUCTION, nil,
|
||||
emitasm, keep, workdir);
|
||||
|
||||
@@ -542,6 +542,16 @@ fn directoryisempty(path: str) bool = {
|
||||
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 = {
|
||||
if (off < 0 || off + want.len > s.len) { return false; };
|
||||
let i: i32 = 0;
|
||||
@@ -12990,3 +13000,490 @@ fn runtimepath(relative: str) str = {
|
||||
assert(!directoryhasnew(parallelwwwork));
|
||||
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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user