run: reject .ww directories as named sources

This commit is contained in:
2026-08-23 01:07:07 +09:00
parent ca0e533c13
commit 59fc76964c
6 changed files with 612 additions and 20 deletions

View File

@@ -1466,8 +1466,8 @@ fn cwdwritedata(dir: str, label: str) void = {
};
// A visible raw test-source build observes only its initial package/import
// header and then omits it. A .ww-spelled directory, including a symlink with
// a _test.ww spelling, instead enters the ordinary directory-package route.
// header and then omits it. Build and test retain their directory-package
// front door, while run's named-source front rejects a .ww-spelled directory.
// Neither classification may redefine package or dotted-import identity.
@test fn named_test_sources_are_header_loaded_then_omitted() void = {
let root: str = fresh();
@@ -1593,6 +1593,8 @@ fn cwdwritedata(dir: str, label: str) void = {
let linkersignaldiag: str = "";
let directorytestrunout: str = "";
let directorytestrunerr: str = "";
let directoryrunout: str = "";
let directoryrunerr: str = "";
let directorytestrunartifacts: str = "";
let directorytestbin: str = "";
let directorytestcompileartifacts: str = "";
@@ -2202,21 +2204,25 @@ fn cwdwritedata(dir: str, label: str) void = {
if (si == 0) { directorytestasm = strings.dup(out.stderr); }
else { assert(same(directorytestasm, out.stderr)); };
// Run was not changed by the build-only dispatcher fix. Cstage follows
// the selected directory and executes it; WWstage retains its exact
// pre-existing raw-source failure for the same requested symlink.
// Run's named-source front rejects the requested _test.ww spelling
// before directory loading, tool production, or runtime execution.
let directoryrunav: []str = [driver(stages[si]), "run", "-I", include,
dirlink];
runcommandenv(root, strings.concat("testonly-directory-run-", tags[si]),
directoryrunav, env,
(60i64 * (time.second: i64)): time.duration, &out);
let directoryrundiag: str = strings.concat(
"ww: cannot run *_test.ww files (", dirlink, ")\n");
expectexit(&out, 1);
assert(out.stdout.len == 0 && same(out.stderr, directoryrundiag)
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
&& readfile(ltrace).len == 0);
if (si == 0) {
expectexit(&out, 5);
assert(out.stdout.len == 0 && out.stderr.len == 0);
directoryrunout = strings.dup(out.stdout);
directoryrunerr = strings.dup(out.stderr);
} else {
expectexit(&out, 1);
assert(out.stdout.len == 0
&& same(out.stderr, "ww: cannot read source\n"));
assert(same(directoryrunout, out.stdout)
&& same(directoryrunerr, out.stderr));
};
rewritefile(dirtargettest, dirtargettestexcluded);
@@ -2420,6 +2426,396 @@ fn cwdwritedata(dir: str, label: str) void = {
clean(root);
};
fn expectrundirectoryrejection(root: str, name: str, av: []str, env: []str,
pidrecord: str, ctrace: str, atrace: str, ltrace: str, want: str) void = {
rewritefile(pidrecord, "");
rewritefile(ctrace, "");
rewritefile(atrace, "");
rewritefile(ltrace, "");
let out: commandout;
runcommandenv(root, name, av, env,
(60i64 * (time.second: i64)): time.duration, &out);
let ownedrun: str = readfile(pidrecord);
let ownedmarker: str = strings.concat(ownedrun, "/observer-marker");
let ownedvalid: bool = ownedrun.len > 12
&& strings.hasprefix(ownedrun, "/tmp/ww_run_");
let observed: bool = out.termination == exec.termination.EXIT
&& out.code == 1 && out.stdout.len == 0 && same(out.stderr, want)
&& ownedvalid && os.exists(ownedrun)
&& same(readfile(ownedmarker), "observer-owned\n")
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
&& readfile(ltrace).len == 0;
let markerclean: i32 = -1;
let dirclean: i32 = -1;
if (ownedvalid) {
markerclean = os.remove(ownedmarker);
dirclean = os.rmdir(ownedrun);
};
assert(observed && markerclean == 0 && dirclean == 0
&& !os.exists(ownedrun));
};
// Run's named-source spelling owns the directory rejection. The physical
// directory remains only loader metadata for the distinct build and test
// fronts, and rejected source contents cannot create package or import state.
@test fn run_dot_ww_directories_are_named_sources() void = {
let root: str = fresh();
let source: str = strings.concat(root, "/source");
let include: str = strings.concat(root, "/include");
let dependency: str = strings.concat(include, "/domain/value");
let logical: str = strings.concat(include, "/domain/logical");
let ordinary: str = strings.concat(source, "/ordinary.ww");
let ordinarylink: str = strings.concat(source, "/requested.ww");
let testdirectory: str = strings.concat(source, "/direct_test.ww");
let testlink: str = strings.concat(source, "/requested_test.ww");
let hidden: str = strings.concat(source, "/.hidden.ww");
let private: str = strings.concat(source, "/_hidden.ww");
let malformed: str = strings.concat(source, "/malformed.ww");
let missingimport: str = strings.concat(source, "/missing-import.ww");
let nonsuffix: str = strings.concat(source, "/directory-control");
let nonsuffixlink: str = strings.concat(source, "/directory-link");
let regular: str = strings.concat(source, "/regular.ww");
let rawtest: str = strings.concat(source, "/raw_control_test.ww");
let prior: str = strings.concat(root, "/prior-public-output");
mkdirall(dependency); mkdirall(logical); mkdirall(ordinary);
mkdirall(testdirectory); mkdirall(hidden); mkdirall(private);
mkdirall(malformed); mkdirall(missingimport); mkdirall(nonsuffix);
writefile(strings.concat(dependency, "/value.ww"), strings.concat(
"package value;\n",
"export fn number() i32 = { return 37; };\n"));
writefile(strings.concat(ordinary, "/main.ww"), strings.concat(
"package main;\nimport domain.value;\n",
"fn main() i32 = { return value.number(); };\n"));
writefile(strings.concat(testdirectory, "/main.ww"),
"package main;\nfn main() i32 = { return 47; };\n");
writefile(strings.concat(hidden, "/main.ww"),
"package main;\nfn main() i32 = { return 48; };\n");
writefile(strings.concat(private, "/main.ww"),
"package main;\nfn main() i32 = { return 49; };\n");
writefile(strings.concat(malformed, "/main.ww"),
"this directory has no package clause\n");
writefile(strings.concat(missingimport, "/main.ww"), strings.concat(
"package main;\nimport absent.before.run.classification;\n",
"fn main() i32 = { return 50; };\n"));
writefile(strings.concat(nonsuffix, "/main.ww"),
"package main;\nfn main() i32 = { return 43; };\n");
writefile(regular,
"package main;\nfn main() i32 = { return 39; };\n");
writefile(strings.concat(logical, "/main.ww"),
"package main;\nfn main() i32 = { return 41; };\n");
writefile(rawtest, strings.concat(
"package runroute;\n",
"@test fn raw_control() void = { assert(true); };\n"));
writefile(prior, "prior-public-bytes\n");
assert(os.symlink(ordinary, ordinarylink) == 0);
assert(os.symlink(testdirectory, testlink) == 0);
assert(os.symlink(nonsuffix, nonsuffixlink) == 0);
let driverwrapper: str = strings.concat(root, "/driver-wrapper.sh");
let compilerwrapper: str = strings.concat(root, "/compiler-wrapper.sh");
let assemblerwrapper: str = strings.concat(root, "/assembler-wrapper.sh");
let linkerwrapper: str = strings.concat(root, "/linker-wrapper.sh");
writeexecutable(driverwrapper, strings.concat(
"#!/bin/sh\nowned=/tmp/ww_run_$$\n",
"printf '%s' \"$owned\" > \"$WW_RUN_PID_RECORD\"\n",
"if test \"$WW_RUN_PREOCCUPY\" = 1; then\n",
" mkdir \"$owned\" || exit 124\n",
" printf 'observer-owned\\n' > \"$owned/observer-marker\"\n",
"fi\n",
"exec \"$WW_RUN_REAL_DRIVER\" \"$@\"\n"));
writeexecutable(compilerwrapper, strings.concat(
"#!/bin/sh\nprintf 'compile\\n' >> \"$WW_RUN_CTRACE\"\n",
"exec \"$WW_RUN_REAL_C\" \"$@\"\n"));
writeexecutable(assemblerwrapper, strings.concat(
"#!/bin/sh\nprintf 'assemble\\n' >> \"$WW_RUN_ATRACE\"\n",
"exec \"$WW_RUN_REAL_A\" \"$@\"\n"));
writeexecutable(linkerwrapper, strings.concat(
"#!/bin/sh\nprintf 'link\\n' >> \"$WW_RUN_LTRACE\"\n",
"exec \"$WW_RUN_REAL_L\" \"$@\"\n"));
let stages: []str = ["ww", "ww_ww"];
let compilers: []str = ["w6c", "w6c_ww"];
let assemblers: []str = ["w6a", "w6a_ww"];
let linkers: []str = ["w6l", "w6l_ww"];
let tags: []str = ["c", "ww"];
let environments: [][]str = alloc([], 2u64)!;
let rejectenvironments: [][]str = alloc([], 2u64)!;
let pidrecords: []str = alloc([], 2u64)!;
let ctraces: []str = alloc([], 2u64)!;
let atraces: []str = alloc([], 2u64)!;
let ltraces: []str = alloc([], 2u64)!;
let inherited: []str = os.getenvs();
let si: i32 = 0;
for (si < stages.len) {
let pidrecord: str = strings.concat(root, "/driver-pid-", tags[si]);
let ctrace: str = strings.concat(root, "/compiler-trace-", tags[si]);
let atrace: str = strings.concat(root, "/assembler-trace-", tags[si]);
let ltrace: str = strings.concat(root, "/linker-trace-", tags[si]);
writefile(pidrecord, ""); writefile(ctrace, "");
writefile(atrace, ""); writefile(ltrace, "");
append(pidrecords, pidrecord); append(ctraces, ctrace);
append(atraces, atrace); append(ltraces, ltrace);
let env: []str = alloc([], (inherited.len + 11): u64)!;
let ei: i32 = 0;
for (ei < inherited.len) {
if (!strings.hasprefix(inherited[ei], "WW_W6C=")
&& !strings.hasprefix(inherited[ei], "WW_W6A=")
&& !strings.hasprefix(inherited[ei], "WW_W6L=")
&& !strings.hasprefix(inherited[ei], "WW_RUN_")) {
append(env, inherited[ei]);
};
ei += 1;
};
append(env, strings.concat("WW_RUN_REAL_DRIVER=", driver(stages[si])));
append(env, strings.concat("WW_RUN_PID_RECORD=", pidrecord));
append(env, strings.concat("WW_W6C=", compilerwrapper));
append(env, strings.concat("WW_W6A=", assemblerwrapper));
append(env, strings.concat("WW_W6L=", linkerwrapper));
append(env, strings.concat("WW_RUN_CTRACE=", ctrace));
append(env, strings.concat("WW_RUN_ATRACE=", atrace));
append(env, strings.concat("WW_RUN_LTRACE=", ltrace));
append(env, strings.concat("WW_RUN_REAL_C=", driver(compilers[si])));
append(env, strings.concat("WW_RUN_REAL_A=", driver(assemblers[si])));
append(env, strings.concat("WW_RUN_REAL_L=", driver(linkers[si])));
append(environments, env);
let rejectenv: []str = alloc([], (env.len + 1): u64)!;
let ri: i32 = 0;
for (ri < env.len) { append(rejectenv, env[ri]); ri += 1; };
append(rejectenv, "WW_RUN_PREOCCUPY=1");
append(rejectenvironments, rejectenv);
si += 1;
};
let selected: []str = [ordinary, ordinarylink, testdirectory, testlink,
hidden, private, malformed, missingimport];
let selectedlabels: []str = ["direct", "symlink", "direct-test",
"symlink-test", "dot-hidden", "underscore-hidden", "malformed",
"missing-import"];
let selectedtest: []bool = [false, false, true, true, false, false,
false, false];
let sourcebefore: str = strings.dup(treesnapshot(source));
si = 0;
for (si < stages.len) {
let ri: i32 = 0;
for (ri < selected.len) {
let want: str;
if (selectedtest[ri]) {
want = strings.concat("ww: cannot run *_test.ww files (",
selected[ri], ")\n");
} else {
want = strings.concat(selected[ri],
" is a directory, should be a WW file\n");
};
let av: []str = [driverwrapper, "run", "-I", include,
selected[ri]];
expectrundirectoryrejection(root, strings.concat("run-reject-",
tags[si], "-", selectedlabels[ri]), av, rejectenvironments[si],
pidrecords[si], ctraces[si], atraces[si], ltraces[si], want);
ri += 1;
};
let boundaryav: []str = [driverwrapper, "run", "-I", include,
ordinarylink, "-I", "program-argument"];
let boundarywant: str = strings.concat(ordinarylink,
" is a directory, should be a WW file\n");
expectrundirectoryrejection(root, strings.concat("run-boundary-",
tags[si]), boundaryav, rejectenvironments[si], pidrecords[si],
ctraces[si], atraces[si], ltraces[si], boundarywant);
si += 1;
};
assert(same(sourcebefore, treesnapshot(source))
&& same(readfile(prior), "prior-public-bytes\n")
&& !directoryhasfragment(root, ".sepwork")
&& !directoryhasfragment(root, ".wwtxn.")
&& !directoryhasfragment(root, ".capture")
&& !directoryhasfragment(root, ".result")
&& !directoryhasfragment(root, ".request"));
// Concurrent preflight rejections leave independently preoccupied exact
// run paths and diagnostics untouched.
let pi: i32 = 0;
for (pi < stages.len) {
rewritefile(pidrecords[pi], ""); rewritefile(ctraces[pi], "");
rewritefile(atraces[pi], ""); rewritefile(ltraces[pi], "");
pi += 1;
};
let cav: []str = [driverwrapper, "run", ordinary];
let wav: []str = [driverwrapper, "run", ordinarylink];
let cc: exec.command;
cc.path = cav[0]; cc.argv = cav;
cc.env = rejectenvironments[0]; cc.dir = repo();
cc.stdoutpath = strings.concat(root, "/parallel-c.stdout");
cc.stderrpath = strings.concat(root, "/parallel-c.stderr");
cc.deadline = time.add(time.now(time.clock.monotonic),
(60i64 * (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 = rejectenvironments[1]; wc.dir = repo();
wc.stdoutpath = strings.concat(root, "/parallel-ww.stdout");
wc.stderrpath = strings.concat(root, "/parallel-ww.stderr");
wc.deadline = time.add(time.now(time.clock.monotonic),
(60i64 * (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);
};
};
let cownedrun: str = readfile(pidrecords[0]);
let wownedrun: str = readfile(pidrecords[1]);
let cownedmarker: str = strings.concat(cownedrun, "/observer-marker");
let wownedmarker: str = strings.concat(wownedrun, "/observer-marker");
let cownedvalid: bool = cownedrun.len > 12
&& strings.hasprefix(cownedrun, "/tmp/ww_run_");
let wownedvalid: bool = wownedrun.len > 12
&& strings.hasprefix(wownedrun, "/tmp/ww_run_");
let parallelobserved: bool = cp.result.errno == 0
&& cp.result.cleanuperrno == 0
&& cp.result.termination == exec.termination.EXIT && cp.result.code == 1
&& wp.result.errno == 0 && wp.result.cleanuperrno == 0
&& wp.result.termination == exec.termination.EXIT && wp.result.code == 1
&& readfile(cc.stdoutpath).len == 0 && readfile(wc.stdoutpath).len == 0
&& same(readfile(cc.stderrpath), strings.concat(ordinary,
" is a directory, should be a WW file\n"))
&& same(readfile(wc.stderrpath), strings.concat(ordinarylink,
" is a directory, should be a WW file\n"))
&& cownedvalid && wownedvalid
&& os.exists(cownedrun) && os.exists(wownedrun)
&& same(readfile(cownedmarker), "observer-owned\n")
&& same(readfile(wownedmarker), "observer-owned\n")
&& readfile(ctraces[0]).len == 0 && readfile(atraces[0]).len == 0
&& readfile(ltraces[0]).len == 0 && readfile(ctraces[1]).len == 0
&& readfile(atraces[1]).len == 0 && readfile(ltraces[1]).len == 0
&& same(sourcebefore, treesnapshot(source))
&& same(readfile(prior), "prior-public-bytes\n");
let cmarkerclean: i32 = -1;
let cdirclean: i32 = -1;
let wmarkerclean: i32 = -1;
let wdirclean: i32 = -1;
if (cownedvalid) {
cmarkerclean = os.remove(cownedmarker);
cdirclean = os.rmdir(cownedrun);
};
if (wownedvalid) {
wmarkerclean = os.remove(wownedmarker);
wdirclean = os.rmdir(wownedrun);
};
assert(parallelobserved && cmarkerclean == 0 && cdirclean == 0
&& wmarkerclean == 0 && wdirclean == 0
&& !os.exists(cownedrun) && !os.exists(wownedrun));
let cbuildbytes: str = "";
let cbuildartifacts: str = "";
let cteststdout: str = "";
let ctestbinary: str = "";
si = 0;
for (si < stages.len) {
let out: commandout;
let trailing: str = strings.concat(ordinary, "/");
let trailingav: []str = [driverwrapper, "run", "-I", include, trailing];
runcommandenv(root, strings.concat("run-trailing-", tags[si]), trailingav,
environments[si], (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 37);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& !os.exists(readfile(pidrecords[si])));
let nonwwav: []str = [driverwrapper, "run", nonsuffixlink];
runcommandenv(root, strings.concat("run-non-ww-", tags[si]), nonwwav,
environments[si], (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 43);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& !os.exists(readfile(pidrecords[si])));
let regularav: []str = [driverwrapper, "run", regular];
runcommandenv(root, strings.concat("run-regular-", tags[si]), regularav,
environments[si], (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 39);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& !os.exists(readfile(pidrecords[si])));
let logicalav: []str = [driverwrapper, "run", "-I", include,
"domain.logical"];
runcommandenv(root, strings.concat("run-logical-", tags[si]), logicalav,
environments[si], (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 41);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& !os.exists(readfile(pidrecords[si])));
let buildwork: str = strings.concat(root, "/build-work-", tags[si]);
let buildout: str = strings.concat(root, "/build-output-", tags[si]);
mkdirall(buildwork);
let buildav: []str = [driverwrapper, "build", "-w", buildwork,
"-I", include, "-o", buildout, ordinarylink];
runcommandenv(root, strings.concat("build-same-spelling-", tags[si]),
buildav, environments[si],
(60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0 && os.exists(buildout));
let buildbytes: str = strings.dup(readfile(buildout));
let buildartifacts: str = strings.dup(artifacttreesnapshot(buildwork));
if (si == 0) {
cbuildbytes = strings.dup(buildbytes);
cbuildartifacts = strings.dup(buildartifacts);
} else {
assert(same(cbuildbytes, buildbytes)
&& same(cbuildartifacts, buildartifacts));
};
let builtav: []str = [buildout];
runcommand(root, strings.concat("built-control-", tags[si]), builtav,
time.second, &out);
expectexit(&out, 37);
let testwork: str = strings.concat(root, "/test-work-", tags[si]);
mkdirall(testwork);
let testav: []str = [driverwrapper, "test", "-w", testwork, rawtest];
runcommandenv(root, strings.concat("raw-test-control-", tags[si]), testav,
environments[si], (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stderr.len == 0 && has(out.stdout, "raw_control ... ok\n"));
if (si == 0) { cteststdout = strings.dup(out.stdout); }
else { assert(same(cteststdout, out.stdout)); };
let testbinary: str = strings.concat(root, "/test-binary-", tags[si]);
let compileav: []str = [driverwrapper, "test", "-c", "-o",
testbinary, rawtest];
runcommandenv(root, strings.concat("raw-test-compile-", tags[si]),
compileav, environments[si],
(60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0 && os.exists(testbinary));
if (si == 0) { ctestbinary = strings.dup(readfile(testbinary)); }
else { assert(same(ctestbinary, readfile(testbinary))); };
let assemblywork: str = strings.concat(root, "/assembly-work-", tags[si]);
let assemblyout: str = strings.concat(root, "/assembly-output-", tags[si]);
mkdirall(assemblywork);
rewritefile(ctraces[si], ""); rewritefile(atraces[si], "");
rewritefile(ltraces[si], "");
let assemblyav: []str = [driverwrapper, "test", "-S", "-w",
assemblywork, "-o", assemblyout, ordinarylink];
runcommandenv(root, strings.concat("directory-test-assembly-", tags[si]),
assemblyav, environments[si],
(60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 2);
assert(out.stdout.len == 0
&& same(out.stderr, "ww test: -S needs a single test file\n")
&& !os.exists(assemblyout) && directoryisempty(assemblywork)
&& readfile(ctraces[si]).len == 0
&& readfile(atraces[si]).len == 0
&& readfile(ltraces[si]).len == 0);
si += 1;
};
assert(!directoryhasnew(root) && !directoryhasfragment(root, ".wwtxn.")
&& !directoryhasfragment(root, ".install")
&& !directoryhasfragment(root, ".capture")
&& !directoryhasfragment(root, ".result")
&& !directoryhasfragment(root, ".request"));
clean(root);
};
// A source file has one contiguous import section immediately after its
// package clause. The parser may continue after a late import for recovery,
// but the imports-only loader and the full compiler parser both reject it.