ww test: omit marker for empty list results
This commit is contained in:
@@ -7628,6 +7628,149 @@ unchanged artifact and binary bytes; build/no-test/compile-only/rejection
|
||||
nonexecution; unchanged raw/direct compatibility; exact tool locale/TMPDIR; and
|
||||
stage, transaction, capture, and workdir cleanup.
|
||||
|
||||
### 11.37 Implemented empty list-mode output
|
||||
|
||||
A valid directory-owned `ww test -list` request now emits one qualified test
|
||||
name per selected descriptor and emits no harness list bytes when its filters
|
||||
select zero tests. The test product still starts, package initialization still
|
||||
runs, the harness returns success without accounting, and the coordinator still
|
||||
emits the normal package `ok` result. Ordinary non-list execution with zero
|
||||
selected tests remains distinct: it keeps `[no matches]` and its
|
||||
discovered/selected/started/completed accounting.
|
||||
|
||||
#### Pinned Go evidence and fact classification
|
||||
|
||||
The sole authority is official Go 1.26.5 at commit
|
||||
`c19862e5f8415b4f24b189d065ed739517c548ba`:
|
||||
|
||||
- `cmd/go` registers `-list` among the flags forwarded to the test binary
|
||||
([`cmd/go/internal/test/testflag.go`, function `init`, lines 32–80](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/testflag.go#L32-L80)).
|
||||
- `testing.(*M).Run` calls `listTests`, sets exit code 0, and returns before the
|
||||
ordinary test execution and no-tests warning
|
||||
([`testing/testing.go`, lines 2407–2411](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/testing/testing.go#L2407-L2411)
|
||||
and [2440–2458](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/testing/testing.go#L2440-L2458)).
|
||||
- `listTests` validates the pattern and prints only inside successful match
|
||||
branches. It has no zero-match output branch
|
||||
([`testing/testing.go`, function `listTests`, lines 2509–2535](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/testing/testing.go#L2509-L2535)).
|
||||
- `testShowPass` retains successful list output, while the successful run actor
|
||||
adds a no-tests suffix only if the captured bytes contain the ordinary
|
||||
no-tests warning
|
||||
([`cmd/go/internal/test/test.go`, function `testShowPass`, lines 649–652](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L649-L652)
|
||||
and method `(*runTestActor).Act`, lines 1706–1732](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1706-L1732)).
|
||||
- Official `list_test_simple.txt` asserts that list mode emits the matching Test,
|
||||
Benchmark, and Example names
|
||||
([`cmd/go/testdata/script/list_test_simple.txt`, lines 3–14](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/list_test_simple.txt#L3-L14)).
|
||||
|
||||
Those source branches and official positive assertions are behavior directly
|
||||
implemented or asserted by pinned Go. That a zero-match `go test -list` run has
|
||||
no list payload, avoids the ordinary no-tests warning, and may still receive the
|
||||
command's normal successful package result is behavior derived from their
|
||||
composition. WW retains its local `-list` plus `-run`/`-filter` syntax; only the
|
||||
applicable selected-name and empty-result behavior is aligned.
|
||||
|
||||
#### Fresh four-axis audit and direct pre-fix measurements
|
||||
|
||||
The bounded audit examined all four permanent axes before selecting this test
|
||||
runtime difference:
|
||||
|
||||
- **Go-like build:** pinned `(*ErrorReporter).errorUnresolved` gives a missing
|
||||
`main.main` a dedicated failure
|
||||
([`cmd/link/internal/ld/errors.go`, lines 29–67](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/errors.go#L29-L67)),
|
||||
asserted by `TestUndefinedRelocErrors` and official `issue10978`
|
||||
([`cmd/link/internal/ld/ld_test.go`, lines 19–45](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/ld_test.go#L19-L45),
|
||||
[`testdata/issue10978/main.go`, lines 5–27](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/testdata/issue10978/main.go#L5-L27)).
|
||||
Both WW stages rejected a selected command package without `fn main`, emitted
|
||||
zero stdout and the same 50 stderr bytes (SHA-256
|
||||
`9ed4d7684412c6d2e615041902072c81e9e09acb3970246d89a2c8bdddd2fcfa`),
|
||||
and published no output. This audited applicable property was aligned.
|
||||
- **Go-like test:** for a directory containing one `visible` test, both stages
|
||||
ran `test -list -run no_such_test`, exited 0 with empty stderr, and emitted the
|
||||
same 78 stdout bytes (SHA-256
|
||||
`d59638ab03a27803ca8e3fd884f341bbb1535ec9604fb69bdff1de4f608a8da0`):
|
||||
`[no matches]\n` followed by the normal package result. Positive list
|
||||
selection printed `list_nomatch.visible` once in both stages. This synthetic
|
||||
empty-result line was the selected difference.
|
||||
- **Go-like package:** pinned `MultiplePackageError` and directory scanning
|
||||
reject conflicting declarations
|
||||
([`go/build/build.go`, lines 538–549](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L538-L549)
|
||||
and [939–967](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L939-L967)),
|
||||
asserted by `TestMultiplePackageImport` and official `testdata/multi`
|
||||
([`go/build/build_test.go`, lines 105–133](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build_test.go#L105-L133)).
|
||||
Both WW stages rejected an `alpha`/`beta` directory with zero stdout and the
|
||||
same 159 stderr bytes (SHA-256
|
||||
`eaaa0c91f41b5d3deac4caf4299d5c7c650a1330be9b43edd090b8bfea906076`).
|
||||
This audited applicable property was aligned.
|
||||
- **Go-like import:** pinned `unusedImports` and `errorUnusedPkg` reject a
|
||||
nonblank unused alias
|
||||
([`cmd/compile/internal/types2/resolver.go`, lines 706–740](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L706-L740)),
|
||||
asserted by official `importdecl0`
|
||||
([`internal/types/testdata/check/importdecl0/importdecl0a.go`, lines 9–26](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/importdecl0/importdecl0a.go#L9-L26)).
|
||||
Both WW stages rejected an unused `spare` alias for dotted import `dep` with
|
||||
zero stdout and the same diagnostic after only private scratch-PID
|
||||
normalization (SHA-256
|
||||
`a429b027e92d52de1c5ec581b1f45c860b0e85b459c3ac7347e2719900cec511`).
|
||||
This audited applicable property was aligned without changing dotted import
|
||||
identity.
|
||||
|
||||
The command observations and byte hashes are directly measured WW behavior.
|
||||
The linked source branches and testdata assertions are behavior directly
|
||||
implemented or asserted by pinned Go. Applying the empty-list rule to WW's one
|
||||
local directory-owned test product while retaining its manifest-free input and
|
||||
filter syntax is behavior derived from that pinned execution boundary.
|
||||
|
||||
Pre-fix `test -c` products were byte-identical between Cstage and WWstage:
|
||||
112829 bytes, SHA-256
|
||||
`d2994ef440d7ceb9be0a7caf53c90dd19d208e847e51ff41ecb89504f825c4c8`.
|
||||
Directly running either retained product with the same nonmatching list filter
|
||||
already emitted no stdout or stderr because it had no coordinator-supplied
|
||||
package prefix. Explicit raw-file requests with package options remained a
|
||||
separate rejected CLI shape in both stages.
|
||||
|
||||
#### Ownership, final behavior, and preserved boundaries
|
||||
|
||||
`lib/test.run` is the semantic owner. Its existing descriptor loop still
|
||||
qualifies, filters, and prints every positive list match in order; its list
|
||||
return now emits nothing extra when the selected count is zero. The package
|
||||
coordinator does not recognize or strip a magic line, so identical bytes written
|
||||
by package initialization or user code remain ordinary captured output.
|
||||
|
||||
Loading and platform source selection are unchanged. Production,
|
||||
internal-test, external-test, recompiled-for-test, support, and generated-main
|
||||
nodes and actions remain unchanged. Exact dotted import identity, declared
|
||||
package names, aliases, variants, physical runtime directories, graph edges,
|
||||
initialization order, symbols, and publication names keep their existing roles.
|
||||
The product process and package initialization still run in list mode; no
|
||||
per-test child starts. Positive matching, option diagnostics and precedence,
|
||||
ordinary non-list no-match output, no-test-file results, and raw-file rejection
|
||||
are unchanged.
|
||||
|
||||
The shared support implementation change legitimately changes its object,
|
||||
archive, and linked test-product bytes. Its exported signature and `.wwi` byte
|
||||
contract do not change. Existing content invalidation rebuilds the affected
|
||||
support/link actions; there is no test-result cache and no new graph identity.
|
||||
Running `-o` still executes the private product before guarded publication, and
|
||||
`-c`, destination safety, transaction rollback, prior-output preservation, and
|
||||
artifact modes are unchanged.
|
||||
|
||||
Load, compile, assemble, archive, link, initialization, signal, timeout,
|
||||
interruption, child-start, publication, and cleanup failure paths retain their
|
||||
existing diagnostics and precedence. Parallel products keep independent
|
||||
processes, captures, environments, working directories, input descriptors, and
|
||||
ordered result slots. The changed runtime branch allocates and publishes no
|
||||
file, and existing cleanup remains responsible for `.new`, `.install`,
|
||||
`.wwtxn.*`, captures, process groups, and request scratch.
|
||||
|
||||
The WW-native `list_mode_with_no_matches_emits_no_sentinel` observer proves
|
||||
both stages across concurrent combined and test-only products, package
|
||||
initialization, cold and warm persistent work, absence of list/accounting
|
||||
sentinels, positive deterministic selection, running `-o` retention, later
|
||||
direct execution, stage stdout/stderr equality, retained executable byte
|
||||
identity, and `.new` cleanup. The existing routing observer separately keeps
|
||||
ordinary non-list `[no matches]` plus accounting pinned.
|
||||
|
||||
No persisted-byte contract changed: build workdir format remains `18`, test
|
||||
workdir format remains `19`, and semantic storage format remains `3`.
|
||||
|
||||
## 12. Candidate architectures and hard-gate decision
|
||||
|
||||
Five candidates were developed as coherent systems, not as feature bins.
|
||||
|
||||
11
docs/spec.md
11
docs/spec.md
@@ -803,6 +803,17 @@ linker, and other build failures retain their diagnostic standard-error
|
||||
channel; build-action stdout and stderr remain separate. Parallel products own
|
||||
independent captures and are still emitted in canonical product order.
|
||||
|
||||
List mode starts the same directory-owned product and therefore performs its
|
||||
ordinary package initialization, but it does not start any selected test child.
|
||||
The harness emits exactly one newline-terminated qualified name for each test
|
||||
selected by the command's filters, in descriptor order. If the selected set is
|
||||
empty, the harness emits no list payload, no `[no matches]` sentinel, and no
|
||||
accounting, and returns success; the coordinator still emits the ordinary
|
||||
successful package result. This does not alter non-list execution: a valid
|
||||
filter selecting no tests continues to emit `[no matches]` and the
|
||||
discovered/selected/started/completed accounting. A retained binary invoked
|
||||
directly has no coordinator package result.
|
||||
|
||||
After those ordered results, an ordinary explicit `ww test` request that records
|
||||
an attributable setup, build, or execution failure emits exactly one standalone
|
||||
`FAIL\n` on standard output. The line is command-owned: it follows even a later
|
||||
|
||||
@@ -219,6 +219,16 @@ every `-j` level, and `-j 1` — the default — matches the former sequential
|
||||
run loop exactly. Measured on the 31-package `lib/...` walk:
|
||||
7.0s sequential, 2.4s at `-j 4`.
|
||||
|
||||
List mode uses that same product process and initialization boundary but starts
|
||||
no per-test child. The shared language harness emits only selected qualified
|
||||
test names, one per line in descriptor order. A valid filter selecting no tests
|
||||
therefore produces no harness list bytes, no `[no matches]` marker, and no
|
||||
accounting while the process returns success; the coordinator continues with
|
||||
the package's normal `ok` result. Ordinary non-list zero-selection execution
|
||||
retains its `[no matches]` marker and accounting. Concurrent products keep
|
||||
independent empty or nonempty captures, and persistent work never caches a test
|
||||
result.
|
||||
|
||||
Directory test binaries are always linked under the coordinator's temporary
|
||||
product root. `-c` independently requests a caller-visible executable copy and
|
||||
suppresses execution. `-o` independently requests a copy and still runs the
|
||||
|
||||
@@ -113,9 +113,6 @@ export fn run(tests: [](str, *fn() void)) i32 = {
|
||||
};
|
||||
|
||||
if (list) {
|
||||
if (selected == 0 && pkgprefix.len != 0) {
|
||||
tstputs("[no matches]\n");
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
if (selected == 0) {
|
||||
|
||||
@@ -844,7 +844,132 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
runcommand(root, "list-nomatch", lnav,
|
||||
(30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(occurrences(out.stdout, "[no matches]\n") == 1);
|
||||
assert(occurrences(out.stdout, "[no matches]\n") == 0);
|
||||
assert(!has(out.stdout, " discovered, "));
|
||||
clean(root);
|
||||
};
|
||||
|
||||
@test fn list_mode_with_no_matches_emits_no_sentinel() void = {
|
||||
let root: str = fresh();
|
||||
let alpha: str = strings.concat(root, "/alpha");
|
||||
let beta: str = strings.concat(root, "/beta");
|
||||
mkdirall(alpha);
|
||||
mkdirall(beta);
|
||||
writefile(strings.concat(alpha, "/alpha.ww"), strings.concat(
|
||||
"package list_alpha;\nimport os;\n",
|
||||
"fn init() void = { os.write(os.STDOUT_FILENO,",
|
||||
" \"alpha-init\\n\".ptr, 11u64); };\n"));
|
||||
writefile(strings.concat(alpha, "/alpha_internal_test.ww"),
|
||||
strings.concat(
|
||||
"package list_alpha;\n",
|
||||
"@test fn first() void = { abort(\"selected first\"); };\n",
|
||||
"@test fn second() void = { abort(\"selected second\"); };\n"));
|
||||
writefile(strings.concat(alpha, "/alpha_external_test.ww"),
|
||||
strings.concat(
|
||||
"package list_alpha_test;\n",
|
||||
"@test fn external() void = { abort(\"selected external\"); };\n"));
|
||||
writefile(strings.concat(beta, "/beta_test.ww"), strings.concat(
|
||||
"package list_beta_test;\nimport os;\n",
|
||||
"fn init() void = { os.write(os.STDOUT_FILENO,",
|
||||
" \"beta-init\\n\".ptr, 10u64); };\n",
|
||||
"@test fn only() void = { abort(\"selected only\"); };\n"));
|
||||
|
||||
let stages: []str = ["ww", "ww_ww"];
|
||||
let tags: []str = ["c", "ww"];
|
||||
let works: []str = [strings.concat(root, "/work-c"),
|
||||
strings.concat(root, "/work-ww")];
|
||||
let coldout: []str = ["", ""];
|
||||
let colderror: []str = ["", ""];
|
||||
let positiveout: []str = ["", ""];
|
||||
let positiveerror: []str = ["", ""];
|
||||
let retainedout: []str = ["", ""];
|
||||
let retainederror: []str = ["", ""];
|
||||
let manualout: []str = ["", ""];
|
||||
let manualerror: []str = ["", ""];
|
||||
let retained: []str = [strings.concat(root, "/retained-c.test"),
|
||||
strings.concat(root, "/retained-ww.test")];
|
||||
let out: commandout;
|
||||
let i: i32 = 0;
|
||||
for (i < stages.len) {
|
||||
let emptyav: []str = [driver(stages[i]), "test", "-j", "2",
|
||||
"-w", works[i], "-list", "-run", "no-such-*", alpha,
|
||||
beta];
|
||||
runcommand(root, strings.concat("list-empty-cold-", tags[i]),
|
||||
emptyav, (90i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stderr.len == 0);
|
||||
assert(occurrences(out.stdout, "alpha-init\n") == 1);
|
||||
assert(occurrences(out.stdout, "beta-init\n") == 1);
|
||||
assert(occurrences(out.stdout, "ok ") == 2);
|
||||
assert(!has(out.stdout, "[no matches]"));
|
||||
assert(!has(out.stdout, " discovered, "));
|
||||
assert(!has(out.stdout, "list_alpha.first"));
|
||||
assert(!has(out.stdout, "list_alpha.second"));
|
||||
assert(!has(out.stdout, "list_alpha.external"));
|
||||
assert(!has(out.stdout, "list_beta.only"));
|
||||
coldout[i] = strings.dup(out.stdout);
|
||||
colderror[i] = strings.dup(out.stderr);
|
||||
|
||||
runcommand(root, strings.concat("list-empty-warm-", tags[i]),
|
||||
emptyav, (90i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(same(out.stdout, coldout[i]));
|
||||
assert(same(out.stderr, colderror[i]));
|
||||
assert(!directoryhasnew(works[i]));
|
||||
|
||||
let positiveav: []str = [driver(stages[i]), "test", "-j", "2",
|
||||
"-w", works[i], "-list", alpha, beta];
|
||||
runcommand(root, strings.concat("list-positive-", tags[i]),
|
||||
positiveav, (90i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stderr.len == 0);
|
||||
let first: i32 = pos(out.stdout, "list_alpha.first\n");
|
||||
let second: i32 = pos(out.stdout, "list_alpha.second\n");
|
||||
let external: i32 = pos(out.stdout, "list_alpha.external\n");
|
||||
let only: i32 = pos(out.stdout, "list_beta.only\n");
|
||||
assert(first >= 0 && first < second && second < external
|
||||
&& external < only);
|
||||
assert(occurrences(out.stdout, "list_alpha.first\n") == 1);
|
||||
assert(occurrences(out.stdout, "list_alpha.second\n") == 1);
|
||||
assert(occurrences(out.stdout, "list_alpha.external\n") == 1);
|
||||
assert(occurrences(out.stdout, "list_beta.only\n") == 1);
|
||||
positiveout[i] = strings.dup(out.stdout);
|
||||
positiveerror[i] = strings.dup(out.stderr);
|
||||
|
||||
let retainav: []str = [driver(stages[i]), "test", "-w",
|
||||
works[i], "-o", retained[i], "-list", "-run",
|
||||
"no-such-*", alpha];
|
||||
runcommand(root, strings.concat("list-retained-", tags[i]),
|
||||
retainav, (90i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stderr.len == 0);
|
||||
assert(occurrences(out.stdout, "alpha-init\n") == 1);
|
||||
assert(occurrences(out.stdout, "ok ") == 1);
|
||||
assert(!has(out.stdout, "[no matches]"));
|
||||
assert(!has(out.stdout, "list_alpha."));
|
||||
assert(os.exists(retained[i]));
|
||||
retainedout[i] = strings.dup(out.stdout);
|
||||
retainederror[i] = strings.dup(out.stderr);
|
||||
|
||||
let manualav: []str = [retained[i], "-list", "no-such-*"];
|
||||
runcommand(root, strings.concat("list-manual-", tags[i]), manualav,
|
||||
(30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(same(out.stdout, "alpha-init\n"));
|
||||
assert(out.stderr.len == 0);
|
||||
manualout[i] = strings.dup(out.stdout);
|
||||
manualerror[i] = strings.dup(out.stderr);
|
||||
i += 1;
|
||||
};
|
||||
assert(same(coldout[0], coldout[1]));
|
||||
assert(same(colderror[0], colderror[1]));
|
||||
assert(same(positiveout[0], positiveout[1]));
|
||||
assert(same(positiveerror[0], positiveerror[1]));
|
||||
assert(same(retainedout[0], retainedout[1]));
|
||||
assert(same(retainederror[0], retainederror[1]));
|
||||
assert(same(manualout[0], manualout[1]));
|
||||
assert(same(manualerror[0], manualerror[1]));
|
||||
assert(same(readfile(retained[0]), readfile(retained[1])));
|
||||
clean(root);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user