driver: implement post-target run argv semantics
This commit is contained in:
@@ -25131,3 +25131,307 @@ fn documentationclean(root: str) void = {
|
||||
&& !directoryhasfragment(root, ".request"));
|
||||
clean(root);
|
||||
};
|
||||
|
||||
// An explicit run target ends driver-option parsing. The child receives every
|
||||
// later string unchanged, including spellings that would have been options at
|
||||
// the driver front; argv[0] remains WW's private PID-bearing executable path.
|
||||
fn runargvexpect(out: *commandout, code: i32, stdout: str, stderr: str,
|
||||
message: str) void = {
|
||||
if (out.termination != exec.termination.EXIT || out.code != code
|
||||
|| !same(out.stdout, stdout) || !same(out.stderr, stderr)) {
|
||||
abort(message);
|
||||
};
|
||||
};
|
||||
|
||||
fn runargvrequire(ok: bool, message: str) void = {
|
||||
if (!ok) { abort(message); };
|
||||
};
|
||||
|
||||
fn runargvprivateclean(record: str, message: str) void = {
|
||||
let path: str = readfile(record);
|
||||
runargvrequire(path.len != 0 && !os.exists(path)
|
||||
&& !os.exists(strings.concat(path, ".sepwork")), message);
|
||||
};
|
||||
|
||||
fn runargvnormalizedstderr(stderr: str) str = {
|
||||
let prefix: str = "/tmp/ww_run_";
|
||||
let begin: i32 = pos(stderr, prefix);
|
||||
runargvrequire(begin >= 0, "run argv: missing private diagnostic path");
|
||||
let digits: i32 = begin + prefix.len;
|
||||
let end: i32 = digits;
|
||||
for (end < stderr.len && stderr[end] >= '0' && stderr[end] <= '9') {
|
||||
end += 1;
|
||||
};
|
||||
runargvrequire(end > digits,
|
||||
"run argv: malformed private diagnostic path");
|
||||
return strings.concat(strings.sub(stderr, 0, begin), "$RUN",
|
||||
strings.sub(stderr, end, stderr.len));
|
||||
};
|
||||
|
||||
@test fn run_post_target_arguments_are_program_argv() void = {
|
||||
let root: str = fresh();
|
||||
let source: str = strings.concat(root, "/source");
|
||||
let command: str = strings.concat(source, "/cmd/argv");
|
||||
let library: str = strings.concat(source, "/lib/argv");
|
||||
let tests: str = strings.concat(source, "/proof/tests");
|
||||
mkdirall(command); mkdirall(library); mkdirall(tests);
|
||||
let named: str = strings.concat(source, "/named.ww");
|
||||
let program: str = strings.concat(
|
||||
"package main;\nimport os;\nimport strings;\n",
|
||||
"fn main() i32 = {\n",
|
||||
" let av: []str = os.args(); let i: i32 = 1;\n",
|
||||
" if (av.len > 1) {\n",
|
||||
" let fd: i32 = os.open(av[1], os.flag.WRONLY | os.flag.CREATE |\n",
|
||||
" os.flag.TRUNC, 384i32); if (fd < 0) { return 96; };\n",
|
||||
" os.write(fd, av[0].ptr, av[0].len: u64); os.close(fd);\n",
|
||||
" };\n",
|
||||
" for (i < av.len) {\n",
|
||||
" os.write(os.STDOUT_FILENO, \"<\".ptr, 1u64);\n",
|
||||
" os.write(os.STDOUT_FILENO, av[i].ptr, av[i].len: u64);\n",
|
||||
" os.write(os.STDOUT_FILENO, \">\\n\".ptr, 2u64); i += 1;\n",
|
||||
" };\n",
|
||||
" if (av.len == 3 && strings.compare(av[2], \"exit-zero\") == 0) {\n",
|
||||
" return 0;\n",
|
||||
" }; return 47;\n};\n");
|
||||
writefile(named, program);
|
||||
writefile(strings.concat(command, "/main.ww"), program);
|
||||
writefile(strings.concat(library, "/library.ww"),
|
||||
"package argvlib;\nexport fn value() i32 = { return 1; };\n");
|
||||
writefile(strings.concat(tests, "/tests.ww"),
|
||||
"package argvtests;\nfn value() i32 = { return 1; };\n");
|
||||
writefile(strings.concat(tests, "/tests_test.ww"), strings.concat(
|
||||
"package argvtests;\n",
|
||||
"@test fn ordinary() void = { assert(value() == 1); };\n"));
|
||||
|
||||
let expected: str = strings.concat(
|
||||
"<-I>\n<runtime-include>\n<--I=joined-include>\n",
|
||||
"<-o>\n<runtime-output>\n<--o=joined-output>\n",
|
||||
"<-L>\n<runtime-library-dir>\n<-l>\n<runtime-library>\n",
|
||||
"<-p>\n<ignored>\n<-->\n<later>\n<later.ww>\n<>\n<-I>\n");
|
||||
let suffix: []str = ["-I", "runtime-include", "--I=joined-include",
|
||||
"-o", "runtime-output", "--o=joined-output", "-L",
|
||||
"runtime-library-dir", "-l", "runtime-library", "-p", "ignored",
|
||||
"--", "later", "later.ww", "", "-I"];
|
||||
let stages: []str = ["ww", "ww_ww"];
|
||||
let tags: []str = ["c", "ww"];
|
||||
let targets: []str = [named, "cmd.argv", command];
|
||||
let targettags: []str = ["named", "dotted", "directory"];
|
||||
let out: commandout;
|
||||
let si: i32 = 0;
|
||||
for (si < stages.len) {
|
||||
let ti: i32 = 0;
|
||||
for (ti < targets.len) {
|
||||
let record: str = strings.concat(root, "/private-", tags[si],
|
||||
"-", targettags[ti]);
|
||||
let ignoredoutput: str = strings.concat(root, "/ignored-run-",
|
||||
tags[si], "-", targettags[ti]);
|
||||
let av: []str = [driver(stages[si]), "run", "-I", source,
|
||||
"-o", ignoredoutput, targets[ti]];
|
||||
append(av, record);
|
||||
let ai: i32 = 0;
|
||||
for (ai < suffix.len) { append(av, suffix[ai]); ai += 1; };
|
||||
runcommand(root, strings.concat("run-argv-", tags[si], "-",
|
||||
targettags[ti]), av,
|
||||
(120i64 * (time.second: i64)): time.duration, &out);
|
||||
runargvexpect(&out, 47,
|
||||
strings.concat("<", record, ">\n", expected), "",
|
||||
"run argv: target suffix result");
|
||||
runargvrequire(!os.exists(ignoredoutput)
|
||||
&& !os.exists(strings.concat(ignoredoutput, ".sepwork")),
|
||||
"run argv: run published an output");
|
||||
runargvprivateclean(record, "run argv: runtime-nonzero cleanup");
|
||||
ti += 1;
|
||||
};
|
||||
|
||||
let successrecord: str = strings.concat(root, "/private-success-",
|
||||
tags[si]);
|
||||
let successav: []str = [driver(stages[si]), "run", named,
|
||||
successrecord, "exit-zero"];
|
||||
runcommand(root, strings.concat("run-argv-success-", tags[si]),
|
||||
successav, (120i64 * (time.second: i64)): time.duration, &out);
|
||||
runargvexpect(&out, 0,
|
||||
strings.concat("<", successrecord, ">\n<exit-zero>\n"), "",
|
||||
"run argv: runtime-success result");
|
||||
runargvprivateclean(successrecord, "run argv: runtime-success cleanup");
|
||||
|
||||
// Before an explicit target, the existing option grammar remains in
|
||||
// force. No-operand run retains its implicit current-directory target.
|
||||
let unknownav: []str = [driver(stages[si]), "run", "-p", named];
|
||||
runcommand(root, strings.concat("run-argv-unknown-", tags[si]),
|
||||
unknownav, time.second, &out);
|
||||
runargvexpect(&out, 2, "", "ww run: unknown flag\n",
|
||||
"run argv: pre-target unknown flag");
|
||||
let missingflagav: []str = [driver(stages[si]), "run", "-I"];
|
||||
runcommand(root, strings.concat("run-argv-missing-flag-", tags[si]),
|
||||
missingflagav, time.second, &out);
|
||||
runargvexpect(&out, 2, "", "ww run: -I needs an argument\n",
|
||||
"run argv: pre-target missing option value");
|
||||
let leadingstopav: []str = [driver(stages[si]), "run", "--", named];
|
||||
runcommand(root, strings.concat("run-argv-leading-stop-", tags[si]),
|
||||
leadingstopav, time.second, &out);
|
||||
runargvexpect(&out, 2, "", "ww run: unknown flag\n",
|
||||
"run argv: leading terminator control");
|
||||
let defaultav: []str = [driver(stages[si]), "run"];
|
||||
runcommanddir(root, strings.concat("run-argv-default-", tags[si]),
|
||||
command, defaultav,
|
||||
(120i64 * (time.second: i64)): time.duration, &out);
|
||||
runargvexpect(&out, 47, "", "", "run argv: default-dot control");
|
||||
|
||||
// Target selection and loading diagnose before inert runtime bytes.
|
||||
let missingtarget: []str = [driver(stages[si]), "run", "-I", source,
|
||||
"missing.argv", "-p", "ignored"];
|
||||
runcommand(root, strings.concat("run-argv-missing-target-", tags[si]),
|
||||
missingtarget, time.second, &out);
|
||||
runargvexpect(&out, 1, "",
|
||||
"ww run: cannot find module missing.argv\n",
|
||||
"run argv: missing target precedence");
|
||||
let nonmain: []str = [driver(stages[si]), "run", "-I", source,
|
||||
"lib.argv", "--", "later.ww"];
|
||||
runcommand(root, strings.concat("run-argv-non-main-", tags[si]),
|
||||
nonmain, time.second, &out);
|
||||
runargvexpect(&out, 1, "",
|
||||
"ww: package lib.argv is not a main package\n",
|
||||
"run argv: non-main target precedence");
|
||||
si += 1;
|
||||
};
|
||||
|
||||
// A selected main that reaches the compiler still diagnoses before any
|
||||
// flag-like suffix can be interpreted or any runtime side effect can occur.
|
||||
let broken: str = strings.concat(source, "/broken.ww");
|
||||
let brokenmarker: str = strings.concat(root, "/broken-ran");
|
||||
writefile(broken, strings.concat(
|
||||
"package main;\nimport os;\nfn main() i32 = {\n",
|
||||
" let fd: i32 = os.open(\"", brokenmarker, "\", os.flag.WRONLY |\n",
|
||||
" os.flag.CREATE | os.flag.TRUNC, 384i32);\n",
|
||||
" if (fd >= 0) { os.close(fd); }; return missing;\n};\n"));
|
||||
let producerstderr: str = "";
|
||||
let producerwant: str = strings.concat(
|
||||
"$RUN/main.sepwork/__root.unit.ww:7:41: error: undefined: missing\n",
|
||||
"ww: w6c failed for (root)\n");
|
||||
si = 0;
|
||||
for (si < stages.len) {
|
||||
let brokenav: []str = [driver(stages[si]), "run", broken,
|
||||
"-p", "ignored", "--", brokenmarker];
|
||||
runcommand(root, strings.concat("run-argv-producer-", tags[si]),
|
||||
brokenav, (120i64 * (time.second: i64)): time.duration, &out);
|
||||
runargvrequire(out.termination == exec.termination.EXIT
|
||||
&& out.code == 1 && out.stdout.len == 0
|
||||
&& occurrences(out.stderr, "undefined: missing") == 1
|
||||
&& occurrences(out.stderr, "ww: w6c failed for (root)") == 1
|
||||
&& pos(out.stderr, "undefined: missing")
|
||||
< pos(out.stderr, "ww: w6c failed for (root)")
|
||||
&& !has(out.stderr, "unknown flag") && !os.exists(brokenmarker),
|
||||
"run argv: producer failure precedence");
|
||||
let normalized: str = runargvnormalizedstderr(out.stderr);
|
||||
runargvrequire(same(normalized, producerwant),
|
||||
"run argv: exact producer diagnostic");
|
||||
if (si == 0) { producerstderr = strings.dup(normalized); }
|
||||
else {
|
||||
runargvrequire(same(producerstderr, normalized),
|
||||
"run argv: producer diagnostic stage parity");
|
||||
};
|
||||
si += 1;
|
||||
};
|
||||
|
||||
// Runtime suffixes are request-local even while the two fronts execute
|
||||
// concurrently and share their source closure.
|
||||
let crecord: str = strings.concat(root, "/private-parallel-c");
|
||||
let wrecord: str = strings.concat(root, "/private-parallel-ww");
|
||||
let cav: []str = [driver("ww"), "run", named, crecord,
|
||||
"c-only", "", "-I"];
|
||||
let wav: []str = [driver("ww_ww"), "run", "-I", source, command,
|
||||
wrecord, "ww-only", "--", "tail.ww"];
|
||||
let cc: exec.command;
|
||||
cc.path = cav[0]; cc.argv = cav; cc.env = os.getenvs(); cc.dir = repo();
|
||||
cc.stdoutpath = strings.concat(root, "/run-argv-parallel-c.stdout");
|
||||
cc.stderrpath = strings.concat(root, "/run-argv-parallel-c.stderr");
|
||||
cc.deadline = time.add(time.now(time.clock.monotonic),
|
||||
(120i64 * (time.second: i64)): time.duration);
|
||||
cc.grace = (100i64 * (time.millisecond: i64)): time.duration;
|
||||
let wc: exec.command;
|
||||
wc.path = wav[0]; wc.argv = wav; wc.env = os.getenvs(); wc.dir = repo();
|
||||
wc.stdoutpath = strings.concat(root, "/run-argv-parallel-ww.stdout");
|
||||
wc.stderrpath = strings.concat(root, "/run-argv-parallel-ww.stderr");
|
||||
wc.deadline = time.add(time.now(time.clock.monotonic),
|
||||
(120i64 * (time.second: i64)): time.duration);
|
||||
wc.grace = (100i64 * (time.millisecond: i64)): time.duration;
|
||||
let cp: exec.process;
|
||||
let wp: exec.process;
|
||||
exec.start(&cp, &cc); exec.start(&wp, &wc);
|
||||
let cdone: bool = false;
|
||||
let wdone: bool = false;
|
||||
for (!cdone || !wdone) {
|
||||
if (!cdone) { cdone = exec.poll(&cp); };
|
||||
if (!wdone) { wdone = exec.poll(&wp); };
|
||||
if (!cdone || !wdone) {
|
||||
time.sleep(time.millisecond, time.clock.monotonic);
|
||||
};
|
||||
};
|
||||
runargvrequire(cp.result.errno == 0 && cp.result.cleanuperrno == 0
|
||||
&& cp.result.termination == exec.termination.EXIT && cp.result.code == 47
|
||||
&& wp.result.errno == 0 && wp.result.cleanuperrno == 0
|
||||
&& wp.result.termination == exec.termination.EXIT && wp.result.code == 47
|
||||
&& same(readfile(cc.stdoutpath), strings.concat("<", crecord,
|
||||
">\n<c-only>\n<>\n<-I>\n"))
|
||||
&& readfile(cc.stderrpath).len == 0
|
||||
&& same(readfile(wc.stdoutpath), strings.concat("<", wrecord,
|
||||
">\n<ww-only>\n<-->\n<tail.ww>\n"))
|
||||
&& readfile(wc.stderrpath).len == 0,
|
||||
"run argv: concurrent isolation");
|
||||
runargvprivateclean(crecord, "run argv: parallel C cleanup");
|
||||
runargvprivateclean(wrecord, "run argv: parallel WW cleanup");
|
||||
|
||||
// Run argv never changes build/test products or persistent action bytes.
|
||||
let cbuildwork: str = strings.concat(root, "/build-work-c");
|
||||
let wbuildwork: str = strings.concat(root, "/build-work-ww");
|
||||
let cbuildout: str = strings.concat(root, "/build-output-c");
|
||||
let wbuildout: str = strings.concat(root, "/build-output-ww");
|
||||
mkdirall(cbuildwork); mkdirall(wbuildwork);
|
||||
let cbuildav: []str = [driver("ww"), "build", "-w", cbuildwork,
|
||||
"-o", cbuildout, named];
|
||||
let wbuildav: []str = [driver("ww_ww"), "build", "-w", wbuildwork,
|
||||
"-o", wbuildout, named];
|
||||
runcommand(root, "run-argv-build-c", cbuildav,
|
||||
(120i64 * (time.second: i64)): time.duration, &out);
|
||||
runargvexpect(&out, 0, "", "", "run argv: C build control");
|
||||
runcommand(root, "run-argv-build-ww", wbuildav,
|
||||
(120i64 * (time.second: i64)): time.duration, &out);
|
||||
runargvexpect(&out, 0, "", "", "run argv: WW build control");
|
||||
runargvrequire(same(readfile(cbuildout), readfile(wbuildout))
|
||||
&& same(wrongsuffixsemantictreesnapshot(cbuildwork),
|
||||
wrongsuffixsemantictreesnapshot(wbuildwork)),
|
||||
"run argv: build artifact parity");
|
||||
let ctestwork: str = strings.concat(root, "/test-work-c");
|
||||
let wtestwork: str = strings.concat(root, "/test-work-ww");
|
||||
let ctestout: str = strings.concat(root, "/test-output-c");
|
||||
let wtestout: str = strings.concat(root, "/test-output-ww");
|
||||
mkdirall(ctestwork); mkdirall(wtestwork);
|
||||
let ctestav: []str = [driver("ww"), "test", "-c", "-w", ctestwork,
|
||||
"-I", source, "-o", ctestout, "proof.tests"];
|
||||
let wtestav: []str = [driver("ww_ww"), "test", "-c", "-w", wtestwork,
|
||||
"-I", source, "-o", wtestout, "proof.tests"];
|
||||
runcommand(root, "run-argv-test-c", ctestav,
|
||||
(120i64 * (time.second: i64)): time.duration, &out);
|
||||
runargvexpect(&out, 0, "", "", "run argv: C test control");
|
||||
runcommand(root, "run-argv-test-ww", wtestav,
|
||||
(120i64 * (time.second: i64)): time.duration, &out);
|
||||
runargvexpect(&out, 0, "", "", "run argv: WW test control");
|
||||
runargvrequire(same(readfile(ctestout), readfile(wtestout))
|
||||
&& same(wrongsuffixsemantictreesnapshot(ctestwork),
|
||||
wrongsuffixsemantictreesnapshot(wtestwork)),
|
||||
"run argv: test artifact parity");
|
||||
|
||||
runargvrequire(!wrongsuffixpathfragment(root, ".new")
|
||||
&& !wrongsuffixpathfragment(root, ".old")
|
||||
&& !wrongsuffixpathfragment(root, ".wwtxn.")
|
||||
&& !wrongsuffixpathfragment(root, ".install")
|
||||
&& !wrongsuffixpathfragment(root, ".sepwork")
|
||||
&& !wrongsuffixpathfragment(root, ".stage")
|
||||
&& !wrongsuffixpathfragment(root, ".capture")
|
||||
&& !wrongsuffixpathfragment(root, ".result")
|
||||
&& !wrongsuffixpathfragment(root, ".request")
|
||||
&& !wrongsuffixpathfragment(root, ".descriptor")
|
||||
&& !wrongsuffixpathfragment(root, ".child"),
|
||||
"run argv: normal residue");
|
||||
clean(root);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user