diff --git a/cmd/ww/main.c b/cmd/ww/main.c index f7102f22..45ce516e 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -1696,9 +1696,16 @@ do_test(int argc, char **argv) size_t tlen = strlen(target); if (strcmp(target, "...") == 0 || (tlen >= 4 && strcmp(target + tlen - 4, "/...") == 0)) { - if (outstem[0]) { + if (emit_asm) { fprintf(stderr, - "ww test: -c/-S/-o need a single test file\n"); + "ww test: -S needs a single test file\n"); + return 2; + } + /* -c -o forwards: the coordinator names the single + * package's artifact and rejects a multi-package fan-out. */ + if (outstem[0] && !compileonly) { + fprintf(stderr, + "ww test: -o needs -c for a package target\n"); return 2; } if (workdir[0]) { @@ -1725,9 +1732,14 @@ do_test(int argc, char **argv) return 1; } if (is_dir) { - if (outstem[0]) { + if (emit_asm) { fprintf(stderr, - "ww test: -c/-S/-o need a single test file\n"); + "ww test: -S needs a single test file\n"); + return 2; + } + if (outstem[0] && !compileonly) { + fprintf(stderr, + "ww test: -o needs -c for a package target\n"); return 2; } if (workdir[0]) { @@ -1862,8 +1874,12 @@ do_test(int argc, char **argv) fprintf(stderr, "ww test: %s is neither file nor directory\n", target); return 1; } - if (outstem[0]) { - fprintf(stderr, "ww test: -c/-S/-o need a single test file\n"); + if (emit_asm) { + fprintf(stderr, "ww test: -S needs a single test file\n"); + return 2; + } + if (outstem[0] && !compileonly) { + fprintf(stderr, "ww test: -o needs -c for a package target\n"); return 2; } if (workdir[0]) { diff --git a/docs/test-system-v2.md b/docs/test-system-v2.md index 54b32bce..f9292769 100644 --- a/docs/test-system-v2.md +++ b/docs/test-system-v2.md @@ -207,9 +207,14 @@ at every `-j` level, and `-j 1` — the default — matches the former sequential loop exactly. Measured on the 31-package `lib/...` walk: 7.0s sequential, 2.4s at `-j 4`. With `-c`, it publishes each exact `.test` binary and adjacent `.test.sepwork` tree in the -package directory; those become caller-owned artifacts. Without `-c`, it -removes the temporary binary and scratch with its workspace. The language -runtime owns individual `@test` functions. +package directory; those become caller-owned artifacts. `-c -o ` +names that artifact instead of the fixed stem, for exactly one package: +the coordinator rejects a multi-package fan-out ("cannot use -o with +multiple packages", Go's `go test -o` rule), and `-o` without `-c` is +rejected at the driver ("needs -c for a package target") because a plain +run always executes from the temp root. Without `-c`, it removes the +temporary binary and scratch with its workspace. The language runtime +owns individual `@test` functions. Separate compilation is the only driver build path; no compatibility mode switch remains. @@ -320,11 +325,8 @@ timeout policy in this architecture. ## Open driver work -Carried over from the dissolved project plan; each is deliberate scope, not -drift: - -- A package-level `-o` contract (single-file `ww test -o` exists; directory - packages publish fixed `.test` stems under `-c`). +None; the package-level `-o` contract (the last carried bullet) landed as +`-c -o ` for exactly one package. ## Validation policy diff --git a/internal/wwpackage/package.ww b/internal/wwpackage/package.ww index 43aa40a2..b831ec8c 100644 --- a/internal/wwpackage/package.ww +++ b/internal/wwpackage/package.ww @@ -506,13 +506,17 @@ fn pkgcombined(g: *pkggroup, srcs: []pkgsource) bool = { }; fn pkgsetpaths(g: *pkggroup, root: str, index: i32, - compileonly: bool) bool = { + compileonly: bool, outname: 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"); if (compileonly) { - g.bin = strings.concat(g.dir, "/", g.pkg, ".test"); + if (outname.len != 0) { + g.bin = outname; + } else { + g.bin = strings.concat(g.dir, "/", g.pkg, ".test"); + }; } else { g.bin = strings.concat(g.root, "/package.test"); }; @@ -669,6 +673,7 @@ export fn packagecommand(args: []str) int = { let filters: []str = alloc([], (args.len + 1): u64)!; let includes: []str = alloc([], (args.len + 1): u64)!; let timeoutarg: str = ""; + let outname: str = ""; let builder: str = pkgdefaultbuilder(); let i: i32 = 0; for (i < args.len) { @@ -688,6 +693,17 @@ export fn packagecommand(args: []str) int = { i += 1; continue; }; + if (strings.compare(a, "-o") == 0) { + if (i + 1 >= args.len) { pkgusage(); return 2; }; + outname = args[i + 1]; + i += 2; + continue; + }; + if (strings.hasprefix(a, "-o") && a.len > 2) { + outname = 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) { @@ -735,6 +751,12 @@ export fn packagecommand(args: []str) int = { pkgusage(); return 2; }; + // -o names the -c artifact; a plain run always executes from the + // temp root, so a caller-owned name has nothing to name. + if (outname.len != 0 && !compileonly) { + pkgputln(os.STDERR_FILENO, "wwtest package: -o needs -c"); + return 2; + }; // Go's ./... form: a trailing "..." path element walks the tree // rooted at the prefix instead of one explicit directory. @@ -870,6 +892,13 @@ export fn packagecommand(args: []str) int = { }; if (groups.len == 0) { return 0; }; pkgsortgroups(groups); + // One caller-owned name cannot fan out (Go: `go test -o` with + // multiple packages is an error). + if (outname.len != 0 && groups.len > 1) { + pkgputln(os.STDERR_FILENO, + "wwtest package: cannot use -o with multiple packages"); + return 2; + }; let borrowed: str = temp.dir(); let tmproot: str = strings.dup(borrowed); @@ -894,7 +923,8 @@ export fn packagecommand(args: []str) int = { for (emitted < groups.len) { for (!stopped && launched < groups.len && active < jobs) { let g: *pkggroup = &groups[launched]; - if (!pkgsetpaths(g, tmproot, launched, compileonly)) { + if (!pkgsetpaths(g, tmproot, launched, compileonly, + outname)) { g.fail = PKGFAILSETUP; g.state = PKGDONE; stopped = true; diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index 57772a6d..4685780a 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -2684,8 +2684,14 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { && target[tlen - 1u64] == '.'; }; if (istree) { - if (outstem != nil) { - cerr("ww test: -c/-S/-o need a single test file\n"); + if (emitasm != 0) { + cerr("ww test: -S needs a single test file\n"); + return 2; + }; + // -c -o forwards: the coordinator names the single + // package's artifact and rejects a multi-package fan-out. + if (outstem != nil && compileonly == 0) { + cerr("ww test: -o needs -c for a package target\n"); return 2; }; if (workdir != nil) { @@ -2728,8 +2734,12 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { emitasm, outstem, workdir, patarg); }; - if (outstem != nil) { - cerr("ww test: -c/-S/-o need a single test file\n"); + if (emitasm != 0) { + cerr("ww test: -S needs a single test file\n"); + return 2; + }; + if (outstem != nil && compileonly == 0) { + cerr("ww test: -o needs -c for a package target\n"); return 2; }; if (workdir != nil) { diff --git a/test/package/package_test.ww b/test/package/package_test.ww index 7cd417da..649a960c 100644 --- a/test/package/package_test.ww +++ b/test/package/package_test.ww @@ -569,7 +569,8 @@ fn packagepath(relative: str) str = { expectexit(&outc, 2); expectexit(&outw, 2); assert(same(outc.stderr, outw.stderr)); - assert(has(outc.stderr, "ww test: -c/-S/-o need a single test file\n")); + 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]; @@ -775,3 +776,72 @@ fn runtimepath(relative: str) str = { "escaped_writer_does_not_hold_runner ... ok\n")); clean(root); }; + +// The package-level -o contract: -c -o names the single package's +// artifact in place of the fixed .test stem; -o without -c +// has nothing to name (runs execute from the temp root); one name +// cannot fan out over multiple packages (Go's `go test -o` rule). +@test fn compile_artifact_naming() void = { + let root: str = fresh(); + let pdir: str = strings.concat(root, "/pkg"); + assert(os.mkdir(pdir, 493i32) == 0); + writefile(strings.concat(pdir, "/pkg.ww"), + "package pkg;\nexport fn v() i32 = { return 7; };\n"); + writefile(strings.concat(pdir, "/pkg_test.ww"), strings.concat( + "package pkg_test;\nimport pkg;\n", + "@test fn seven() void = { assert(pkg.v() == 7); };\n")); + let adir: str = strings.concat(root, "/a"); + assert(os.mkdir(adir, 493i32) == 0); + // '_'-prefixed: the tree walk skips it, so the artifacts and + // their .sepwork trees never pollute the multi-package discovery. + let outdir: str = strings.concat(root, "/_out"); + assert(os.mkdir(outdir, 493i32) == 0); + writefile(strings.concat(adir, "/a.ww"), + "package a;\nexport fn v() i32 = { return 1; };\n"); + writefile(strings.concat(adir, "/a_test.ww"), strings.concat( + "package a_test;\nimport a;\n", + "@test fn one() void = { assert(a.v() == 1); };\n")); + let out: commandout; + let drvs: []str = ["ww", "ww_ww"]; + let i: i32 = 0; + for (i < 2) { + let named: str = strings.concat(outdir, "/out_", drvs[i], + ".bin"); + let coav: []str = [driver(drvs[i]), "test", "-c", "-o", + named, pdir]; + runcommand(root, strings.concat("nameco_", drvs[i]), coav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let fi: os.filestat; + match (os.stat(&fi, named)) { + case void => void; + case let e: os.oserror => abort("-c -o artifact missing"); + }; + match (os.stat(&fi, strings.concat(pdir, "/pkg.test"))) { + case void => abort("-c -o still published the fixed stem"); + case let e: os.oserror => void; + }; + let runav: []str = [named]; + runcommand(root, strings.concat("namerun_", drvs[i]), runav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "seven ... ok")); + + let nocav: []str = [driver(drvs[i]), "test", "-o", named, + pdir]; + runcommand(root, strings.concat("noc_", drvs[i]), nocav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 2); + assert(has(out.stderr, "-o needs -c for a package target")); + + let mulav: []str = [driver(drvs[i]), "test", "-c", "-o", + named, strings.concat(root, "/...")]; + runcommand(root, strings.concat("multi_", drvs[i]), mulav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 2); + assert(has(out.stderr, + "cannot use -o with multiple packages")); + i += 1; + }; + clean(root); +};