build: omit named test source operands
This commit is contained in:
@@ -705,6 +705,136 @@ fn directoryisempty(path: str) bool = {
|
||||
return true;
|
||||
};
|
||||
|
||||
fn snapshotu64(out: *[]u8, value: u64) void = {
|
||||
let v: u64 = value;
|
||||
let i: i32 = 0;
|
||||
for (i < 8) {
|
||||
append(*out, (v & 255u64): u8);
|
||||
v >>= 8u64;
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
|
||||
fn snapshotbytes(out: *[]u8, value: str) void = {
|
||||
snapshotu64(out, value.len: u64);
|
||||
let i: i32 = 0;
|
||||
for (i < value.len) { append(*out, value[i]); i += 1; };
|
||||
};
|
||||
|
||||
fn treepaths(root: str, relative: str, paths: *[]str) void = {
|
||||
let current: str = root;
|
||||
if (relative.len != 0) { current = strings.concat(root, "/", relative); };
|
||||
let fd: i32 = os.open(current, os.flag.RDONLY, 0i32);
|
||||
assert(fd >= 0);
|
||||
let buf: []u8 = alloc([], 16384u64)!;
|
||||
buf.len = 16384;
|
||||
let n: i64 = os.getdents64(fd, buf.ptr, buf.len: u64);
|
||||
for (n > 0i64) {
|
||||
let off: i32 = 0;
|
||||
for (off < n: i32) {
|
||||
let reclen: i32 = (buf[off + 16]: i32)
|
||||
+ ((buf[off + 17]: i32) * 256);
|
||||
assert(reclen >= 20 && off + reclen <= n: i32);
|
||||
let len: i32 = 0;
|
||||
for (buf[off + 19 + len] != 0u8) { len += 1; };
|
||||
let dot: bool = len == 1 && buf[off + 19] == '.';
|
||||
let dotdot: bool = len == 2 && buf[off + 19] == '.'
|
||||
&& buf[off + 20] == '.';
|
||||
if (!dot && !dotdot) {
|
||||
let name: str;
|
||||
name.ptr = buf.ptr + (off + 19): u64;
|
||||
name.len = len;
|
||||
let child: str = strings.dup(name);
|
||||
if (relative.len != 0) {
|
||||
child = strings.concat(relative, "/", child);
|
||||
};
|
||||
append(*paths, child);
|
||||
let full: str = strings.concat(root, "/", child);
|
||||
let fi: os.filestat;
|
||||
match (os.lstat(&fi, full)) {
|
||||
case void => void;
|
||||
case let e: os.oserror => abort("tree lstat failed");
|
||||
};
|
||||
let kind: u32 = (fi.mode: u32) & 61440u32;
|
||||
if (kind == os.mode.DIR: u32) {
|
||||
treepaths(root, child, paths);
|
||||
};
|
||||
};
|
||||
off += reclen;
|
||||
};
|
||||
n = os.getdents64(fd, buf.ptr, buf.len: u64);
|
||||
};
|
||||
assert(n == 0i64);
|
||||
assert(os.close(fd) == 0);
|
||||
};
|
||||
|
||||
// Canonical complete-tree state: relative names, types, modes, and regular or
|
||||
// symlink bytes. Timestamps and inode numbers are observation metadata, not
|
||||
// semantic build state, and are deliberately excluded.
|
||||
fn treesnapshotfiltered(root: str, skipdriver: bool) str = {
|
||||
let paths: []str = alloc([], 16u64)!;
|
||||
append(paths, "");
|
||||
treepaths(root, "", &paths);
|
||||
let i: i32 = 1;
|
||||
for (i < paths.len) {
|
||||
let j: i32 = i;
|
||||
for (j > 0 && strings.compare(paths[j - 1], paths[j]) > 0) {
|
||||
let swap: str = paths[j - 1];
|
||||
paths[j - 1] = paths[j];
|
||||
paths[j] = swap;
|
||||
j -= 1;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
let out: []u8 = alloc([], 4096u64)!;
|
||||
i = 0;
|
||||
for (i < paths.len) {
|
||||
if (skipdriver && same(paths[i], ".wwtool.ww")) {
|
||||
i += 1;
|
||||
continue;
|
||||
};
|
||||
let full: str = root;
|
||||
if (paths[i].len != 0) {
|
||||
full = strings.concat(root, "/", paths[i]);
|
||||
};
|
||||
let fi: os.filestat;
|
||||
match (os.lstat(&fi, full)) {
|
||||
case void => void;
|
||||
case let e: os.oserror => abort("snapshot lstat failed");
|
||||
};
|
||||
snapshotbytes(&out, paths[i]);
|
||||
snapshotu64(&out, fi.mode: u32: u64);
|
||||
let kind: u32 = (fi.mode: u32) & 61440u32;
|
||||
if (kind == os.mode.REG: u32) {
|
||||
snapshotbytes(&out, readfile(full));
|
||||
} else { if (kind == os.mode.LINK: u32) {
|
||||
let target: [4096]u8;
|
||||
let got: i64 = os.readlink(full, &target[0], 4096u64);
|
||||
assert(got >= 0i64 && got <= 4096i64);
|
||||
let value: str;
|
||||
value.ptr = &target[0];
|
||||
value.len = got: i32;
|
||||
snapshotbytes(&out, value);
|
||||
} else {
|
||||
snapshotu64(&out, 0u64);
|
||||
}; };
|
||||
i += 1;
|
||||
};
|
||||
return strings.frombytes(out);
|
||||
};
|
||||
|
||||
fn treesnapshot(root: str) str = {
|
||||
return treesnapshotfiltered(root, false);
|
||||
};
|
||||
|
||||
// The two bootstrap stages intentionally snapshot different driver binaries
|
||||
// in `.wwtool.ww`. Exclude only that producer-provenance file when comparing
|
||||
// semantic artifacts across stages; per-stage preservation uses the complete
|
||||
// snapshot above and therefore still covers it.
|
||||
fn artifacttreesnapshot(root: str) str = {
|
||||
return treesnapshotfiltered(root, true);
|
||||
};
|
||||
|
||||
fn discardedlinkcleaned(marker: str) void = {
|
||||
let output: str = readfile(marker);
|
||||
assert(output.len != 0);
|
||||
@@ -1335,6 +1465,493 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
clean(root);
|
||||
};
|
||||
|
||||
// A visible raw test-source build observes only its initial package/import
|
||||
// header. It must not turn that observation into a package, import, action,
|
||||
// artifact, or work-state identity.
|
||||
@test fn named_test_sources_are_header_loaded_then_omitted() void = {
|
||||
let root: str = fresh();
|
||||
let source: str = strings.concat(root, "/source");
|
||||
let dirsource: str = strings.concat(source, "/directory");
|
||||
let dirtarget: str = strings.concat(source, "/directory-target");
|
||||
mkdirall(source); mkdirall(dirsource); mkdirall(dirtarget);
|
||||
|
||||
let ignored: str = strings.concat(source, "/raw_windows_test.ww");
|
||||
let platform: str = strings.concat(source, "/platform_windows_test.ww");
|
||||
let badpackage: str = strings.concat(source, "/bad_package_test.ww");
|
||||
let badimport: str = strings.concat(source, "/bad_import_test.ww");
|
||||
let bodyutf8: str = strings.concat(source, "/body_utf8_test.ww");
|
||||
let bodybom: str = strings.concat(source, "/body_bom_test.ww");
|
||||
let bodynul: str = strings.concat(source, "/body_nul_test.ww");
|
||||
let badcomment: str = strings.concat(source, "/bad_comment_test.ww");
|
||||
let linktarget: str = strings.concat(source, "/target.ww");
|
||||
let link: str = strings.concat(source, "/requested_test.ww");
|
||||
let dirlink: str = strings.concat(source, "/directory_test.ww");
|
||||
let rawmain: str = strings.concat(source, "/raw.ww");
|
||||
let runsource: str = strings.concat(source, "/run_test.ww");
|
||||
let testroute: str = strings.concat(source, "/route_test.ww");
|
||||
let warmpath: str = strings.concat(source, "/warm.ww");
|
||||
let warmtest: str = strings.concat(source, "/warm_test.ww");
|
||||
let header: str = strings.concat(
|
||||
"package raw_test;\nimport absent.pkg;\n",
|
||||
"this body is deliberately malformed and never reaches a producer {\n",
|
||||
"import late.missing;\n");
|
||||
writefile(ignored, header);
|
||||
writefile(platform, header);
|
||||
writefile(linktarget, header);
|
||||
writefile(badpackage, "this source has no package clause\n");
|
||||
writefile(badimport, "package malformed;\nimport ;\n");
|
||||
putinvalidutf8file(bodyutf8, "package body;\n",
|
||||
"fn deliberately_unparsed(\n", false);
|
||||
writemidbomfile(bodybom, "package body;\n",
|
||||
"fn deliberately_unparsed(\n");
|
||||
writenulfile(bodynul, "package body;\n", "fn body() void = {};\n");
|
||||
writefile(badcomment, "package body;\n/* unterminated");
|
||||
writefile(rawmain, "package main;\nfn main() i32 = { return 4; };\n");
|
||||
writefile(runsource,
|
||||
"package main;\nfn main() i32 = { return 9; };\n");
|
||||
writefile(testroute, strings.concat(
|
||||
"package route;\n",
|
||||
"@test fn still_selected() void = { assert(true); };\n"));
|
||||
writefile(warmpath, "package main;\nfn main() i32 = { return 6; };\n");
|
||||
writefile(strings.concat(dirsource, "/main.ww"),
|
||||
"package main;\nfn main() i32 = { return 3; };\n");
|
||||
writefile(strings.concat(dirtarget, "/main.ww"),
|
||||
"package main;\nfn main() i32 = { return 5; };\n");
|
||||
assert(os.symlink(linktarget, link) == 0);
|
||||
assert(os.symlink(dirtarget, dirlink) == 0);
|
||||
|
||||
let compilerwrapper: str = strings.concat(root, "/testonly-w6c.sh");
|
||||
let assemblerwrapper: str = strings.concat(root, "/testonly-w6a.sh");
|
||||
let linkerwrapper: str = strings.concat(root, "/testonly-w6l.sh");
|
||||
writeexecutable(compilerwrapper, strings.concat(
|
||||
"#!/bin/sh\nprintf 'compile\\n' >> \"$WW_TESTONLY_CTRACE\"\n",
|
||||
"exec \"$WW_TESTONLY_REAL_C\" \"$@\"\n"));
|
||||
writeexecutable(assemblerwrapper, strings.concat(
|
||||
"#!/bin/sh\nprintf 'assemble\\n' >> \"$WW_TESTONLY_ATRACE\"\n",
|
||||
"exec \"$WW_TESTONLY_REAL_A\" \"$@\"\n"));
|
||||
writeexecutable(linkerwrapper, strings.concat(
|
||||
"#!/bin/sh\nprintf 'link\\n' >> \"$WW_TESTONLY_LTRACE\"\n",
|
||||
"exec \"$WW_TESTONLY_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 packagebad: []str = ["", ""];
|
||||
let importbad: []str = ["", ""];
|
||||
let nulbad: []str = ["", ""];
|
||||
let commentbad: []str = ["", ""];
|
||||
let multibad: []str = ["", ""];
|
||||
let environments: [][]str = alloc([], stages.len: u64)!;
|
||||
let tracecs: []str = alloc([], stages.len: u64)!;
|
||||
let traceas: []str = alloc([], stages.len: u64)!;
|
||||
let tracels: []str = alloc([], stages.len: u64)!;
|
||||
let cwarmbin: str = "";
|
||||
let cwarmartifacts: str = "";
|
||||
let ccompiled: str = "";
|
||||
let ctestasm: str = "";
|
||||
let crawbin: str = "";
|
||||
let baseenv: []str = os.getenvs();
|
||||
let si: i32 = 0;
|
||||
for (si < stages.len) {
|
||||
let ctrace: str = strings.concat(root, "/testonly-c-", tags[si]);
|
||||
let atrace: str = strings.concat(root, "/testonly-a-", tags[si]);
|
||||
let ltrace: str = strings.concat(root, "/testonly-l-", tags[si]);
|
||||
writefile(ctrace, ""); writefile(atrace, ""); writefile(ltrace, "");
|
||||
append(tracecs, ctrace); append(traceas, atrace); append(tracels, ltrace);
|
||||
let env: []str = alloc([], (baseenv.len + 10): u64)!;
|
||||
let ei: i32 = 0;
|
||||
for (ei < baseenv.len) {
|
||||
if (!strings.hasprefix(baseenv[ei], "WW_W6C=")
|
||||
&& !strings.hasprefix(baseenv[ei], "WW_W6A=")
|
||||
&& !strings.hasprefix(baseenv[ei], "WW_W6L=")
|
||||
&& !strings.hasprefix(baseenv[ei], "WW_TESTONLY_")) {
|
||||
append(env, baseenv[ei]);
|
||||
};
|
||||
ei += 1;
|
||||
};
|
||||
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_TESTONLY_CTRACE=", ctrace));
|
||||
append(env, strings.concat("WW_TESTONLY_ATRACE=", atrace));
|
||||
append(env, strings.concat("WW_TESTONLY_LTRACE=", ltrace));
|
||||
append(env, strings.concat("WW_TESTONLY_REAL_C=", driver(compilers[si])));
|
||||
append(env, strings.concat("WW_TESTONLY_REAL_A=", driver(assemblers[si])));
|
||||
append(env, strings.concat("WW_TESTONLY_REAL_L=", driver(linkers[si])));
|
||||
append(environments, env);
|
||||
|
||||
let out: commandout;
|
||||
let unused: str = strings.concat(root, "/unused-", tags[si]);
|
||||
let defaultav: []str = [driver(stages[si]), "build", "-w", unused,
|
||||
ignored];
|
||||
runcommandenv(root, strings.concat("testonly-default-", tags[si]),
|
||||
defaultav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
let defaultoutput: str = strings.concat(source, "/raw_windows_test");
|
||||
let defaultsepwork: str = strings.concat(defaultoutput, ".sepwork");
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||
&& !os.exists(unused) && !os.exists(defaultoutput)
|
||||
&& !os.exists(defaultsepwork)
|
||||
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
|
||||
&& readfile(ltrace).len == 0);
|
||||
|
||||
let asmwork: str = strings.concat(root, "/asm-unused-", tags[si]);
|
||||
let asmav: []str = [driver(stages[si]), "build", "-S", "-w", asmwork,
|
||||
ignored];
|
||||
runcommandenv(root, strings.concat("testonly-assembly-", tags[si]),
|
||||
asmav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0 && !os.exists(asmwork)
|
||||
&& !os.exists(defaultoutput) && !os.exists(defaultsepwork)
|
||||
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
|
||||
&& readfile(ltrace).len == 0);
|
||||
|
||||
let nullwork: str = strings.concat(root, "/null-unused-", tags[si]);
|
||||
let nullav: []str = [driver(stages[si]), "build", "-w", nullwork,
|
||||
"-o", "/dev/null", ignored];
|
||||
runcommandenv(root, strings.concat("testonly-null-", tags[si]),
|
||||
nullav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0 && !os.exists(nullwork)
|
||||
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
|
||||
&& readfile(ltrace).len == 0);
|
||||
|
||||
let filework: str = strings.concat(root, "/file-unused-", tags[si]);
|
||||
let fileout: str = strings.concat(root, "/file-output-", tags[si]);
|
||||
let fileav: []str = [driver(stages[si]), "build", "-w", filework,
|
||||
"-o", fileout, ignored];
|
||||
runcommandenv(root, strings.concat("testonly-file-", tags[si]),
|
||||
fileav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, "ww: no packages to build\n")
|
||||
&& !os.exists(filework) && !os.exists(fileout)
|
||||
&& !os.exists(strings.concat(fileout, ".new"))
|
||||
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
|
||||
&& readfile(ltrace).len == 0);
|
||||
let fileasmav: []str = [driver(stages[si]), "build", "-S", "-w",
|
||||
filework, "-o", fileout, ignored];
|
||||
runcommandenv(root, strings.concat("testonly-file-assembly-", tags[si]),
|
||||
fileasmav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, "ww: no packages to build\n")
|
||||
&& !os.exists(filework) && !os.exists(fileout));
|
||||
|
||||
let outputdir: str = strings.concat(root, "/output-dir-", tags[si]);
|
||||
let marker: str = strings.concat(outputdir, "/marker");
|
||||
mkdirall(outputdir); writefile(marker, "caller-owned\n");
|
||||
let dirwork: str = strings.concat(root, "/dir-unused-", tags[si]);
|
||||
let dirav: []str = [driver(stages[si]), "build", "-w", dirwork,
|
||||
"-o", outputdir, ignored];
|
||||
runcommandenv(root, strings.concat("testonly-directory-", tags[si]),
|
||||
dirav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0
|
||||
&& same(out.stderr, "ww: no main packages to build\n")
|
||||
&& same(readfile(marker), "caller-owned\n") && !os.exists(dirwork));
|
||||
let trailingav: []str = [driver(stages[si]), "build", "-w", dirwork,
|
||||
"-o", strings.concat(outputdir, "/"), ignored];
|
||||
runcommandenv(root, strings.concat("testonly-directory-trailing-", tags[si]),
|
||||
trailingav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0
|
||||
&& same(out.stderr, "ww: no main packages to build\n")
|
||||
&& same(readfile(marker), "caller-owned\n") && !os.exists(dirwork)
|
||||
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
|
||||
&& readfile(ltrace).len == 0);
|
||||
|
||||
let badwork: str = strings.concat(root, "/bad-unused-", tags[si]);
|
||||
let badout: str = strings.concat(root, "/bad-output-", tags[si]);
|
||||
let badpackageav: []str = [driver(stages[si]), "build", "-w", badwork,
|
||||
"-o", badout, badpackage];
|
||||
runcommandenv(root, strings.concat("testonly-bad-package-", tags[si]),
|
||||
badpackageav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0
|
||||
&& has(out.stderr, "invalid or missing package clause\n")
|
||||
&& !has(out.stderr, "no packages to build") && !os.exists(badwork)
|
||||
&& !os.exists(badout) && readfile(ctrace).len == 0
|
||||
&& readfile(atrace).len == 0 && readfile(ltrace).len == 0);
|
||||
packagebad[si] = strings.dup(out.stderr);
|
||||
let badimportav: []str = [driver(stages[si]), "build", "-w", badwork,
|
||||
"-o", badout, badimport];
|
||||
runcommandenv(root, strings.concat("testonly-bad-import-", tags[si]),
|
||||
badimportav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
let importwant: str = strings.concat(badimport,
|
||||
":2:8: error: expected identifier, got ;\n");
|
||||
assert(out.stdout.len == 0 && same(out.stderr, importwant)
|
||||
&& !has(out.stderr, "no packages to build") && !os.exists(badwork)
|
||||
&& !os.exists(badout) && readfile(ctrace).len == 0
|
||||
&& readfile(atrace).len == 0 && readfile(ltrace).len == 0);
|
||||
importbad[si] = strings.dup(out.stderr);
|
||||
|
||||
// Pinned readGoInfo discards the first ordinary body stop byte, even
|
||||
// when it starts malformed UTF-8 or a non-leading BOM. Reached NUL and
|
||||
// unterminated header-trivia comments remain loader errors.
|
||||
let utf8av: []str = [driver(stages[si]), "build", "-w", badwork,
|
||||
"-o", badout, bodyutf8];
|
||||
runcommandenv(root, strings.concat("testonly-body-utf8-", tags[si]),
|
||||
utf8av, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0
|
||||
&& same(out.stderr, "ww: no packages to build\n")
|
||||
&& !os.exists(badwork) && !os.exists(badout));
|
||||
let bomav: []str = [driver(stages[si]), "build", "-w", badwork,
|
||||
"-o", badout, bodybom];
|
||||
runcommandenv(root, strings.concat("testonly-body-bom-", tags[si]),
|
||||
bomav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0
|
||||
&& same(out.stderr, "ww: no packages to build\n")
|
||||
&& !os.exists(badwork) && !os.exists(badout));
|
||||
let nulav: []str = [driver(stages[si]), "build", "-w", badwork,
|
||||
"-o", badout, bodynul];
|
||||
runcommandenv(root, strings.concat("testonly-body-nul-", tags[si]),
|
||||
nulav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
let nulwant: str = strings.concat(bodynul,
|
||||
":2:1: error: invalid NUL character\n");
|
||||
assert(out.stdout.len == 0 && same(out.stderr, nulwant)
|
||||
&& !os.exists(badwork) && !os.exists(badout));
|
||||
nulbad[si] = strings.dup(out.stderr);
|
||||
let commentav: []str = [driver(stages[si]), "build", "-w", badwork,
|
||||
"-o", badout, badcomment];
|
||||
runcommandenv(root, strings.concat("testonly-bad-comment-", tags[si]),
|
||||
commentav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
let commentwant: str = strings.concat(badcomment,
|
||||
":2:16: error: unterminated /* comment\n");
|
||||
assert(out.stdout.len == 0 && same(out.stderr, commentwant)
|
||||
&& !os.exists(badwork) && !os.exists(badout)
|
||||
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
|
||||
&& readfile(ltrace).len == 0);
|
||||
commentbad[si] = strings.dup(out.stderr);
|
||||
|
||||
let spellingwork: str = strings.concat(root, "/spelling-unused-", tags[si]);
|
||||
let platformav: []str = [driver(stages[si]), "build", "-w",
|
||||
spellingwork, platform];
|
||||
runcommandenv(root, strings.concat("testonly-platform-", tags[si]),
|
||||
platformav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0 && !os.exists(spellingwork));
|
||||
let linkav: []str = [driver(stages[si]), "build", "-w", spellingwork,
|
||||
link];
|
||||
runcommandenv(root, strings.concat("testonly-requested-link-", tags[si]),
|
||||
linkav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0 && !os.exists(spellingwork)
|
||||
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
|
||||
&& readfile(ltrace).len == 0);
|
||||
|
||||
// A directory target remains a directory request despite a test-looking
|
||||
// requested spelling; this is deliberately the one producer-positive row.
|
||||
let linkdirwork: str = strings.concat(root, "/link-dir-work-", tags[si]);
|
||||
let linkdirout: str = strings.concat(root, "/link-dir-output-", tags[si]);
|
||||
mkdirall(linkdirwork);
|
||||
let linkdirav: []str = [driver(stages[si]), "build", "-w", linkdirwork,
|
||||
"-o", linkdirout, dirlink];
|
||||
runcommandenv(root, strings.concat("testonly-directory-link-", tags[si]),
|
||||
linkdirav, env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
if (si == 0) {
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||
&& os.exists(linkdirout) && readfile(ctrace).len != 0
|
||||
&& readfile(atrace).len != 0 && readfile(ltrace).len != 0);
|
||||
} else {
|
||||
// The selected non-directory classifier must not absorb this
|
||||
// pre-existing WWstage resolution difference into the slice.
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0
|
||||
&& same(out.stderr, "ww: cannot read source\n")
|
||||
&& !os.exists(linkdirout) && readfile(ctrace).len == 0
|
||||
&& readfile(atrace).len == 0 && readfile(ltrace).len == 0);
|
||||
};
|
||||
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||
|
||||
// A valid retained raw source seeds state. Renaming only its requested
|
||||
// basename to test-only must leave all public and private bytes untouched.
|
||||
let warmwork: str = strings.concat(root, "/warm-work-", tags[si]);
|
||||
let warmout: str = strings.concat(root, "/warm-output-", tags[si]);
|
||||
mkdirall(warmwork);
|
||||
let warmav: []str = [driver(stages[si]), "build", "-w", warmwork,
|
||||
"-o", warmout, warmpath];
|
||||
runcommandenv(root, strings.concat("testonly-warm-seed-", tags[si]),
|
||||
warmav, env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0 && os.exists(warmout));
|
||||
let warmbin: str = strings.dup(readfile(warmout));
|
||||
let warmtree: str = strings.dup(treesnapshot(warmwork));
|
||||
if (si == 0) {
|
||||
cwarmbin = strings.dup(warmbin);
|
||||
cwarmartifacts = strings.dup(artifacttreesnapshot(warmwork));
|
||||
} else {
|
||||
assert(same(cwarmbin, warmbin)
|
||||
&& same(cwarmartifacts, artifacttreesnapshot(warmwork)));
|
||||
};
|
||||
assert(os.rename(warmpath, warmtest) == 0);
|
||||
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||
let warmtestav: []str = [driver(stages[si]), "build", "-w", warmwork,
|
||||
"-o", warmout, warmtest];
|
||||
runcommandenv(root, strings.concat("testonly-warm-omit-", tags[si]),
|
||||
warmtestav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, "ww: no packages to build\n")
|
||||
&& same(warmbin, readfile(warmout)) && !os.exists(strings.concat(warmout, ".new"))
|
||||
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
|
||||
&& readfile(ltrace).len == 0 && !directoryhasnew(warmwork)
|
||||
&& !directoryhasfragment(warmwork, ".wwtxn.")
|
||||
&& same(warmtree, treesnapshot(warmwork)));
|
||||
if (si == 1) {
|
||||
assert(same(cwarmbin, readfile(warmout))
|
||||
&& same(cwarmartifacts, artifacttreesnapshot(warmwork)));
|
||||
};
|
||||
assert(os.rename(warmtest, warmpath) == 0);
|
||||
runcommandenv(root, strings.concat("testonly-warm-restored-", tags[si]),
|
||||
warmav, env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||
&& same(warmbin, readfile(warmout)) && !directoryhasnew(warmwork)
|
||||
&& same(warmtree, treesnapshot(warmwork)));
|
||||
if (si == 1) {
|
||||
assert(same(cwarmbin, readfile(warmout))
|
||||
&& same(cwarmartifacts, artifacttreesnapshot(warmwork)));
|
||||
};
|
||||
|
||||
// Test and run keep their existing raw-source semantics, while a visible
|
||||
// non-test raw build and a multi-operand error do not enter this slice.
|
||||
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||
let testwork: str = strings.concat(root, "/route-work-", tags[si]);
|
||||
mkdirall(testwork);
|
||||
let testav: []str = [driver(stages[si]), "test", "-w", testwork,
|
||||
testroute];
|
||||
runcommandenv(root, strings.concat("testonly-raw-test-", tags[si]),
|
||||
testav, env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stderr.len == 0 && has(out.stdout, "still_selected ... ok\n")
|
||||
&& readfile(ctrace).len != 0);
|
||||
let compiled: str = strings.concat(root, "/route-compiled-", tags[si]);
|
||||
let compileav: []str = [driver(stages[si]), "test", "-c", "-o", compiled,
|
||||
testroute];
|
||||
runcommandenv(root, strings.concat("testonly-raw-test-compile-", tags[si]),
|
||||
compileav, env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0 && os.exists(compiled));
|
||||
let compiledbytes: str = strings.dup(readfile(compiled));
|
||||
if (si == 0) { ccompiled = strings.dup(compiledbytes); }
|
||||
else { assert(same(ccompiled, compiledbytes)); };
|
||||
let testasm: str = strings.concat(root, "/route-assembly-", tags[si]);
|
||||
let testasmav: []str = [driver(stages[si]), "test", "-S", "-o", testasm,
|
||||
testroute];
|
||||
runcommandenv(root, strings.concat("testonly-raw-test-assembly-", tags[si]),
|
||||
testasmav, env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
let testasmtree: str = strings.concat(testasm, ".sepwork");
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||
&& os.exists(testasmtree));
|
||||
let testasmstate: str = strings.dup(artifacttreesnapshot(testasmtree));
|
||||
if (si == 0) { ctestasm = strings.dup(testasmstate); }
|
||||
else { assert(same(ctestasm, testasmstate)); };
|
||||
let runav: []str = [driver(stages[si]), "run", runsource];
|
||||
runcommandenv(root, strings.concat("testonly-run-", tags[si]), runav,
|
||||
env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 9);
|
||||
let rawout: str = strings.concat(root, "/raw-output-", tags[si]);
|
||||
let rawav: []str = [driver(stages[si]), "build", "-o", rawout, rawmain];
|
||||
runcommandenv(root, strings.concat("testonly-raw-build-", tags[si]),
|
||||
rawav, env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0 && os.exists(rawout));
|
||||
let rawbytes: str = strings.dup(readfile(rawout));
|
||||
if (si == 0) { crawbin = strings.dup(rawbytes); }
|
||||
else { assert(same(crawbin, rawbytes)); };
|
||||
let multiout: str = strings.concat(root, "/multi-output-", tags[si]);
|
||||
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
|
||||
let multiav: []str = [driver(stages[si]), "build", "-o", multiout,
|
||||
rawmain, ignored];
|
||||
runcommandenv(root, strings.concat("testonly-multiple-", tags[si]),
|
||||
multiav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
let multiwant: str = strings.concat(
|
||||
"wwtest package: ", rawmain,
|
||||
": cannot canonicalize package directory\n",
|
||||
"wwtest package: ", ignored,
|
||||
": cannot canonicalize package directory\n");
|
||||
assert(out.termination == exec.termination.EXIT && out.code != 0
|
||||
&& out.stdout.len == 0 && same(out.stderr, multiwant)
|
||||
&& !os.exists(multiout) && readfile(ctrace).len == 0
|
||||
&& readfile(atrace).len == 0 && readfile(ltrace).len == 0);
|
||||
multibad[si] = strings.dup(out.stderr);
|
||||
|
||||
si += 1;
|
||||
};
|
||||
assert(same(packagebad[0], packagebad[1]));
|
||||
assert(same(importbad[0], importbad[1]));
|
||||
assert(same(nulbad[0], nulbad[1]));
|
||||
assert(same(commentbad[0], commentbad[1]));
|
||||
assert(same(multibad[0], multibad[1]));
|
||||
|
||||
// Two simultaneous omitted requests have no work/output collision and no
|
||||
// producer invocation in either stage.
|
||||
let ti: i32 = 0;
|
||||
for (ti < stages.len) {
|
||||
rewritefile(tracecs[ti], "");
|
||||
rewritefile(traceas[ti], "");
|
||||
rewritefile(tracels[ti], "");
|
||||
ti += 1;
|
||||
};
|
||||
let pcwork: str = strings.concat(root, "/parallel-c-unused");
|
||||
let pwwork: str = strings.concat(root, "/parallel-ww-unused");
|
||||
let pcav: []str = [driver("ww"), "build", "-w", pcwork, ignored];
|
||||
let pwav: []str = [driver("ww_ww"), "build", "-w", pwwork, ignored];
|
||||
let pc: exec.command;
|
||||
pc.path = pcav[0]; pc.argv = pcav; pc.env = environments[0]; pc.dir = repo();
|
||||
pc.stdoutpath = strings.concat(root, "/parallel-c.stdout");
|
||||
pc.stderrpath = strings.concat(root, "/parallel-c.stderr");
|
||||
pc.deadline = time.add(time.now(time.clock.monotonic),
|
||||
(30i64 * (time.second: i64)): time.duration);
|
||||
pc.grace = (100i64 * (time.millisecond: i64)): time.duration;
|
||||
let pw: exec.command;
|
||||
pw.path = pwav[0]; pw.argv = pwav; pw.env = environments[1]; pw.dir = repo();
|
||||
pw.stdoutpath = strings.concat(root, "/parallel-ww.stdout");
|
||||
pw.stderrpath = strings.concat(root, "/parallel-ww.stderr");
|
||||
pw.deadline = time.add(time.now(time.clock.monotonic),
|
||||
(30i64 * (time.second: i64)): time.duration);
|
||||
pw.grace = (100i64 * (time.millisecond: i64)): time.duration;
|
||||
let pcp: exec.process;
|
||||
let pwp: exec.process;
|
||||
exec.start(&pcp, &pc); exec.start(&pwp, &pw);
|
||||
let pcdone: bool = false;
|
||||
let pwdone: bool = false;
|
||||
for (!pcdone || !pwdone) {
|
||||
if (!pcdone) { pcdone = exec.poll(&pcp); };
|
||||
if (!pwdone) { pwdone = exec.poll(&pwp); };
|
||||
if (!pcdone || !pwdone) {
|
||||
time.sleep(time.millisecond, time.clock.monotonic);
|
||||
};
|
||||
};
|
||||
assert(pcp.result.errno == 0 && pcp.result.cleanuperrno == 0
|
||||
&& pcp.result.termination == exec.termination.EXIT && pcp.result.code == 0
|
||||
&& pwp.result.errno == 0 && pwp.result.cleanuperrno == 0
|
||||
&& pwp.result.termination == exec.termination.EXIT && pwp.result.code == 0);
|
||||
assert(readfile(pc.stdoutpath).len == 0 && readfile(pc.stderrpath).len == 0
|
||||
&& readfile(pw.stdoutpath).len == 0 && readfile(pw.stderrpath).len == 0
|
||||
&& !os.exists(pcwork) && !os.exists(pwwork));
|
||||
ti = 0;
|
||||
for (ti < stages.len) {
|
||||
assert(readfile(tracecs[ti]).len == 0
|
||||
&& readfile(traceas[ti]).len == 0
|
||||
&& readfile(tracels[ti]).len == 0);
|
||||
ti += 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.
|
||||
|
||||
@@ -18,10 +18,11 @@ package wwdump_test;
|
||||
// wwdump_ww's exit code is deliberately NOT gated: the diagnostics
|
||||
// land on stderr regardless, and the armed bail flips the exit code —
|
||||
// the line count is the one signal stable across the fold sequence.
|
||||
// #90 sep-feed: `ww build -S` resolves import-bearing fixtures to an
|
||||
// owner unit and direct exports; this raw wwdump-only gate explicitly
|
||||
// composes those artifacts because wwdump has no package-input CLI. The
|
||||
// import-free test/wcc/901_*.ww companions stay raw-fed.
|
||||
// #90 sep-feed: `ww build -S` resolves production fixtures and `ww test
|
||||
// -S` resolves test fixtures to an owner unit and direct exports; this raw
|
||||
// wwdump-only gate explicitly composes those artifacts because wwdump has no
|
||||
// package-input CLI. The import-free test/wcc/901_*.ww companions stay
|
||||
// raw-fed.
|
||||
//
|
||||
// wwdumpgate (#52, F15 c4) — the -c and -r arms gate on parse-stage
|
||||
// errors instead of silently emitting over a broken AST: parse-errored
|
||||
@@ -87,18 +88,20 @@ fn countdiag(stderr: str) i32 = {
|
||||
if (seps[i]) {
|
||||
let stem: str = strings.concat(td, "/unit");
|
||||
let bo: testenv.commandout;
|
||||
let bav: []str = [testenv.driver("ww"), "build", "-S", "-o",
|
||||
let action: str = "build";
|
||||
if (strings.hassuffix(rels[i], "_test.ww")) { action = "test"; };
|
||||
let bav: []str = [testenv.driver("ww"), action, "-S", "-o",
|
||||
stem, src];
|
||||
testenv.runcommand(td, td, "resolve", bav, tmo(), &bo);
|
||||
if (bo.termination != exec.termination.EXIT || bo.code != 0) {
|
||||
fail(classes[i], strings.concat(rels[i],
|
||||
": no resolved sep unit (ww build -S failed)"));
|
||||
": no resolved sep unit (ww build/test -S failed)"));
|
||||
};
|
||||
let work: str = strings.concat(stem, ".sepwork/");
|
||||
let owner: str = strings.concat(work, "__root.unit.ww");
|
||||
if (!testenv.exists(owner)) {
|
||||
fail(classes[i], strings.concat(rels[i],
|
||||
": __root.unit.ww missing after ww build -S"));
|
||||
": __root.unit.ww missing after ww build/test -S"));
|
||||
};
|
||||
let deps: []str = [];
|
||||
if (i == 0) { append(deps, "math.checked"); append(deps, "types"); };
|
||||
|
||||
@@ -100,6 +100,40 @@ imports_to_str(const char *src, int *errs, const char **pkg)
|
||||
return buf;
|
||||
}
|
||||
|
||||
static char *
|
||||
header_to_str(const char *src, size_t len, int *errs, const char **pkg)
|
||||
{
|
||||
Arena *a = newarena();
|
||||
Lex l;
|
||||
Parser p;
|
||||
lexinit(&l, a, "header.ww", src, len);
|
||||
parserinit(&p, a, &l);
|
||||
Node *n = parsepackageheader(&p);
|
||||
*errs = p.errs + l.errs;
|
||||
*pkg = n->module ? strdup(n->module) : NULL;
|
||||
|
||||
char *buf = NULL;
|
||||
size_t outlen = 0;
|
||||
FILE *f = open_memstream(&buf, &outlen);
|
||||
if (f != NULL) {
|
||||
astprint(f, n);
|
||||
fclose(f);
|
||||
}
|
||||
freearena(a);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int
|
||||
header_errors(const char *src)
|
||||
{
|
||||
int errs;
|
||||
const char *pkg;
|
||||
char *got = header_to_str(src, strlen(src), &errs, &pkg);
|
||||
free((void *)pkg);
|
||||
free(got);
|
||||
return errs;
|
||||
}
|
||||
|
||||
static int
|
||||
must_import_shape(const char *src, int imports_only, const char *binding,
|
||||
const char *path, const char *alias)
|
||||
@@ -300,6 +334,119 @@ main(void)
|
||||
free((void *)pkg);
|
||||
free(got);
|
||||
}
|
||||
{
|
||||
const char *src =
|
||||
"package main;\n"
|
||||
"import zed;\n"
|
||||
"import stable alpha.beta;\n"
|
||||
"fn body() void = {};\n";
|
||||
int errs;
|
||||
const char *pkg;
|
||||
char *got = header_to_str(src, strlen(src), &errs, &pkg);
|
||||
if (errs != 0 || pkg == NULL || strcmp(pkg, "main") != 0
|
||||
|| got == NULL || strstr(got, "(use \"zed\"") == NULL
|
||||
|| strstr(got, "(use \"stable\"") == NULL
|
||||
|| strstr(got, "body") != NULL) {
|
||||
fputs("package-header parse mismatch\n", stderr);
|
||||
fail++;
|
||||
}
|
||||
free((void *)pkg);
|
||||
free(got);
|
||||
}
|
||||
{
|
||||
const char *src =
|
||||
"package main;\n"
|
||||
"\xff";
|
||||
int errs;
|
||||
const char *pkg;
|
||||
char *got = header_to_str(src, strlen(src), &errs, &pkg);
|
||||
if (errs != 0 || pkg == NULL || strcmp(pkg, "main") != 0
|
||||
|| got == NULL) {
|
||||
fputs("package-header observed the first malformed UTF-8 body byte\n",
|
||||
stderr);
|
||||
fail++;
|
||||
}
|
||||
free((void *)pkg);
|
||||
free(got);
|
||||
}
|
||||
{
|
||||
const char src[] =
|
||||
"package main;\n"
|
||||
"import alpha;\n"
|
||||
"\xef\xbb\xbf";
|
||||
int errs;
|
||||
const char *pkg;
|
||||
char *got = header_to_str(src, sizeof src - 1, &errs, &pkg);
|
||||
if (errs != 0 || pkg == NULL || strcmp(pkg, "main") != 0
|
||||
|| got == NULL || strstr(got, "(use \"alpha\"") == NULL) {
|
||||
fputs("package-header observed the first body BOM\n", stderr);
|
||||
fail++;
|
||||
}
|
||||
free((void *)pkg);
|
||||
free(got);
|
||||
}
|
||||
{
|
||||
const char src[] = "package main;\n\0fn body() void = {};\n";
|
||||
int errs;
|
||||
const char *pkg;
|
||||
char *got = header_to_str(src, sizeof src - 1, &errs, &pkg);
|
||||
if (errs == 0) {
|
||||
fputs("package-header ignored a reached NUL boundary byte\n",
|
||||
stderr);
|
||||
fail++;
|
||||
}
|
||||
free((void *)pkg);
|
||||
free(got);
|
||||
}
|
||||
if (header_errors("package main;\n/* unterminated") == 0) {
|
||||
fputs("package-header accepted unterminated header trivia\n", stderr);
|
||||
fail++;
|
||||
}
|
||||
{
|
||||
const char *src =
|
||||
"package main;\n"
|
||||
"/* between */ import alpha; // after\n"
|
||||
"import beta;\n"
|
||||
"\xff";
|
||||
int errs;
|
||||
const char *pkg;
|
||||
char *got = header_to_str(src, strlen(src), &errs, &pkg);
|
||||
if (errs != 0 || got == NULL
|
||||
|| strstr(got, "(use \"alpha\"") == NULL
|
||||
|| strstr(got, "(use \"beta\"") == NULL) {
|
||||
fputs("package-header comment/import boundary mismatch\n", stderr);
|
||||
fail++;
|
||||
}
|
||||
free((void *)pkg);
|
||||
free(got);
|
||||
}
|
||||
{
|
||||
int errs;
|
||||
const char *pkg;
|
||||
char *got = header_to_str(
|
||||
"package main;\nimported body;\nimport late;\n",
|
||||
strlen("package main;\nimported body;\nimport late;\n"),
|
||||
&errs, &pkg);
|
||||
if (errs != 0 || got == NULL || strstr(got, "late") != NULL) {
|
||||
fputs("package-header mistook an ordinary identifier for import\n",
|
||||
stderr);
|
||||
fail++;
|
||||
}
|
||||
free((void *)pkg);
|
||||
free(got);
|
||||
}
|
||||
if (header_errors("package ;\n") == 0) {
|
||||
fputs("package-header accepted a malformed package clause\n", stderr);
|
||||
fail++;
|
||||
}
|
||||
if (header_errors("package main;\nimport ;\n") == 0) {
|
||||
fputs("package-header accepted a malformed import\n", stderr);
|
||||
fail++;
|
||||
}
|
||||
if (header_errors("package main\nfn body() void = {};\n") == 0) {
|
||||
fputs("package-header accepted a missing package semicolon\n", stderr);
|
||||
fail++;
|
||||
}
|
||||
{
|
||||
int errs;
|
||||
const char *pkg;
|
||||
@@ -333,6 +480,6 @@ main(void)
|
||||
fprintf(stderr, "%d parse tests failed\n", fail);
|
||||
return 1;
|
||||
}
|
||||
printf("parse: %d/%d ok + 14 shape ok\n", n, n);
|
||||
printf("parse: %d/%d ok + 14 shape + 10 header ok\n", n, n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user