From bcc3aa9336afd6c7cbd5e12ece000c8f4a450899 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 21 Aug 2026 11:59:46 +0900 Subject: [PATCH] ww: report no test files for empty test packages --- docs/build-system.md | 143 +++++++++++++++++++++++++++--- docs/spec.md | 4 +- docs/test-system-v2.md | 14 +-- internal/wwpackage/package.ww | 2 +- test/package/no_tests/no_tests.ww | 2 + test/package/package_test.ww | 80 ++++++++++------- 6 files changed, 195 insertions(+), 50 deletions(-) diff --git a/docs/build-system.md b/docs/build-system.md index 413362db..107cef1e 100644 --- a/docs/build-system.md +++ b/docs/build-system.md @@ -3180,7 +3180,7 @@ still checked; that is a real zero-test package and runs one empty combined harness. A directory with no selected test files instead follows the no-real-run path: validate/compile ordinary production as needed, publish status only, and create no support action, generated main, link, binary, result, or process. The -coordinator alone emits its `[no tests]` report. +coordinator alone emits its `[no test files]` report. Persistent workdirs keep a global driver/compiler/assembler/stamp identity and per-action committed units. When that global identity is stale, the command @@ -5593,12 +5593,14 @@ precise body/export and target-removal/re-addition invalidation, combined-graph allocation failure, repeated late internal/external/main/link rollback, and a complete work-directory sweep for staged residue. -Deliberately unchanged or unsupported behavior includes the raw single-file -compatibility path, WW's `[no tests]` presentation text, Go modules and build -cache, network resolution, manifests, coverage/vet/fuzz/benchmark generation, -source-level build expressions, quoted/grouped/dot imports, and targets other -than the separately specified fixed `linux/amd64` filename selection. None is -used to define canonical package or directory-product identity. +At completion of this earlier topology slice, deliberately unchanged or +unsupported behavior included the raw single-file compatibility path, the +then-current `[no tests]` presentation text, Go modules and build cache, network +resolution, manifests, coverage/vet/fuzz/benchmark generation, source-level +build expressions, quoted/grouped/dot imports, and targets other than the +separately specified fixed `linux/amd64` filename selection. Section 11.35 +subsequently closes only that presentation-text gap. None is used to define +canonical package or directory-product identity. ### 11.23 Implemented case-fold collision preflight @@ -6271,9 +6273,9 @@ selected package; missing parents are created with `0777` subject to umask. A non-directory destination accepts exactly one selected package. Exact `/dev/null` suppresses retained copies, permits duplicate visible names, and does not suppress execution unless `-c` is also present. A no-test package -performs ordinary production validation, reports `[no tests]`, and creates no -binary or otherwise-unused output directory. Successful compile-only products -are silent, matching Go's no-op print action. +performs ordinary production validation, reports `[no test files]`, and creates +no binary or otherwise-unused output directory. Successful test-bearing +compile-only products are silent, matching Go's no-op print action. For `-c`, the driver copies the private runnable bytes to a distinct `.new` inode opened with executable mode `0777` subject to umask. Temporary runnable, @@ -7343,6 +7345,127 @@ Existing directory execution observers cover signals, timeouts, child cleanup, and canonical-order behavior with the new final line, while build-mode controls prove that the other command axis remains silent. +### 11.35 Implemented Go-like no-test-files package result + +A source-bearing directory test product with no selected `*_test.ww` file now +reports exactly `? [no test files]\n` after ordinary production +validation. It continues to create no test-support action, generated main, +link, runnable, retained binary, captured runtime result, or process. The rule +is shared by explicit directories, implicit current-directory selection, +logical/dotted targets, recursive discovery, and compile-only or retained-output +requests. Platform-ineligible test filenames do not prevent the result. A +selected helper-only test file remains a real test product with an empty +harness; the raw single-file compatibility route remains outside the +directory-owned selected-test-file classification. + +#### Pinned Go evidence and fact classification + +The sole authority is official Go 1.26.5 at commit +`c19862e5f8415b4f24b189d065ed739517c548ba`: + +- `builderTest` detects + `len(p.TestGoFiles)+len(p.XTestGoFiles) == 0`, keeps ordinary production as a + dependency, constructs output-preparation and print actions, and creates no + real test binary or run action + ([`cmd/go/internal/test/test.go`, lines 1133–1169](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1133-L1169)). +- `(*runTestActor).Act` owns that print action and writes exactly + `? \t%s\t[no test files]\n` in the ordinary non-coverage case + ([`cmd/go/internal/test/test.go`, lines 1524–1557, especially 1551–1552](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1524-L1557)). +- Official `test_no_tests.txt` invokes `go test testnorun`, requires the + `[no test files]` result, and gives production an initializer that panics if a + test binary is linked and executed + ([`cmd/go/testdata/script/test_no_tests.txt`, lines 1–14](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_no_tests.txt#L1-L14)). + +Those source rules and the script expectation are behavior directly implemented +or asserted by pinned Go. That the ordinary case compiles production, prints the +package result, and does not execute initialization is behavior derived from the +pinned action graph. Applying the bracketed status to WW's established local +package presentation is likewise derived: WW has no module import path, but its +directory product already owns the corresponding selected-test-file decision +and no-process action branch. This does not import Go's coverage behavior, +module loader, cache, manifest, registry, or network resolution. + +#### Fresh four-axis audit and direct pre-fix measurements + +The bounded audit considered all four permanent axes and selected only this +test-result wording gap: + +- **Go-like build:** pinned unresolved-symbol handling and its special missing + `main` case are implemented by `(*ErrorReporter).errorUnresolved` + ([`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)) + and asserted by `TestUndefinedRelocErrors` + ([`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 `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 `package main` without `fn main`, emitted + identical 50-byte linker diagnostics, and created no output. This candidate + was aligned. +- **Go-like test:** a production directory with an aborting initializer and no + test file made both stages exit 0 with empty stderr and byte-identical 55-byte + stdout (SHA-256 + `36adf30792e2900b60ec8cd02ba86e0387acebd41c4d9aab186af2c649ffe67c`): + `? /tmp/ww-go1265-four-axis.Wq8d8H/notest [no tests]\n`. + Bare implicit, logical `-I`, and `-c` forms produced the same bytes; `-c` + created no binary. A platform-excluded test file produced the same old status + class without observing its missing import or test body. These are directly + measured pre-fix WW facts and establish the selected external difference. +- **Go-like package:** `MultiplePackageError` and the package scan reject + conflicting selected declarations + ([`go/build/build.go`, lines 538–548 and 931–967](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L538-L548)); + `TestMultiplePackageImport` asserts the file/name pairs + ([`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` production directory identically + before tools. This candidate was aligned. +- **Go-like import:** `unusedImports` and `errorUnusedPkg` implement unused + ordinary and renamed-import diagnostics + ([`cmd/compile/internal/types2/resolver.go`, lines 706–740](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L706-L740)); + official `importdecl0` asserts both forms + ([`internal/types/testdata/check/importdecl0/importdecl0a.go`, lines 9–27](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/importdecl0/importdecl0a.go#L9-L27)). + After normalizing only PID-bearing scratch roots, both WW stages rejected an + unused renamed dotted import with the same diagnostic and no output. This + candidate was aligned. + +The command observations in that list are directly measured WW behavior. The +linked source and testdata are behavior directly implemented or asserted by +pinned Go. Selecting only the no-test-files presentation while keeping build, +package, and import semantics fixed is behavior derived from the pinned action +boundary and WW's already aligned no-process topology. + +#### Ownership, final behavior, and preserved boundaries + +`internal/wwpackage.pkgemitgroup` is the sole semantic owner of the directory +package result. The loader still sets `g.notests` only after exact filename and +platform eligibility have selected the source set. Product construction still +compiles ordinary production and omits support/main/link/output/status actions; +the scheduler still skips execution. The successful result literal changes +only after that work succeeds. Loading, import, package, graph, compiler, +assembler, archiver, linker, or publication failure therefore retains its prior +diagnostic and precedence and cannot be hidden by a no-test-files result. + +Canonical dotted package/import identity, declared names, file-local import +bindings, graph nodes and edges, action keys, physical-directory metadata, +symbols, `.wwi`, source units, assembly, objects, archives, and executable bytes +are unchanged. There is no new artifact or publication destination. `-c` and +`-o` still omit a binary and do not create an otherwise-unused output hierarchy; +prior caller state and persistent generations are preserved on every producer +failure. Warm reuse and invalidation still concern production actions only. + +Concurrent products retain canonical ordered emission because the text remains +inside the existing group emitter. Interruption before emission makes no new +promise; the branch starts no child that can be signalled or timed out. Cleanup +still removes only request-private plan state, creates no `.new` or `.install` +stage, and leaves no test-process residue. Cstage and WWstage use the same +coordinator owner and therefore emit byte-identical diagnostics and results. + +The WW-native `empty_and_invalid_package_classes` observer now requires the +exact explicit and implicit result in both stages and uses an aborting production +initializer to prove no process starts. Existing package observers cover +logical and recursive selection, platform filtering, helper-only selected test +files, compile-only/output omission, persistent cold/warm/invalidation behavior, +large scheduling sets, failure rollback, stage parity, and artifact-byte +identity. 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. diff --git a/docs/spec.md b/docs/spec.md index 1a882c85..deeb6550 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -710,7 +710,9 @@ runtime lets, and init declarations found only in `*_test.ww` never enter an ordinary production build. Test-only same-package, external-package, and valid mixed directories are accepted from their selected test files. A directory with no selected test file validates ordinary production but creates no test -support, generated main, link, binary, result, or process. +support, generated main, link, binary, captured runtime result, or process. The +coordinator reports that successful validation exactly as +`? [no test files]\n`. Every test-bearing directory product links one request-private runnable. `-c` retains an executable copy and suppresses its execution; `-o` retains a diff --git a/docs/test-system-v2.md b/docs/test-system-v2.md index 0748a476..c3a97798 100644 --- a/docs/test-system-v2.md +++ b/docs/test-system-v2.md @@ -207,9 +207,10 @@ not followed. A wildcard cannot consume a non-terminal exact `vendor` element, while an explicitly vendor-rooted pattern remains legal. Every selected test-bearing directory becomes exactly one package run, binary, and result even when both internal and external actions exist. Selected source-bearing -directories without selected test files report the usual `?` line after -ordinary production validation, with no support, generated main, link, binary, -result, or process. The coordinator first +directories without selected test files report exactly +`? [no test files]\n` after ordinary production validation, with +no support, generated main, link, binary, captured runtime result, or process. +The coordinator first launches one driver plan for the complete command-global package/action union. After that shared build completes, `-j N` schedules up to N successful selected test binaries concurrently under `os.exec` start/poll supervision (no threads); @@ -230,9 +231,10 @@ names reject before tools; exact `/dev/null` discards every copy and permits 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. +package with no selected test source validates production, reports the same +`[no test files]` result, publishes nothing, and does not create an otherwise +unneeded output directory. Successful `-c` output is silent apart from that +no-test-files report. The package suite also pins the adjacent `ww build` output-directory branch. After normal loading, an existing directory (including a symlink to one) or an diff --git a/internal/wwpackage/package.ww b/internal/wwpackage/package.ww index 4699bb54..235a2bd8 100644 --- a/internal/wwpackage/package.ww +++ b/internal/wwpackage/package.ww @@ -1720,7 +1720,7 @@ fn pkgemitgroup(g: *pkggroup, compileonly: bool, if (g.notests) { pkgput(os.STDOUT_FILENO, "? "); pkgput(os.STDOUT_FILENO, g.dir); - pkgputln(os.STDOUT_FILENO, " [no tests]"); + pkgputln(os.STDOUT_FILENO, " [no test files]"); return true; }; if (compileonly) { diff --git a/test/package/no_tests/no_tests.ww b/test/package/no_tests/no_tests.ww index 268e8c97..bc988cde 100644 --- a/test/package/no_tests/no_tests.ww +++ b/test/package/no_tests/no_tests.ww @@ -1,3 +1,5 @@ package no_tests; +fn init() void = { abort(); }; + export fn value() int = { return 1; }; diff --git a/test/package/package_test.ww b/test/package/package_test.ww index 03060e12..31d3e112 100644 --- a/test/package/package_test.ww +++ b/test/package/package_test.ww @@ -1407,8 +1407,8 @@ fn cwdwritedata(dir: str, label: str) void = { expectexit(&outw, 0); assert(same(outc.stdout, outw.stdout)); assert(same(outc.stderr, outw.stderr) && outc.stderr.len == 0); - assert(has(outc.stdout, strings.concat("? ", plain, " [no tests]\n"))); - assert(has(outc.stdout, strings.concat("? ", cmdno, " [no tests]\n"))); + assert(has(outc.stdout, strings.concat("? ", plain, " [no test files]\n"))); + assert(has(outc.stdout, strings.concat("? ", cmdno, " [no test files]\n"))); assert(!has(outc.stdout, "plain-init")); assert(!has(outc.stdout, "cmdno-init")); assert(!has(outc.stdout, "cmdno-main")); @@ -1968,13 +1968,29 @@ fn cwdwritedata(dir: str, label: str) void = { @test fn empty_and_invalid_package_classes() void = { let root: str = fresh(); - let av: []str = [driver("ww"), "test", packagepath("no_tests")]; + let stages: []str = ["ww", "ww_ww"]; let out: commandout; - runcommand(root, "notests", av, - (30i64 * (time.second: i64)): time.duration, &out); - expectexit(&out, 0); - assert(has(out.stdout, " [no tests]\n")); - assert(!has(out.stdout, " ... ok")); + let nofiles: str = strings.concat("? ", packagepath("no_tests"), + " [no test files]\n"); + let si: i32 = 0; + for (si < stages.len) { + let av: []str = [driver(stages[si]), "test", packagepath("no_tests")]; + runcommand(root, strings.concat("notests-explicit-", stages[si]), av, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(same(out.stdout, nofiles)); + assert(out.stderr.len == 0); + assert(!has(out.stdout, " ... ok")); + + let implicit: []str = [driver(stages[si]), "test"]; + runcommanddir(root, strings.concat("notests-implicit-", stages[si]), + packagepath("no_tests"), implicit, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(same(out.stdout, nofiles)); + assert(out.stderr.len == 0); + si += 1; + }; let bad: []str = [driver("ww"), "test", packagepath("bad_external")]; runcommand(root, "badexternal", bad, @@ -2000,7 +2016,7 @@ fn cwdwritedata(dir: str, label: str) void = { (30i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 1); assert(has(out.stderr, "cannot find package nowhere")); - assert(!has(out.stdout, "[no tests]")); + assert(!has(out.stdout, "[no test files]")); // A test file is part of its variant even when it only supplies helpers. // Its imports and package clause must therefore reach the shared loader; @@ -2011,8 +2027,7 @@ fn cwdwritedata(dir: str, label: str) void = { "package helperbad;\nexport fn value() i32 = { return 1; };\n"); writefile(strings.concat(helperbad, "/helper_test.ww"), "package helperbad_test;\nimport nowhere;\nfn helper() void = { };\n"); - let stages: []str = ["ww", "ww_ww"]; - let si: i32 = 0; + si = 0; for (si < stages.len) { let helperav: []str = [driver(stages[si]), "test", helperbad]; runcommand(root, strings.concat("helper-only-", stages[si]), helperav, @@ -2078,7 +2093,7 @@ fn cwdwritedata(dir: str, label: str) void = { (30i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); assert(has(out.stdout, "multiline.actually_runs ... ok\n")); - assert(!has(out.stdout, "[no tests]")); + assert(!has(out.stdout, "[no test files]")); let commented: str = strings.concat(root, "/commented-test"); assert(os.mkdir(commented, 448i32) == 0); @@ -2093,7 +2108,7 @@ fn cwdwritedata(dir: str, label: str) void = { (30i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); assert(has(out.stdout, "ok ")); - assert(!has(out.stdout, " [no tests]\n")); + assert(!has(out.stdout, " [no test files]\n")); assert(!has(out.stdout, "phantom")); // Go's complete empty-package matrix: production-only, internal-only, @@ -2163,9 +2178,9 @@ fn cwdwritedata(dir: str, label: str) void = { }; shi += 1; }; - assert(has(shaperefs[0], " [no tests]\n")); + assert(has(shaperefs[0], " [no test files]\n")); assert(occurrences(shaperefs[0], strings.concat( - "? ", prodonly, " [no tests]\n")) == 1); + "? ", prodonly, " [no test files]\n")) == 1); assert(has(shaperefs[1], "shapeinternal.internal_runs ... ok\n")); assert(has(shaperefs[2], "shapeexternal.external_runs ... ok\n")); assert(occurrences(shaperefs[3], " ... ok\n") == 2); @@ -3979,7 +3994,7 @@ fn cwdwritedata(dir: str, label: str) void = { expectexit(&out, 0); assert(occurrences(out.stdout, "built ") == 0); assert(has(out.stdout, strings.concat( - "? ", gamma, " [no tests]\n"))); + "? ", gamma, " [no test files]\n"))); assert(os.exists(publicalpha) && os.exists(publicbeta)); assert(!os.exists(publicgamma)); assert(!os.exists(strings.concat(alpha, "/alpha_test.test"))); @@ -5039,7 +5054,7 @@ fn cwdwritedata(dir: str, label: str) void = { assert(has(out.stdout, strings.concat("ok ", suite, "/p051 [p051]\n"))); assert(has(out.stdout, strings.concat("? ", suite, - "/p256 [no tests]\n"))); + "/p256 [no test files]\n"))); let beforecompiler: i32 = readfile(compilertraces[si]).len; runcommandenv(root, strings.concat("dynamic-coordinator-warm-", @@ -8228,7 +8243,7 @@ fn cwdwritedata(dir: str, label: str) void = { assert(has(out.stdout, "selected_internal_pair ... ok\n")); assert(has(out.stdout, "selected_external_pair ... ok\n")); assert(has(out.stdout, strings.concat("? ", notests, - " [no tests]\n"))); + " [no test files]\n"))); assert(!has(out.stdout, "wrong_target_test")); assert(!has(out.stderr, "only_windows")); assert(!has(readfile(compilertrace), "dep.wrong")); @@ -9307,11 +9322,11 @@ fn cwdwritedata(dir: str, label: str) void = { assert(has(seq, strings.concat("ok ", tree, "/domain/app [app]\n"))); assert(has(seq, strings.concat("? ", tree, - "/gamma [no tests]\n"))); + "/gamma [no test files]\n"))); assert(has(seq, strings.concat("? ", tree, - "/vendor [no tests]\n"))); + "/vendor [no test files]\n"))); assert(has(seq, strings.concat("? ", tree, - "/vendorx [no tests]\n"))); + "/vendorx [no test files]\n"))); let rootreport: i32 = pos(seq, strings.concat("ok ", tree, " [rootpkg]\n")); let alphareport: i32 = pos(seq, strings.concat("ok ", tree, @@ -9323,11 +9338,11 @@ fn cwdwritedata(dir: str, label: str) void = { let domainreport: i32 = pos(seq, strings.concat("ok ", tree, "/domain/app [app]\n")); let gammareport: i32 = pos(seq, strings.concat("? ", tree, - "/gamma [no tests]\n")); + "/gamma [no test files]\n")); let vendorreport: i32 = pos(seq, strings.concat("? ", tree, - "/vendor [no tests]\n")); + "/vendor [no test files]\n")); let vendorxreport: i32 = pos(seq, strings.concat("? ", tree, - "/vendorx [no tests]\n")); + "/vendorx [no test files]\n")); assert(rootreport >= 0 && rootreport < alphareport && alphareport < appreport && appreport < innerreport && innerreport < domainreport && domainreport < gammareport @@ -9342,11 +9357,11 @@ fn cwdwritedata(dir: str, label: str) void = { assert(occurrences(seq, strings.concat("ok ", tree, "/domain/app [app]\n")) == 1); assert(occurrences(seq, strings.concat("? ", tree, - "/gamma [no tests]\n")) == 1); + "/gamma [no test files]\n")) == 1); assert(occurrences(seq, strings.concat("? ", tree, - "/vendor [no tests]\n")) == 1); + "/vendor [no test files]\n")) == 1); assert(occurrences(seq, strings.concat("? ", tree, - "/vendorx [no tests]\n")) == 1); + "/vendorx [no test files]\n")) == 1); assert(!has(seq, ".hidden")); assert(!has(seq, "_skip")); assert(!has(seq, "/testdata")); @@ -9488,7 +9503,7 @@ fn cwdwritedata(dir: str, label: str) void = { assert(same(outc.stdout, outw.stdout)); assert(same(outc.stderr, outw.stderr)); assert(has(outc.stdout, strings.concat("? ", tree, - "/vendor [no tests]\n"))); + "/vendor [no test files]\n"))); assert(has(outc.stdout, strings.concat("ok ", tree, "/vendor/bad [bad]\n"))); assert(!has(outc.stdout, "/vendor/bad/vendor/deep")); @@ -9506,14 +9521,14 @@ fn cwdwritedata(dir: str, label: str) void = { assert(same(outc.stdout, outw.stdout)); assert(same(outc.stderr, outw.stderr)); assert(has(outc.stdout, strings.concat("? ", tree, - "/app/vendor/lib/math [no tests]\n"))); + "/app/vendor/lib/math [no test files]\n"))); let vendorroots: []str = [driver("ww"), "test", "-I", tree, strings.concat(tree, "/app/vendor/...")]; runcommand(root, "tree-pattern-below-vendor", vendorroots, (30i64 * (time.second: i64)): time.duration, &outc); expectexit(&outc, 0); assert(has(outc.stdout, strings.concat("? ", tree, - "/app/vendor/lib/math [no tests]\n"))); + "/app/vendor/lib/math [no test files]\n"))); // A child symlink is never traversed. The same symlink is followed when it // is itself the explicit traversal root, then canonical output names it once. @@ -12690,7 +12705,8 @@ fn runtimepath(relative: str) str = { let fanpkg: str = strings.concat(fanarg, "pkg.test"); assert(os.exists(fanabin) && os.exists(fanpkg)); assert(!os.exists(strings.concat(fanarg, "znone.test"))); - assert(has(out.stdout, strings.concat("? ", none, " [no tests]\n"))); + assert(has(out.stdout, strings.concat("? ", none, + " [no test files]\n"))); if (i == 0) { fanabytes = readfile(fanabin); fanpkgbytes = readfile(fanpkg); @@ -12945,7 +12961,7 @@ fn runtimepath(relative: str) str = { (60i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); assert(has(out.stdout, strings.concat("? ", none, - " [no tests]\n"))); + " [no test files]\n"))); assert(out.stderr.len == 0 && !os.exists(noneroot)); // Pinned builderTest makes the install action depend on the run action.