diff --git a/Makefile b/Makefile index a320cac6..66b3b131 100644 --- a/Makefile +++ b/Makefile @@ -482,35 +482,12 @@ wwtest/test/tool/ffivariadic_test.ww: $(OUT)/ffivariadic/libffifix.a # Native library tests execute their source owner directly. The retired C # launchers added no assertion beyond this exit status. -LIBRARY_TESTS = lib/errors/errno_test.ww lib/ascii/ascii_test.ww \ - lib/ww/syntax/tok_test.ww lib/ww/syntax/ast_test.ww \ - lib/ww/syntax/sym_test.ww \ - lib/strconv/test/ftos_test.ww lib/strconv/test/stof_test.ww \ - lib/strconv/test/int_test.ww lib/strings/strings_test.ww \ - lib/strings/suffix_test.ww lib/strings/contains_test.ww \ - lib/strings/index_test.ww lib/strings/compare_test.ww \ - lib/bytes/equal_test.ww lib/bytes/index_test.ww \ - lib/bytes/contains_test.ww lib/bytes/tokenize_test.ww \ - lib/bytes/trim_test.ww lib/encoding/utf8/rune_test.ww \ - lib/encoding/utf8/encode_test.ww lib/encoding/utf8/decode_test.ww \ - lib/encoding/utf8/types_test.ww \ - lib/bufio/stream_test.ww lib/bufio/scanner_test.ww \ - lib/math/random/random_test.ww \ - lib/math/checked/checked_test.ww lib/fmt/fmt_test.ww \ - lib/log/funcs_test.ww lib/log/global_test.ww \ - lib/log/silent_test.ww lib/fnmatch/fnmatch_test.ww \ - lib/shlex/shlex_test.ww lib/time/arithm_test.ww \ - lib/time/duration_test.ww lib/time/instant_test.ww \ - lib/encoding/hex/hex_test.ww lib/memio/memio_test.ww \ - lib/temp/temp_test.ww lib/getopt/getopt_test.ww \ - lib/encoding/base32/base32_test.ww \ - lib/encoding/base64/base64_test.ww \ - lib/hash/adler32/adler32_test.ww lib/hash/crc16/crc16_test.ww \ - lib/hash/crc32/crc32_test.ww lib/hash/crc64/crc64_test.ww \ - lib/path/buffer_test.ww lib/path/posix_test.ww \ - lib/path/stack_test.ww lib/crypto/sha256/sha256_test.ww \ - lib/hash/siphash/siphash_test.ww \ - lib/os/os_test.ww lib/os/stat_test.ww lib/dirs/dirs_test.ww +# Library behavior has ONE owner: the package coordinator's lib/... +# tree walk in test-library (Go's `go test ./...` — the PACKAGE, not +# the file, is the unit of testing; same-package *_test.ww compose +# into one unit). No per-file registration exists: a new lib +# *_test.ww is discovered by the walk, and the libbyteid completeness +# scan still fails loudly on any unenrolled lib/ dir. # These real sources have a standalone-compilation contract that is not # subsumed by a library @test import owner. @@ -541,13 +518,12 @@ test-compiler: $(WWFIXTURE_BIN) $(WRAPPER_TOOLS) $(COMPILER_WRAPPER_BINS) \ echo "compiler artifact $$t"; BIN=$(CURDIR)/$(BIN) $(CURDIR)/$$t; \ done -# One phony target per language/library test file so `make -jN` -# parallelizes the suites, each in its own persistent -w workdir so only -# changed packages rebuild. Tests always execute — the workdir caches -# builds, never results. +# One phony target per language test file so `make -jN` parallelizes +# the suite, each in its own persistent -w workdir so only changed +# packages rebuild. Tests always execute — the workdir caches builds, +# never results. LANG_TEST_FILES = $(wildcard test/lang/*_test.ww) LANG_TEST_TARGETS = $(LANG_TEST_FILES:%=wwtest/%) -LIBRARY_TEST_TARGETS = $(LIBRARY_TESTS:%=wwtest/%) $(LANG_TEST_TARGETS): wwtest/%: $(BIN)/ww \ $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a @@ -555,13 +531,6 @@ $(LANG_TEST_TARGETS): wwtest/%: $(BIN)/ww \ @mkdir -p $(WWBUILD)/wwtest/$(subst /,_,$*) @$(BIN)/ww test -w $(WWBUILD)/wwtest/$(subst /,_,$*) $* -$(LIBRARY_TEST_TARGETS): wwtest/%: $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a - @echo "ww test $*" - @mkdir -p $(WWBUILD)/wwtest/$(subst /,_,$*) - @$(BIN)/ww test -w $(WWBUILD)/wwtest/$(subst /,_,$*) \ - -I $(CURDIR)/lib/ww $* - $(BYTEID_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a \ $(BIN)/w6l $(BIN)/wwdump $(LIB)/libwwrt.a \ $(BIN)/ww_ww $(BIN)/w6c_ww @@ -640,16 +609,19 @@ $(TOOL_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ test-lang: $(LANG_TEST_TARGETS) -test-library: $(LIBRARY_TEST_TARGETS) $(BIN)/w6c $(BIN)/w6c_ww \ - $(WWTEST_BIN) $(LIBRARY_STANDALONE_SOURCES) +test-library: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(LIB)/libwwrt.a $(BIN)/w6c_ww $(WWTEST_BIN) \ + $(LIBRARY_STANDALONE_SOURCES) @set -e; for f in $(LIBRARY_STANDALONE_SOURCES); do \ echo "library compile c $$f"; \ $(CURDIR)/$(BIN)/w6c $(CURDIR)/$$f > /dev/null; \ echo "library compile ww $$f"; \ $(CURDIR)/$(BIN)/w6c_ww $(CURDIR)/$$f > /dev/null; \ done - @echo "ww test lib/regex" - @$(CURDIR)/$(BIN)/ww test lib/regex + @echo "ww test -j $(JOBS) lib/..." + @mkdir -p $(WWBUILD)/wwtest-lib + @$(CURDIR)/$(BIN)/ww test -j $(JOBS) -w $(WWBUILD)/wwtest-lib \ + -I $(CURDIR)/lib/ww lib/... test-bootstrap-native: $(WRAPPER_TOOLS) $(BOOTSTRAP_WRAPPER_BINS) @set -e; for t in $(BOOTSTRAP_WRAPPER_BINS); do \ @@ -1000,7 +972,7 @@ nocc: .PHONY: all cstage wwstage wwfixture test-wwfixture-process test-wwfixture \ test test-unit test-compiler-smoke test-compiler test-package \ test-lang test-library test-commit test-lang-byteid \ - $(LANG_TEST_TARGETS) $(LIBRARY_TEST_TARGETS) $(BYTEID_WW_TARGETS) \ + $(LANG_TEST_TARGETS) $(BYTEID_WW_TARGETS) \ $(SEP_WW_TARGETS) test-data-byteid test-byteid \ test-bootstrap-native test-bootstrap test-incremental \ test-platform test-all install clean bootstrap nocc bootstrap-snapshot diff --git a/cmd/ww/main.c b/cmd/ww/main.c index 45ce516e..1ffc7021 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -1708,16 +1708,13 @@ do_test(int argc, char **argv) "ww test: -o needs -c for a package target\n"); return 2; } - if (workdir[0]) { - fprintf(stderr, - "ww test: -w needs a single test file\n"); - return 2; - } if (pattern) { fprintf(stderr, "ww test: pattern needs a single test file\n"); return 2; } + /* -w forwards: the coordinator keys one persistent driver + * workdir per package group under the given root. */ return exec_package_tests(argc, argv, src, NULL, 0); } struct stat st; @@ -1742,11 +1739,6 @@ do_test(int argc, char **argv) "ww test: -o needs -c for a package target\n"); return 2; } - if (workdir[0]) { - fprintf(stderr, - "ww test: -w needs a single test file\n"); - return 2; - } if (pattern) { fprintf(stderr, "ww test: pattern needs a single test file\n"); @@ -1882,10 +1874,6 @@ do_test(int argc, char **argv) fprintf(stderr, "ww test: -o needs -c for a package target\n"); return 2; } - if (workdir[0]) { - fprintf(stderr, "ww test: -w needs a single test file\n"); - return 2; - } /* The second bare positional remains the legacy single-file filter form; * package filtering uses explicit -run/-filter options. */ if (pattern) { diff --git a/docs/test-system-v2.md b/docs/test-system-v2.md index 7cc18bfe..421aac74 100644 --- a/docs/test-system-v2.md +++ b/docs/test-system-v2.md @@ -26,7 +26,7 @@ categories out of the ordinary developer target. | Compile success/rejection, stage-routed diagnostics, and runtime exit | `test/wcc/data/*/case.ww`, executed by `wwfixture` | | Package semantics | `test/package` and the native package-test coordinator | | Language behavior | `test/lang/*_test.ww` through the language `@test` runtime | -| Library behavior | Library-owned `@test` sources named by `LIBRARY_TESTS` | +| Library behavior | The package coordinator's `lib/...` tree walk (`ww test -j N -I lib/ww lib/...`) | | Standalone library-source compilation | Four real source paths named by `LIBRARY_STANDALONE_SOURCES`, compiled directly by both frontends | | Compiler-output identity | `test-lang-byteid` and `test-data-byteid` plus residual byte/artifact carriers | | Fixed point and self-host | `test-bootstrap` | @@ -101,7 +101,7 @@ outright; git history is the archive. | `test-compiler` | Complete fixture corpus plus residual compiler/integration carriers | | `test-package` | Package planning, grouping, routing, and package runtime only | | `test-lang` | Language-owned `@test` behavior | -| `test-library` | Library-owned `@test` behavior plus four direct standalone-source C/WW compilation checks | +| `test-library` | The coordinator's `lib/...` walk plus four direct standalone-source C/WW compilation checks | | `test-commit` | Unit + compiler + package + language + library behavior | | `test-byteid` | Compiler-output identity gates | | `test-bootstrap` | Fixed-point bootstrap plus the 950/991–995 native gates | @@ -246,6 +246,16 @@ This is build staleness in the Make/mk/Go sense, not a result cache: tests always run, and the byte-identity and bootstrap gates keep building on fresh scratch. `make clean` reclaims every workdir under `out/`. +On a package or tree target, `ww test -w DIR` forwards to the coordinator, +which keys one persistent driver workdir per package group under `DIR` +(`_` with `/` flattened) and passes it to each inner build; every +reuse decision stays with the driver's content-identity contract above. +`-w` and `-c` do not mix there — the coordinator rejects the combination +rather than guess which artifact tree the caller owns. `test-library` uses +exactly this: one `ww test -j $(JOBS) -w out/wwbuild/wwtest-lib -I lib/ww +lib/...` line owns library behavior (measured: 2.3s cold, 1.0s warm at +`-j 4`, vs 4.7s for the retired 59-target per-file fan-out). + `test/lang` currently uses one package per source file, so its complete gate still performs independent package builds. That remaining source layout is not hidden behind caching or concurrency; it is outside the small `test` target. diff --git a/internal/wwpackage/package.ww b/internal/wwpackage/package.ww index b831ec8c..1993ae7f 100644 --- a/internal/wwpackage/package.ww +++ b/internal/wwpackage/package.ww @@ -32,6 +32,7 @@ type pkggroup = struct { external: bool, root: str, combined: str, + workdir: str, bin: str, buildout: str, builderr: str, @@ -105,9 +106,10 @@ fn pkgfailpath(path: str, reason: str) void = { fn pkgusage() void = { let s: str = strings.concat( - "usage: wwtest package [-c] [-list] [-j N] [-I DIR] [-run|-filter GLOB] [-timeout-ms=N] [DIR | DIR/...] [-- GLOB ...]\n", + "usage: wwtest package [-c] [-list] [-j N] [-I DIR] [-w DIR] [-run|-filter GLOB] [-timeout-ms=N] [DIR | DIR/...] [-- GLOB ...]\n", " *_test.ww is canonical; noncanonical files require an actual @test declaration\n", - " -c retains the compiled package binaries; -j N runs up to N package groups at once\n"); + " -c retains the compiled package binaries; -j N runs up to N package groups at once\n", + " -w DIR keys a persistent per-package-group build workdir under DIR\n"); pkgput(os.STDERR_FILENO, s); }; @@ -505,12 +507,35 @@ fn pkgcombined(g: *pkggroup, srcs: []pkgsource) bool = { return ok; }; +fn pkgworkkey(dir: str, pkg: str) str = { + let s: str = strings.dup(strings.concat(dir, "_", pkg)); + let b: []u8 = strings.toutf8(s); + let i: i32 = 0; + for (i < b.len) { + if (b[i] == '/') { b[i] = '_'; }; + i += 1; + }; + return strings.frombytes(b); +}; + fn pkgsetpaths(g: *pkggroup, root: str, index: i32, - compileonly: bool, outname: str) bool = { + compileonly: bool, outname: str, workroot: str) bool = { let num: str = strconv.i32tos(index, strconv.base.DEC); g.root = strings.concat(root, "/group-", num); if (!pkgmakedir(g.root)) { return false; }; g.combined = strings.concat(g.root, "/package.ww"); + // A caller-owned persistent workdir root keys one driver -w dir + // per (dir, pkg) group; the driver's content-identity contract + // owns every reuse decision, so this stays a pure path policy. + g.workdir = ""; + if (workroot.len != 0) { + g.workdir = strings.concat(workroot, "/", + pkgworkkey(g.dir, g.pkg)); + match (os.mkdirs(g.workdir, 448)) { + case void => void; + case let e: os.oserror => return false; + }; + }; if (compileonly) { if (outname.len != 0) { g.bin = outname; @@ -561,7 +586,7 @@ fn pkgreportcommand(kind: str, g: *pkggroup, r: *exec.result) void = { fn pkgstartbuild(g: *pkggroup, builder: str, includes: []str, h: *exec.process) void = { - let ba: []str = alloc([], (9 + includes.len * 2): u64)!; + let ba: []str = alloc([], (11 + includes.len * 2): u64)!; append(ba, builder); append(ba, "test"); append(ba, "-c"); @@ -575,6 +600,10 @@ fn pkgstartbuild(g: *pkggroup, builder: str, includes: []str, append(ba, includes[ii]); ii += 1; }; + if (g.workdir.len != 0) { + append(ba, "-w"); + append(ba, g.workdir); + }; append(ba, g.combined); let bcmd: exec.command; bcmd.path = builder; @@ -674,6 +703,7 @@ export fn packagecommand(args: []str) int = { let includes: []str = alloc([], (args.len + 1): u64)!; let timeoutarg: str = ""; let outname: str = ""; + let workroot: str = ""; let builder: str = pkgdefaultbuilder(); let i: i32 = 0; for (i < args.len) { @@ -704,6 +734,17 @@ export fn packagecommand(args: []str) int = { i += 1; continue; }; + if (strings.compare(a, "-w") == 0) { + if (i + 1 >= args.len) { pkgusage(); return 2; }; + workroot = args[i + 1]; + i += 2; + continue; + }; + if (strings.hasprefix(a, "-w") && a.len > 2) { + workroot = a[2:a.len]; + i += 1; + continue; + }; if (strings.hasprefix(a, "-timeout-ms=")) { if (timeoutarg.len != 0 || a.len == 12 || pkgparsedec(a[12:a.len], 3600000i64) <= 0i64) { @@ -757,6 +798,14 @@ export fn packagecommand(args: []str) int = { pkgputln(os.STDERR_FILENO, "wwtest package: -o needs -c"); return 2; }; + // -c publishes caller-owned sepwork artifacts; mixing that + // contract with a persistent workdir is unwired — reject rather + // than guess which tree the caller owns. + if (workroot.len != 0 && compileonly) { + pkgputln(os.STDERR_FILENO, + "wwtest package: -w conflicts with -c"); + return 2; + }; // Go's ./... form: a trailing "..." path element walks the tree // rooted at the prefix instead of one explicit directory. @@ -924,7 +973,7 @@ export fn packagecommand(args: []str) int = { for (!stopped && launched < groups.len && active < jobs) { let g: *pkggroup = &groups[launched]; if (!pkgsetpaths(g, tmproot, launched, compileonly, - outname)) { + outname, workroot)) { g.fail = PKGFAILSETUP; g.state = PKGDONE; stopped = true; diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index 4685780a..8191c17f 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -2694,14 +2694,12 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { cerr("ww test: -o needs -c for a package target\n"); return 2; }; - if (workdir != nil) { - cerr("ww test: -w needs a single test file\n"); - return 2; - }; if (patarg != nil) { cerr("ww test: pattern needs a single test file\n"); return 2; }; + // -w forwards: the coordinator keys one persistent driver + // workdir per package group under the given root. return execpackagetests(selfdir, argv, argc, start, targetindex, nil, false); }; @@ -2742,10 +2740,6 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { cerr("ww test: -o needs -c for a package target\n"); return 2; }; - if (workdir != nil) { - cerr("ww test: -w needs a single test file\n"); - return 2; - }; if (patarg != nil) { cerr("ww test: pattern needs a single test file\n"); return 2; diff --git a/test/package/package_test.ww b/test/package/package_test.ww index 649a960c..0caf4554 100644 --- a/test/package/package_test.ww +++ b/test/package/package_test.ww @@ -572,14 +572,36 @@ fn packagepath(relative: str) str = { assert(has(outc.stderr, "ww test: -o needs -c for a package target\n")); - let wdc: []str = [driver("ww"), "test", "-w", root, spec]; - let wdw: []str = [driver("ww_ww"), "test", "-w", root, spec]; - runcommand(root, "tree-w-c", wdc, time.second, &outc); - runcommand(root, "tree-w-ww", wdw, time.second, &outw); + // -w on a package target forwards to the coordinator: one + // persistent driver workdir per package group under the root, + // reuse owned by the driver's content-identity contract. The + // byte stream must match the plain run, cold and warm. + let wdroot: str = strings.concat(root, "/wd"); + assert(os.mkdir(wdroot, 448i32) == 0); + let wdc: []str = [driver("ww"), "test", "-w", wdroot, spec]; + let wdw: []str = [driver("ww_ww"), "test", "-w", wdroot, spec]; + runcommand(root, "tree-w-c", wdc, + (30i64 * (time.second: i64)): time.duration, &outc); + runcommand(root, "tree-w-ww", wdw, + (30i64 * (time.second: i64)): time.duration, &outw); + expectexit(&outc, 0); + expectexit(&outw, 0); + assert(same(outc.stdout, outw.stdout)); + assert(same(outc.stdout, seq)); + runcommand(root, "tree-w-warm", wdc, + (30i64 * (time.second: i64)): time.duration, &outc); + expectexit(&outc, 0); + assert(same(outc.stdout, seq)); + // -c publishes caller-owned artifacts; a persistent workdir is a + // different ownership contract — the coordinator rejects the mix. + let wcc: []str = [driver("ww"), "test", "-c", "-w", wdroot, spec]; + let wcw: []str = [driver("ww_ww"), "test", "-c", "-w", wdroot, spec]; + runcommand(root, "tree-wc-c", wcc, time.second, &outc); + runcommand(root, "tree-wc-ww", wcw, time.second, &outw); expectexit(&outc, 2); expectexit(&outw, 2); assert(same(outc.stderr, outw.stderr)); - assert(has(outc.stderr, "ww test: -w needs a single test file\n")); + assert(has(outc.stderr, "wwtest package: -w conflicts with -c\n")); let bare: str = strings.concat(root, "/bare"); assert(os.mkdir(bare, 448i32) == 0);