ww output: match Go exact -o parsing

This commit is contained in:
2026-08-22 01:59:02 +09:00
parent 4b9968e4e6
commit d61805262e
7 changed files with 701 additions and 36 deletions

View File

@@ -13816,6 +13816,435 @@ fn runtimepath(relative: str) str = {
clean(root);
};
@test fn output_flag_exact_name_and_value_semantics() void = {
let root: str = fresh();
let source: str = strings.concat(root, "/source");
let multi_a: str = strings.concat(source, "/alpha");
let multi_b: str = strings.concat(source, "/beta");
let tests: str = strings.concat(source, "/tests");
let outputs: str = strings.concat(root, "/outputs");
mkdirall(source); mkdirall(multi_a); mkdirall(multi_b);
mkdirall(tests); mkdirall(outputs);
let single: str = strings.concat(source, "/single.ww");
writefile(single,
"package main;\nfn main() i32 = { return 29; };\n");
writefile(strings.concat(multi_a, "/main.ww"),
"package main;\nfn main() i32 = { return 31; };\n");
writefile(strings.concat(multi_b, "/main.ww"),
"package main;\nfn main() i32 = { return 37; };\n");
writefile(strings.concat(tests, "/tests.ww"),
"package exactflags;\nfn value() i32 = { return 41; };\n");
writefile(strings.concat(tests, "/tests_test.ww"), strings.concat(
"package exactflags;\n",
"@test fn exact() void = { assert(value() == 41); };\n"));
let rawtest: str = strings.concat(source, "/raw_test.ww");
writefile(rawtest, strings.concat(
"package exactraw;\n",
"@test fn raw_exact() void = { assert(true); };\n"));
let compilerwrapper: str = strings.concat(root, "/output-flag-w6c.sh");
writeexecutable(compilerwrapper, strings.concat(
"#!/bin/sh\nprintf '%s\\n' \"$*\" >> \"$WW_OUTPUT_FLAG_TRACE\"\n",
"exec \"$WW_OUTPUT_FLAG_REAL_C\" \"$@\"\n"));
let stages: []str = ["ww", "ww_ww"];
let compilers: []str = ["w6c", "w6c_ww"];
let tags: []str = ["c", "ww"];
let forms: []str = ["one-separate", "one-equals", "two-separate",
"two-equals"];
let buildref: str = "";
let testref: str = "";
let alpharef: str = "";
let betaref: str = "";
let rawref: str = "";
let rejectrefs: []str = ["", "", "", "", ""];
let coordinatorrejectref: str = "";
let buildmissingref: str = "";
let testmissingref: str = "";
let stopref: str = "";
let baseenv: []str = os.getenvs();
let out: commandout;
let si: i32 = 0;
for (si < stages.len) {
let trace: str = strings.concat(root, "/output-flag-", tags[si],
".trace");
writefile(trace, "");
let env: []str = alloc([], (baseenv.len + 3): u64)!;
let ei: i32 = 0;
for (ei < baseenv.len) {
if (!strings.hasprefix(baseenv[ei], "WW_W6C=")
&& !strings.hasprefix(baseenv[ei], "WW_OUTPUT_FLAG_")) {
append(env, baseenv[ei]);
};
ei += 1;
};
append(env, strings.concat("WW_W6C=", compilerwrapper));
append(env, strings.concat("WW_OUTPUT_FLAG_REAL_C=",
driver(compilers[si])));
append(env, strings.concat("WW_OUTPUT_FLAG_TRACE=", trace));
let ignoredrunout: str = strings.concat(outputs, "/ignored-run-",
tags[si]);
let runcontrol: []str = [driver(stages[si]), "run", "-I", source,
strings.concat("-o", ignoredrunout), single];
runcommand(root, strings.concat("output-flag-run-control-", tags[si]),
runcontrol, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 29);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& !os.exists(ignoredrunout));
// Exact output names change only publication. Distinct accepted
// spellings retain the same action and executable bytes.
let buildwork: str = strings.concat(root, "/build-work-", tags[si]);
mkdirall(buildwork);
let fi: i32 = 0;
for (fi < forms.len) {
let binary: str = strings.concat(outputs, "/build-", tags[si],
"-", forms[fi], "=tail");
let av: []str = [driver(stages[si]), "build", "-w", buildwork,
"-I", source];
if (fi == 0) { append(av, "-o"); append(av, binary); }
else if (fi == 1) { append(av, strings.concat("-o=", binary)); }
else if (fi == 2) { append(av, "--o"); append(av, binary); }
else { append(av, strings.concat("--o=", binary)); };
append(av, single);
rewritefile(trace, "");
runcommandenv(root, strings.concat("output-flag-build-", tags[si],
"-", forms[fi]), av, env,
(60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& os.exists(binary));
if (fi == 0) { assert(readfile(trace).len != 0); }
else { assert(readfile(trace).len == 0); };
let bytes: str = readfile(binary);
if (buildref.len == 0) { buildref = strings.dup(bytes); }
else { assert(same(buildref, bytes)); };
let runav: []str = [binary];
runcommand(root, strings.concat("output-flag-build-run-", tags[si],
"-", forms[fi]), runav, time.second, &out);
expectexit(&out, 29);
assert(out.stdout.len == 0 && out.stderr.len == 0);
fi += 1;
};
// Repeated string flags are last-wins. A final empty value returns to
// default naming and cannot create a file or scratch tree named '='.
let stale: str = strings.concat(outputs, "/stale-build-", tags[si]);
let winner: str = strings.concat(outputs, "/winner-build-", tags[si],
"=last");
let lastav: []str = [driver(stages[si]), "build", "-w", buildwork,
"-I", source, "-o", stale, strings.concat("--o=", winner), single];
rewritefile(trace, "");
runcommandenv(root, strings.concat("output-flag-last-", tags[si]),
lastav, env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& !os.exists(stale) && same(buildref, readfile(winner))
&& readfile(trace).len == 0);
let defaultbin: str = strings.concat(root, "/single");
let emptyav: []str = [driver(stages[si]), "build", "-w", buildwork,
"-I", source, "-o", stale, "-o=", single];
rewritefile(trace, "");
runcommandenvdir(root, strings.concat("output-flag-empty-", tags[si]),
emptyav, env, root,
(60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& !os.exists(stale) && same(buildref, readfile(defaultbin))
&& readfile(trace).len == 0
&& !os.exists(strings.concat(root, "/="))
&& !os.exists(strings.concat(root, "/=.sepwork")));
assert(os.remove(defaultbin) == 0);
clean(strings.concat(defaultbin, ".sepwork"));
// Build parsing stops at its first operand: a later flag-like value is
// package input, not a second output option.
let early: str = strings.concat(outputs, "/early");
let late: str = strings.concat(outputs, "/late");
let stopav: []str = [driver(stages[si]), "build", "-I", source,
"-o", early, single, strings.concat("--o=", late)];
runcommand(root, strings.concat("output-flag-stop-", tags[si]), stopav,
(60i64 * (time.second: i64)): time.duration, &out);
assert(out.termination == exec.termination.EXIT && out.code != 0);
assert(out.stdout.len == 0 && !has(out.stderr, "unknown flag")
&& !os.exists(early) && !os.exists(late));
if (si == 0) { stopref = strings.dup(out.stderr); }
else { assert(same(stopref, out.stderr)); };
// Joined names are unknown before loading, work creation, or output.
let ri: i32 = 0;
for (ri < 2) {
let badout: str = strings.concat(outputs, "/bad-build-", tags[si],
"-", forms[ri]);
let badwork: str = strings.concat(root, "/bad-build-work-", tags[si],
"-", forms[ri]);
let prefix: str = "-o";
if (ri != 0) { prefix = "--o"; };
let joined: str = strings.concat(prefix, badout);
let badav: []str = [driver(stages[si]), "build", "-w", badwork,
"-I", source, joined, single];
runcommand(root, strings.concat("output-flag-reject-build-", tags[si],
"-", forms[ri]), badav, time.second, &out);
expectexit(&out, 2);
assert(out.stdout.len == 0
&& same(out.stderr, "ww build: unknown flag\n")
&& !os.exists(badout) && !os.exists(badwork));
if (si == 0) { rejectrefs[ri] = strings.dup(out.stderr); }
else { assert(same(rejectrefs[ri], out.stderr)); };
ri += 1;
};
let buildmissingwork: str = strings.concat(root,
"/missing-build-work-", tags[si]);
let buildmissing: []str = [driver(stages[si]), "build", "-w",
buildmissingwork, "-I", source, "--o"];
runcommand(root, strings.concat("output-flag-missing-build-", tags[si]),
buildmissing, time.second, &out);
expectexit(&out, 2);
assert(out.stdout.len == 0
&& same(out.stderr, "ww build: -o needs an argument\n")
&& !os.exists(buildmissingwork));
if (si == 0) { buildmissingref = strings.dup(out.stderr); }
else { assert(same(buildmissingref, out.stderr)); };
// Two roots exercise coordinator output planning. The second spelling
// reuses the first request's compiled actions, and final empty performs
// the established no-public-output multi-build.
let multiwork: str = strings.concat(root, "/multi-work-", tags[si]);
mkdirall(multiwork);
let multione: str = strings.concat(root, "/multi-one-", tags[si], "/");
let multiav: []str = [driver(stages[si]), "build", "-w", multiwork,
"-I", source, strings.concat("--o=", multione), multi_a, multi_b];
rewritefile(trace, "");
runcommandenvdir(root, strings.concat("output-flag-multi-one-", tags[si]),
multiav, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& readfile(trace).len != 0);
let alphaone: str = strings.concat(multione, "alpha");
let betaone: str = strings.concat(multione, "beta");
if (alpharef.len == 0) {
alpharef = readfile(alphaone); betaref = readfile(betaone);
} else {
assert(same(alpharef, readfile(alphaone))
&& same(betaref, readfile(betaone)));
};
let alpharun: []str = [alphaone];
runcommand(root, strings.concat("output-flag-alpha-run-", tags[si]),
alpharun, time.second, &out);
expectexit(&out, 31);
assert(out.stdout.len == 0 && out.stderr.len == 0);
let betarun: []str = [betaone];
runcommand(root, strings.concat("output-flag-beta-run-", tags[si]),
betarun, time.second, &out);
expectexit(&out, 37);
assert(out.stdout.len == 0 && out.stderr.len == 0);
let multitwo: str = strings.concat(root, "/multi-two-", tags[si], "/");
let warmav: []str = [driver(stages[si]), "build", "-w", multiwork,
"-I", source, "-o", multitwo, multi_a, multi_b];
rewritefile(trace, "");
runcommandenvdir(root, strings.concat("output-flag-multi-warm-", tags[si]),
warmav, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& readfile(trace).len == 0
&& same(alpharef, readfile(strings.concat(multitwo, "alpha")))
&& same(betaref, readfile(strings.concat(multitwo, "beta"))));
let unusedmulti: str = strings.concat(root, "/multi-unused-", tags[si],
"/");
let multiempty: []str = [driver(stages[si]), "build", "-w", multiwork,
"-I", source, "-o", unusedmulti, "--o=", multi_a, multi_b];
rewritefile(trace, "");
runcommandenvdir(root, strings.concat("output-flag-multi-empty-", tags[si]),
multiempty, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& readfile(trace).len == 0 && !os.exists(unusedmulti)
&& !os.exists(strings.concat(root, "/alpha"))
&& !os.exists(strings.concat(root, "/beta")));
let multibadout: str = strings.concat(outputs, "/bad-multi-", tags[si]);
let multibadwork: str = strings.concat(root, "/bad-multi-work-", tags[si]);
let multibad: []str = [driver(stages[si]), "build", "-w", multibadwork,
"-I", source, strings.concat("--o", multibadout), multi_a, multi_b];
runcommand(root, strings.concat("output-flag-reject-multi-", tags[si]),
multibad, time.second, &out);
expectexit(&out, 2);
assert(out.stdout.len == 0
&& same(out.stderr, "ww build: unknown flag\n")
&& !os.exists(multibadout) && !os.exists(multibadwork));
if (si == 0) { rejectrefs[4] = strings.dup(out.stderr); }
else { assert(same(rejectrefs[4], out.stderr)); };
let directbadout: str = strings.concat(outputs,
"/bad-direct-coordinator-", tags[si]);
let directbadwork: str = strings.concat(root,
"/bad-direct-coordinator-work-", tags[si]);
let directbad: []str = [driver("wwtest"), "package",
"--ww-operation", "build", "--ww-driver", driver(stages[si]),
"-w", directbadwork, "-I", source,
strings.concat("-o", directbadout), multi_a, multi_b];
runcommand(root, strings.concat("output-flag-reject-coordinator-",
tags[si]), directbad, time.second, &out);
expectexit(&out, 2);
assert(out.stdout.len == 0 && has(out.stderr, "usage: wwtest package")
&& !os.exists(directbadout) && !os.exists(directbadwork));
if (si == 0) { coordinatorrejectref = strings.dup(out.stderr); }
else { assert(same(coordinatorrejectref, out.stderr)); };
// Compile-only directory testing accepts the same four spellings. Its
// final empty option may follow the package and restores the default.
let testwork: str = strings.concat(root, "/test-work-", tags[si]);
mkdirall(testwork);
fi = 0;
for (fi < forms.len) {
let binary: str = strings.concat(outputs, "/test-", tags[si], "-",
forms[fi], "=tail");
let av: []str = [driver(stages[si]), "test", "-c", "-w", testwork,
"-I", source];
if (fi == 0) { append(av, "-o"); append(av, binary); }
else if (fi == 1) { append(av, strings.concat("-o=", binary)); }
else if (fi == 2) { append(av, "--o"); append(av, binary); }
else { append(av, strings.concat("--o=", binary)); };
append(av, tests);
rewritefile(trace, "");
runcommandenv(root, strings.concat("output-flag-test-", tags[si], "-",
forms[fi]), av, env,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& os.exists(binary));
if (fi == 0) { assert(readfile(trace).len != 0); }
else { assert(readfile(trace).len == 0); };
let bytes: str = readfile(binary);
if (testref.len == 0) { testref = strings.dup(bytes); }
else { assert(same(testref, bytes)); };
let runav: []str = [binary];
runcommand(root, strings.concat("output-flag-test-run-", tags[si], "-",
forms[fi]), runav,
(30i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stderr.len == 0 && has(out.stdout, "exact ... ok\n"));
fi += 1;
};
let staletest: str = strings.concat(outputs, "/stale-test-", tags[si]);
let runningretained: str = strings.concat(outputs,
"/running-test-", tags[si], "=tail");
let runningretainedav: []str = [driver(stages[si]), "test", "-w",
testwork, "-I", source, strings.concat("--o=", runningretained),
tests];
rewritefile(trace, "");
runcommandenvdir(root, strings.concat("output-flag-test-running-retained-",
tags[si]), runningretainedav, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stderr.len == 0 && has(out.stdout, "exact ... ok\n")
&& same(testref, readfile(runningretained))
&& readfile(trace).len == 0);
let defaulttest: str = strings.concat(root, "/tests.test");
let testempty: []str = [driver(stages[si]), "test", "-c", "-w",
testwork, "-I", source, "-o", staletest, tests, "--o", ""];
rewritefile(trace, "");
runcommandenvdir(root, strings.concat("output-flag-test-empty-", tags[si]),
testempty, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& !os.exists(staletest) && same(testref, readfile(defaulttest))
&& readfile(trace).len == 0
&& !os.exists(strings.concat(root, "/=")));
assert(os.remove(defaulttest) == 0);
let runningstale: str = strings.concat(outputs,
"/stale-running-test-", tags[si]);
let runningempty: []str = [driver(stages[si]), "test", "-w",
testwork, "-I", source, "-o", runningstale, tests, "-o="];
rewritefile(trace, "");
runcommandenvdir(root, strings.concat("output-flag-test-running-empty-",
tags[si]), runningempty, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stderr.len == 0 && has(out.stdout, "exact ... ok\n")
&& !os.exists(runningstale) && readfile(trace).len == 0);
// The raw single-file route owns output state in the public driver, so
// final empty must normalize before its retain/copy decision too.
let rawwork: str = strings.concat(root, "/raw-work-", tags[si]);
mkdirall(rawwork);
let rawretained: str = strings.concat(outputs, "/raw-test-", tags[si],
"=tail");
let rawretainedav: []str = [driver(stages[si]), "test", "-w", rawwork,
strings.concat("--o=", rawretained), rawtest];
runcommandenvdir(root, strings.concat("output-flag-raw-retained-",
tags[si]), rawretainedav, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stderr.len == 0 && has(out.stdout, "raw_exact ... ok\n")
&& os.exists(rawretained));
let rawpublishedbytes: str = readfile(rawretained);
if (rawref.len == 0) { rawref = strings.dup(rawpublishedbytes); }
else { assert(same(rawref, rawpublishedbytes)); };
let rawstale: str = strings.concat(outputs, "/stale-raw-", tags[si]);
let rawempty: []str = [driver(stages[si]), "test", "-c", "-w",
rawwork, "-o", rawstale, "--o=", rawtest];
runcommandenvdir(root, strings.concat("output-flag-raw-empty-", tags[si]),
rawempty, env, root,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
let rawbinary: str = strings.concat(rawwork, "/main");
assert(out.stdout.len == 0 && out.stderr.len == 0
&& !os.exists(rawstale) && os.exists(rawbinary)
&& !os.exists(strings.concat(root, "/=")));
let rawbytes: str = readfile(rawbinary);
assert(same(rawref, rawbytes));
ri = 0;
for (ri < 2) {
let badout: str = strings.concat(outputs, "/bad-test-", tags[si],
"-", forms[ri]);
let badwork: str = strings.concat(root, "/bad-test-work-", tags[si],
"-", forms[ri]);
let prefix: str = "-o";
if (ri != 0) { prefix = "--o"; };
let joined: str = strings.concat(prefix, badout);
let badav: []str = [driver(stages[si]), "test", "-c", "-w",
badwork, "-I", source, joined, tests];
runcommand(root, strings.concat("output-flag-reject-test-", tags[si],
"-", forms[ri]), badav, time.second, &out);
expectexit(&out, 2);
assert(out.stdout.len == 0
&& same(out.stderr, "ww test: unknown flag\n")
&& !os.exists(badout) && !os.exists(badwork));
if (si == 0) { rejectrefs[2 + ri] = strings.dup(out.stderr); }
else { assert(same(rejectrefs[2 + ri], out.stderr)); };
ri += 1;
};
let testmissingwork: str = strings.concat(root,
"/missing-test-work-", tags[si]);
let testmissing: []str = [driver(stages[si]), "test", "-c", "-w",
testmissingwork, "-I", source, "--o"];
runcommand(root, strings.concat("output-flag-missing-test-", tags[si]),
testmissing, time.second, &out);
expectexit(&out, 2);
assert(out.stdout.len == 0
&& same(out.stderr, "ww test: -o needs an argument\n")
&& !os.exists(testmissingwork));
if (si == 0) { testmissingref = strings.dup(out.stderr); }
else { assert(same(testmissingref, out.stderr)); };
let cleanpaths: []str = [root, outputs, source, multi_a, multi_b, tests,
buildwork, multiwork, testwork, rawwork];
let ci: i32 = 0;
for (ci < cleanpaths.len) {
assert(!directoryhasnew(cleanpaths[ci])
&& !directoryhasfragment(cleanpaths[ci], ".install")
&& !directoryhasfragment(cleanpaths[ci], ".wwtxn."));
ci += 1;
};
si += 1;
};
assert(!os.exists(strings.concat(root, "/="))
&& !os.exists(strings.concat(root, "/=.sepwork")));
clean(root);
};
@test fn compile_artifact_naming() void = {
let root: str = fresh();
let pdir: str = strings.concat(root, "/pkg");