ww: put test result trailers on a new line
This commit is contained in:
@@ -7047,6 +7047,149 @@ transaction, stage, workdir, and generated-file cleanup. Existing signal,
|
||||
timeout, interruption, concurrent-driver, and public-output transaction owners
|
||||
cover the unchanged mechanisms at those boundaries.
|
||||
|
||||
### 11.33 Implemented newline before directory test result trailers
|
||||
|
||||
When a directory-owned test product has emitted a nonempty combined capture
|
||||
whose final byte is not newline, `ww test` now emits exactly one newline before
|
||||
its existing `ok` or run-status `FAIL` trailer. Empty and already
|
||||
newline-terminated captures gain no byte. The rule belongs only to the
|
||||
coordinator boundary between completed test-process output and its result
|
||||
trailer; it does not rewrite the capture or affect a route with no coordinator
|
||||
trailer.
|
||||
|
||||
#### Pinned Go evidence and classification
|
||||
|
||||
The sole authority is official Go 1.26.5 at commit
|
||||
`c19862e5f8415b4f24b189d065ed739517c548ba`:
|
||||
|
||||
- `(*runTestActor).Act` selects one output writer/buffer for a package test
|
||||
([`cmd/go/internal/test/test.go`, lines 1436–1499](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1436-L1499)),
|
||||
then assigns that same writer to the test command's stdout and stderr and
|
||||
retains the resulting bytes as `out`
|
||||
([lines 1661–1708](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1661-L1708)).
|
||||
- On success, a nonempty `out` without a trailing newline receives one before
|
||||
the `ok` record
|
||||
([lines 1712–1732](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1712-L1732)).
|
||||
On failure with partial output, the same check inserts one before `FAIL`
|
||||
([lines 1733–1769](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1733-L1769)).
|
||||
- Official `test_fail_newline.txt` asserts that buffered partial failure output
|
||||
and `FAIL` begin on different lines, and that buffered verbose partial
|
||||
success output and `ok` begin on different lines. It also records the
|
||||
deliberate streaming-mode exception
|
||||
([lines 3–35](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_fail_newline.txt#L3-L35)).
|
||||
|
||||
Those source rules and official script assertions are behavior directly
|
||||
implemented or asserted by pinned Go. Applying the buffered-package boundary
|
||||
to WW's directory-product capture is derived from that implementation: WW's
|
||||
manifest-free directory coordinator likewise owns the completed combined bytes
|
||||
and immediately appends a package result trailer. Excluding raw single-file
|
||||
tests and later manual execution of retained binaries is also derived from the
|
||||
pinned distinction: those WW routes have no coordinator-owned `ok` or `FAIL`
|
||||
trailer to separate.
|
||||
|
||||
#### Fresh four-axis audit and direct pre-fix measurements
|
||||
|
||||
The bounded audit considered all four permanent axes and selected only this
|
||||
test-output gap:
|
||||
|
||||
- **Go-like build:** pinned `(*ErrorReporter).errorUnresolved` gives an
|
||||
undeclared main function its dedicated link error
|
||||
([`cmd/link/internal/ld/errors.go`, lines 29–65](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/errors.go#L29-L65)).
|
||||
`TestUndefinedRelocErrors` directly requires build failure and that message
|
||||
([`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)),
|
||||
using official `issue10978/main.go`, whose main function is absent
|
||||
([lines 5–27](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/testdata/issue10978/main.go#L5-L27)).
|
||||
Direct Cstage and WWstage `ww build -o /dev/null` probes of a selected WW
|
||||
command package without `main` both exited `1`, produced the identical
|
||||
`w6l: undefined reference to 'main'` then `ww: w6l failed` diagnostics, and
|
||||
published nothing. This candidate was aligned.
|
||||
- **Go-like test:** a selected external `*_test.ww` initializer wrote exactly
|
||||
`partial-success` without newline and exited `0`. Both stages exited `0`,
|
||||
wrote no stderr, and emitted 72 stdout bytes beginning
|
||||
`partial-successok `. The corresponding initializer wrote exactly
|
||||
`partial-failure` to stderr and exited `7`; both commands exited `1`, wrote
|
||||
no coordinator stderr, and emitted 86 stdout bytes beginning
|
||||
`partial-failureFAIL `. Full Cstage and WWstage captures were byte-identical.
|
||||
These are directly measured pre-fix WW facts and establish the selected
|
||||
externally observable difference.
|
||||
- **Go-like package:** `MultiplePackageError` represents conflicting selected
|
||||
package clauses and formats the two declarations
|
||||
([`go/build/build.go`, lines 538–548](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L538-L548));
|
||||
the scanner creates it when selected files disagree
|
||||
([lines 930–967](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L930-L967)),
|
||||
and `TestMultiplePackageImport` asserts the typed result and files
|
||||
([`go/build/build_test.go`, lines 105–124](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build_test.go#L105-L124)).
|
||||
Direct WW directories declaring `alpha` and `beta` were rejected before any
|
||||
producer by both stages with the same positioned conflict diagnostic. This
|
||||
candidate was aligned.
|
||||
- **Go-like import:** `loadImport` rejects a package declared `main` when it is
|
||||
imported from another directory
|
||||
([`cmd/go/internal/load/pkg.go`, lines 787–805](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L787-L805));
|
||||
official `import_main.txt` asserts the rule for builds and internal/external
|
||||
tests
|
||||
([lines 3–35](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/import_main.txt#L3-L35)).
|
||||
Both WW stages rejected a direct dotted import of a package declared `main`
|
||||
with the identical `ww: package cmdpkg is a program, not an importable
|
||||
package` diagnostic. This candidate was aligned.
|
||||
|
||||
The direct Go source and official-test statements above are behavior directly
|
||||
implemented or asserted by pinned Go. The WW command results are directly
|
||||
measured behavior. The conclusion that the selected change is a runtime
|
||||
presentation boundary, with no build, package, or import identity effect, is
|
||||
derived from the pinned placement after command completion and before the
|
||||
result record.
|
||||
|
||||
#### Ownership, final behavior, and preserved boundaries
|
||||
|
||||
`internal/wwpackage.pkgemitgroup` is the true owner because it alone has both
|
||||
the completed product-local combined capture and knowledge that an existing
|
||||
directory result trailer follows. It first emits the capture unchanged, then
|
||||
emits one separator only when the capture is nonempty and its last byte is not
|
||||
newline, then follows the established success or failure branch. The check is
|
||||
shared by Cstage and WWstage and is independent for every canonically ordered
|
||||
product, including internal, external, and combined variants; dependency
|
||||
initialization; filters and list mode; concurrent products; and the private run
|
||||
of a retained request.
|
||||
|
||||
Empty output does not acquire a leading blank line, and output already ending
|
||||
in newline does not acquire a second one. A nonzero exit or signal retains its
|
||||
existing process classification and `FAIL` text; only a preceding partial line
|
||||
is terminated. Test-harness timeout and ordinary assertion output already end
|
||||
in newline and therefore remain byte-identical. A child that cannot start has
|
||||
no completed capture/trailer boundary in this function. Parent interruption,
|
||||
producer failure, and load, compile, assemble, archive, link, or install
|
||||
failure retain their existing diagnostics and precedence. Raw single-file
|
||||
tests and later direct execution of retained binaries have no package
|
||||
coordinator result trailer and retain their exact process bytes.
|
||||
|
||||
Loading, graph construction, action construction and scheduling, compiler,
|
||||
assembler, archiver, linker, generated main, test executable, and retained
|
||||
artifact bytes are unchanged. The separator is emitted after the private
|
||||
process completes; it is not written into the capture, executable, interface,
|
||||
archive, work record, or public destination. Running retention remains private
|
||||
build, private run, then guarded install. Failed, signalled, interrupted, or
|
||||
unstartable runs still publish nothing and preserve prior retained bytes.
|
||||
Concurrent products retain separate capture files and canonical emission;
|
||||
there is no shared mutable newline state. Existing process-group cleanup,
|
||||
transaction rollback, temporary-root removal, and staged-file cleanup are
|
||||
unchanged.
|
||||
|
||||
Physical source directories remain runtime/loader metadata only and do not
|
||||
become package, import, graph, action, artifact, symbol, `.wwi`, publication,
|
||||
or persistence identity. No persisted byte or key changes, so build workdir
|
||||
format remains `18`, test workdir format remains `19`, and semantic storage
|
||||
format remains `3`.
|
||||
|
||||
The WW-native `directory_test_trailer_starts_on_new_line` observer proves both
|
||||
stages for unterminated stdout success, unterminated stderr failure,
|
||||
unterminated signal output, already terminated output, and empty output. It
|
||||
also covers concurrent products, a filter, list mode, running retained
|
||||
publication, retained executable byte identity, and the unchanged raw/manual
|
||||
routes; complete stdout and stderr from the concurrent Cstage and WWstage runs
|
||||
must match exactly. Existing package execution, timeout, interruption,
|
||||
transaction, persistence, byte-identity, and cleanup owners continue to prove
|
||||
the mechanisms this slice does not alter.
|
||||
|
||||
## 12. Candidate architectures and hard-gate decision
|
||||
|
||||
Five candidates were developed as coherent systems, not as feature bins.
|
||||
|
||||
16
docs/spec.md
16
docs/spec.md
@@ -779,12 +779,16 @@ artifact, or persistence identity.
|
||||
The product's standard output and standard error refer to one product-local
|
||||
capture. Bytes from either descriptor retain the order in which their writes
|
||||
reach that shared open output, and the coordinator emits the completed capture
|
||||
on its standard output. A runtime failure or child-setup failure appends the
|
||||
product status diagnostic to standard output as well. Loader, source,
|
||||
compiler, assembler, 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.
|
||||
on its standard output. Immediately before an existing directory-product
|
||||
`ok` or run-status `FAIL` trailer, a nonempty capture whose final byte is not
|
||||
newline receives exactly one newline; an empty or already newline-terminated
|
||||
capture receives none. The capture itself is unchanged, and raw single-file or
|
||||
later direct retained-binary execution has no coordinator trailer and no such
|
||||
separator. A runtime failure or child-setup failure appends the product status
|
||||
diagnostic to standard output as well. Loader, source, compiler, assembler,
|
||||
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.
|
||||
|
||||
The coordinator does not change its own cwd or environment. Parallel products
|
||||
receive independent child environments and each uses its own source directory.
|
||||
|
||||
@@ -313,9 +313,14 @@ The same product maps fd 1 and fd 2 to one open product-local capture. This
|
||||
matches Go's test-command use of one writer for `exec.Cmd.Stdout` and
|
||||
`exec.Cmd.Stderr`: writes are not drained into two files and regrouped later.
|
||||
The coordinator emits the completed combined bytes and any product run-status
|
||||
line on stdout. Each parallel product has a distinct capture, while canonical
|
||||
group-order emission remains byte-stable across `-j` values. Build-plan stdout
|
||||
and stderr stay distinct, and loader/build diagnostics stay on stderr.
|
||||
line on stdout. Before its existing directory-product `ok` or run-status
|
||||
`FAIL` trailer, it emits exactly one newline when that capture is nonempty and
|
||||
does not already end in newline. Empty and newline-terminated captures gain no
|
||||
byte. Raw single-file and later direct retained-binary execution have no
|
||||
coordinator trailer, so their bytes remain untouched. Each parallel product
|
||||
has a distinct capture, while canonical group-order emission remains
|
||||
byte-stable across `-j` values. Build-plan stdout and stderr stay distinct, and
|
||||
loader/build diagnostics stay on stderr.
|
||||
|
||||
The physical directory remains distinct from exact dotted package identity and
|
||||
from production, internal, external, recompiled, support, and generated-main
|
||||
@@ -436,6 +441,14 @@ workdir for staged residue.
|
||||
Command-global bounded-memory failure remains independently
|
||||
owned by `allocation_failure_is_command_global`.
|
||||
|
||||
The focused `directory_test_trailer_starts_on_new_line` observer generates
|
||||
empty, already terminated, and unterminated combined captures. Across both
|
||||
stages it covers stdout success, stderr failure, signal termination,
|
||||
concurrent products, filtering, listing, running retention, retained artifact
|
||||
bytes, and the raw/manual routes that have no coordinator trailer. It requires
|
||||
complete concurrent stdout/stderr parity and forbids both an adjacent trailer
|
||||
and a doubled separator.
|
||||
|
||||
The same package owner contains the focused
|
||||
`platform_filename_source_selection` observer. It independently generates the
|
||||
suffix matrix, production/internal/external sources, wrong-target import and
|
||||
|
||||
@@ -1724,6 +1724,9 @@ fn pkgemitgroup(g: *pkggroup, compileonly: bool) bool = {
|
||||
return false;
|
||||
};
|
||||
pkgput(os.STDOUT_FILENO, runoutput);
|
||||
if (runoutput.len != 0 && runoutput[runoutput.len - 1] != '\n') {
|
||||
pkgput(os.STDOUT_FILENO, "\n");
|
||||
};
|
||||
if (!pkgrunok(g)) {
|
||||
pkgreportcommand(os.STDOUT_FILENO, "test", g, &g.runres);
|
||||
return false;
|
||||
|
||||
@@ -1683,6 +1683,115 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
clean(root);
|
||||
};
|
||||
|
||||
@test fn directory_test_trailer_starts_on_new_line() void = {
|
||||
let root: str = fresh();
|
||||
let pass: str = strings.concat(root, "/pass");
|
||||
let fail: str = strings.concat(root, "/fail");
|
||||
let complete: str = strings.concat(root, "/complete");
|
||||
let empty: str = strings.concat(root, "/empty");
|
||||
let signal: str = strings.concat(root, "/signal");
|
||||
mkdirall(pass);
|
||||
mkdirall(fail);
|
||||
mkdirall(complete);
|
||||
mkdirall(empty);
|
||||
mkdirall(signal);
|
||||
writefile(strings.concat(pass, "/partial_test.ww"), strings.concat(
|
||||
"package trailer_pass_test;\nimport os;\n",
|
||||
"fn init() void = { os.write(os.STDOUT_FILENO,",
|
||||
" \"partial-success\".ptr, 15u64); os.exit(0); };\n",
|
||||
"@test fn unreachable() void = { void; };\n"));
|
||||
writefile(strings.concat(fail, "/partial_test.ww"), strings.concat(
|
||||
"package trailer_fail_test;\nimport os;\n",
|
||||
"fn init() void = { os.write(os.STDERR_FILENO,",
|
||||
" \"partial-failure\".ptr, 15u64); os.exit(7); };\n",
|
||||
"@test fn unreachable() void = { void; };\n"));
|
||||
writefile(strings.concat(complete, "/complete_test.ww"), strings.concat(
|
||||
"package trailer_complete_test;\nimport os;\n",
|
||||
"fn init() void = { os.write(os.STDOUT_FILENO,",
|
||||
" \"complete\\n\".ptr, 9u64); os.exit(0); };\n",
|
||||
"@test fn unreachable() void = { void; };\n"));
|
||||
writefile(strings.concat(empty, "/empty_test.ww"), strings.concat(
|
||||
"package trailer_empty_test;\nimport os;\n",
|
||||
"fn init() void = { os.exit(0); };\n",
|
||||
"@test fn unreachable() void = { void; };\n"));
|
||||
writefile(strings.concat(signal, "/partial_test.ww"), strings.concat(
|
||||
"package trailer_signal_test;\nimport os;\n",
|
||||
"fn init() void = { os.write(os.STDOUT_FILENO,",
|
||||
" \"partial-signal\".ptr, 14u64);",
|
||||
" os.kill(os.getpid(), os.SIGTERM); };\n",
|
||||
"@test fn unreachable() void = { void; };\n"));
|
||||
|
||||
let stages: []str = ["ww", "ww_ww"];
|
||||
let tags: []str = ["c", "ww"];
|
||||
let multiout: []str = ["", ""];
|
||||
let multierr: []str = ["", ""];
|
||||
let retained: []str = ["", ""];
|
||||
let out: commandout;
|
||||
let i: i32 = 0;
|
||||
for (i < stages.len) {
|
||||
let mav: []str = [driver(stages[i]), "test", "-j", "5", "-run",
|
||||
"unreachable", pass, fail, complete, empty, signal];
|
||||
runcommand(root, strings.concat("trailer-multi-", tags[i]), mav,
|
||||
(90i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stderr.len == 0);
|
||||
assert(has(out.stdout, "partial-success\nok "));
|
||||
assert(!has(out.stdout, "partial-successok "));
|
||||
assert(has(out.stdout, "partial-failure\nFAIL "));
|
||||
assert(!has(out.stdout, "partial-failureFAIL "));
|
||||
assert(has(out.stdout, "partial-signal\nFAIL "));
|
||||
assert(!has(out.stdout, "partial-signalFAIL "));
|
||||
assert(has(out.stdout, "complete\nok "));
|
||||
assert(!has(out.stdout, "complete\n\nok "));
|
||||
multiout[i] = strings.dup(out.stdout);
|
||||
multierr[i] = strings.dup(out.stderr);
|
||||
|
||||
let eav: []str = [driver(stages[i]), "test", empty];
|
||||
runcommand(root, strings.concat("trailer-empty-", tags[i]), eav,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stderr.len == 0);
|
||||
assert(pos(out.stdout, "ok ") == 0);
|
||||
|
||||
let lav: []str = [driver(stages[i]), "test", "-list", pass];
|
||||
runcommand(root, strings.concat("trailer-list-", tags[i]), lav,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stderr.len == 0);
|
||||
assert(has(out.stdout, "partial-success\nok "));
|
||||
|
||||
retained[i] = strings.concat(root, "/retained-", tags[i]);
|
||||
let rav: []str = [driver(stages[i]), "test", "-o", retained[i],
|
||||
pass];
|
||||
runcommand(root, strings.concat("trailer-retained-", tags[i]), rav,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stderr.len == 0);
|
||||
assert(has(out.stdout, "partial-success\nok "));
|
||||
assert(os.exists(retained[i]));
|
||||
|
||||
let runav: []str = [retained[i]];
|
||||
runcommand(root, strings.concat("trailer-manual-", tags[i]), runav,
|
||||
(30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(same(out.stdout, "partial-success"));
|
||||
assert(out.stderr.len == 0);
|
||||
|
||||
let rawav: []str = [driver(stages[i]), "test",
|
||||
strings.concat(pass, "/partial_test.ww")];
|
||||
runcommand(root, strings.concat("trailer-raw-", tags[i]), rawav,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(same(out.stdout, "partial-success"));
|
||||
assert(out.stderr.len == 0);
|
||||
i += 1;
|
||||
};
|
||||
assert(same(multiout[0], multiout[1]));
|
||||
assert(same(multierr[0], multierr[1]));
|
||||
assert(same(readfile(retained[0]), readfile(retained[1])));
|
||||
clean(root);
|
||||
};
|
||||
|
||||
@test fn imported_dependency_tests_do_not_leak() void = {
|
||||
let root: str = fresh();
|
||||
let base: str = packagepath("dependency");
|
||||
|
||||
Reference in New Issue
Block a user