Files
ww/test/sep/localbuild_test.ww

1973 lines
86 KiB
Plaintext

package localbuild_test;
// End-to-end local package builds through both production drivers. Fixtures
// are created at runtime so there is no manifest or hidden graph input: every
// edge below comes from an ordinary WW import declaration.
import os;
import os.exec;
import strings;
import testenv;
import time;
fn fail(label: str, why: str) void = {
let m: str = strings.concat("localbuild FAIL: ", label, " -- ", why,
"\n");
os.write(2, m.ptr, m.len: u64);
assert(false);
};
fn tmo() time.duration = {
return (240i64 * (time.second: i64)): time.duration;
};
fn mkdir(path: str) void = { assert(os.mkdir(path, 493) == 0); };
fn command(dir: str, name: str, argv: []str,
out: *testenv.commandout) void = {
testenv.runcommand(dir, dir, name, argv, tmo(), out);
};
fn code(dir: str, name: str, argv: []str) i32 = {
let co: testenv.commandout;
command(dir, name, argv, &co);
if (co.termination != exec.termination.EXIT) { return -1; };
return co.code;
};
fn build(dir: str, driver: str, tag: str, target: str, out: str) i32 = {
let av: []str = [testenv.driver(driver), "build", "-I", dir,
"-o", out, target];
return code(dir, strings.concat("build_", tag), av);
};
fn roottraceenv(source: str, runtime: str,
compiler: str, assembler: str, linker: str,
compilertrace: str, assemblertrace: str, linkertrace: str,
realcompiler: str, realassembler: str, reallinker: str) []str = {
let base: []str = os.getenvs();
let env: []str = alloc([], (base.len + 11): u64)!;
let i: i32 = 0;
for (i < base.len) {
if (!strings.hasprefix(base[i], "WW_SRCLIB=")
&& !strings.hasprefix(base[i], "WW_LIB=")
&& !strings.hasprefix(base[i], "WW_W6C=")
&& !strings.hasprefix(base[i], "WW_W6A=")
&& !strings.hasprefix(base[i], "WW_W6L=")
&& !strings.hasprefix(base[i], "WW_ROOT_COMPILER_TRACE=")
&& !strings.hasprefix(base[i], "WW_ROOT_ASSEMBLER_TRACE=")
&& !strings.hasprefix(base[i], "WW_ROOT_LINKER_TRACE=")
&& !strings.hasprefix(base[i], "WW_ROOT_REAL_COMPILER=")
&& !strings.hasprefix(base[i], "WW_ROOT_REAL_ASSEMBLER=")
&& !strings.hasprefix(base[i], "WW_ROOT_REAL_LINKER=")) {
append(env, base[i]);
};
i += 1;
};
append(env, strings.concat("WW_SRCLIB=", source));
append(env, strings.concat("WW_LIB=", runtime));
append(env, strings.concat("WW_W6C=", compiler));
append(env, strings.concat("WW_W6A=", assembler));
append(env, strings.concat("WW_W6L=", linker));
append(env, strings.concat("WW_ROOT_COMPILER_TRACE=", compilertrace));
append(env, strings.concat("WW_ROOT_ASSEMBLER_TRACE=", assemblertrace));
append(env, strings.concat("WW_ROOT_LINKER_TRACE=", linkertrace));
append(env, strings.concat("WW_ROOT_REAL_COMPILER=", realcompiler));
append(env, strings.concat("WW_ROOT_REAL_ASSEMBLER=", realassembler));
append(env, strings.concat("WW_ROOT_REAL_LINKER=", reallinker));
return env;
};
fn diagnosticbody(s: str) str = {
let p: i32 = testenv.pos(s, ": error: ");
// Driver-owned graph diagnostics have no source-location prefix. Compare
// those in full; strip only compiler paths/locations from source errors.
if (p < 0) { return s; };
let begin: i32 = p + 9;
let end: i32 = begin;
for (end < s.len && s[end] != '\n') { end += 1; };
return strings.sub(s, begin, end);
};
fn rejectstable(dir: str, label: str, target: str, needle: str) void = {
let drivers: []str = ["ww", "ww_ww", "ww", "ww_ww"];
let tags: []str = ["c1", "w1", "c2", "w2"];
let errors: []str = ["", "", "", ""];
let cwork: str = strings.concat(dir, "/work-", label, "-c");
let wwork: str = strings.concat(dir, "/work-", label, "-w");
mkdir(cwork); mkdir(wwork);
let i: i32 = 0;
for (i < drivers.len) {
let out: str = strings.concat(dir, "/reject-", label, "-", tags[i]);
let work: str = cwork;
if (i == 1 || i == 3) { work = wwork; };
let av: []str = [testenv.driver(drivers[i]), "build", "-I", dir,
"-w", work, "-o", out, target];
let co: testenv.commandout;
command(dir, strings.concat("reject_", label, "_", tags[i]), av,
&co);
if (co.termination != exec.termination.EXIT || co.code == 0) {
fail(label, strings.concat(drivers[i], " accepted invalid graph"));
};
if (!testenv.has(co.stderr, needle)) {
fail(label, strings.concat(drivers[i], " diagnostic missing '",
needle, "': ", co.stderr));
};
errors[i] = co.stderr;
i += 1;
};
if (!testenv.same(errors[0], errors[2])) {
fail(label, "C diagnostic changed across repeated builds");
};
if (!testenv.same(errors[1], errors[3])) {
fail(label, "WW diagnostic changed across repeated builds");
};
let cb: str = diagnosticbody(errors[0]);
let wb: str = diagnosticbody(errors[1]);
if (!testenv.same(cb, wb)) {
fail(label, strings.concat("C/WW diagnostic bodies differ: '", cb,
"' vs '", wb, "'"));
};
};
@test fn standalone_and_package_artifact() void = {
let td: str = testenv.fresh();
let app: str = strings.concat(td, "/main");
mkdir(app);
testenv.writefile(strings.concat(app, "/main.ww"), strings.concat(
"package main;\n",
"fn main() i32 = { return 23; };\n"));
// Ordinary builds must not accidentally select test-only sources.
testenv.writefile(strings.concat(app, "/broken_test.ww"),
"package other;\nthis is deliberately invalid\n");
let c1: str = strings.concat(td, "/standalone-c1");
let c2: str = strings.concat(td, "/standalone-c2");
let ww: str = strings.concat(td, "/standalone-ww");
if (build(td, "ww", "standalone_c1", app, c1) != 0
|| build(td, "ww", "standalone_c2", app, c2) != 0
|| build(td, "ww_ww", "standalone_ww", app, ww) != 0) {
fail("standalone", "manifest-free directory build failed");
};
let av1: []str = [c1];
let av2: []str = [ww];
if (code(td, "run_standalone_c", av1) != 23
|| code(td, "run_standalone_ww", av2) != 23) {
fail("standalone", "built executable returned the wrong value");
};
if (!testenv.same(testenv.readfile(c1), testenv.readfile(c2))) {
fail("standalone", "two clean builds were not byte-identical");
};
let lib: str = strings.concat(td, "/libonly");
mkdir(lib);
testenv.writefile(strings.concat(lib, "/lib.ww"), strings.concat(
"package libonly;\n",
"export fn shown() i32 = { return 7; };\n",
"fn hidden() i32 = { return 99; };\n"));
let ca: str = strings.concat(td, "/libonly-c.a");
let wa: str = strings.concat(td, "/libonly-w.a");
let cav: []str = [testenv.driver("ww"), "build", "-I", td,
"-o", ca, lib];
let wav: []str = [testenv.driver("ww_ww"), "build", "-I", td,
"-o", wa, lib];
if (code(td, "build_package_c", cav) != 0
|| code(td, "build_package_w", wav) != 0) {
fail("package-artifact", "non-main package build failed");
};
if (!testenv.exists(strings.concat(ca, ".wwi"))
|| !testenv.exists(strings.concat(wa, ".wwi"))) {
fail("package-artifact", "missing compiler export sidecar");
};
if (!testenv.same(testenv.readfile(ca), testenv.readfile(wa))
|| !testenv.same(testenv.readfile(strings.concat(ca, ".wwi")),
testenv.readfile(strings.concat(wa, ".wwi")))) {
fail("package-artifact", "C/WW package products differ");
};
let iface: str = testenv.readfile(strings.concat(ca, ".wwi"));
if (!testenv.has(iface, "shown") || testenv.has(iface, "hidden")) {
fail("package-artifact", "export interface crossed the private boundary");
};
testenv.clean(td);
};
@test fn self_contained_direct_exports() void = {
let td: str = testenv.fresh();
let dimensions: str = strings.concat(td, "/dimensions");
let implementation: str = strings.concat(td, "/implementation");
let api: str = strings.concat(td, "/api");
let main: str = strings.concat(td, "/main");
mkdir(dimensions); mkdir(implementation); mkdir(api); mkdir(main);
let dimensionssrc: str = strings.concat(
"package dimensions;\n",
"export def WIDTH: i32 = 4;\n",
"export def UNUSED_WIDTH: i32 = 8;\n");
let implsrc: str = strings.concat(
"package implementation;\n",
"import dimensions;\n",
"def PRIVATE_WIDTH: i32 = 3;\n",
"export type Buffer = [PRIVATE_WIDTH]u8;\n",
"export type PublicBuffer = [dimensions.WIDTH]u8;\n",
"export type CastBuffer = [(dimensions.WIDTH: u64)]u8;\n",
"export type Detail = struct { code: i64, };\n",
"export type Member = struct { detail: Detail, payload: i64, };\n",
"export type Unused = struct { noise: i32, };\n",
"export def UNUSED: i32 = 99;\n",
"type private_type = struct { secret: i32, };\n",
"def private_value: i32 = 77;\n",
"export fn construct(v: i32) Member = { return Member{detail=Detail{code=(v: i64) + 1i64}, payload=(v: i64) + 2i64}; };\n",
"export fn unrelated() i32 = { return UNUSED; };\n",
"fn hidden() i32 = { return private_value; };\n");
let apisrc: str = strings.concat(
"package api;\n",
"import implementation;\n",
"export type Member = struct { local: i32, };\n",
"export fn produce(v: i32) implementation.Member = { return implementation.construct(v); };\n",
"export fn score(m: implementation.Member) i32 = { return (m.detail.code + m.payload): i32; };\n",
"export fn width_of(b: implementation.Buffer) i32 = { return len(b): i32; };\n",
"export fn public_width_of(b: implementation.PublicBuffer) i32 = { return len(b): i32; };\n",
"export fn cast_width_of(b: implementation.CastBuffer) i32 = { return len(b): i32; };\n");
let mainsrc: str = strings.concat(
"package main;\n",
"import api;\n",
"fn main() i32 = { let m = api.produce(19); let p: *i64 = alloc(api.score(m): i64)!; return (*p): i32; };\n");
testenv.writefile(strings.concat(dimensions, "/dimensions.ww"), dimensionssrc);
testenv.writefile(strings.concat(implementation, "/implementation.ww"),
implsrc);
testenv.writefile(strings.concat(api, "/api.ww"), apisrc);
testenv.writefile(strings.concat(main, "/main.ww"), mainsrc);
let outputs: []str = [strings.concat(td, "/direct-c1"),
strings.concat(td, "/direct-c2"), strings.concat(td, "/direct-w")];
let drivers: []str = ["ww", "ww", "ww_ww"];
let tags: []str = ["direct_c1", "direct_c2", "direct_w"];
let i: i32 = 0;
for (i < outputs.len) {
if (build(td, drivers[i], tags[i], main, outputs[i]) != 0) {
fail("self-contained", strings.concat(drivers[i], " build failed"));
};
let runav: []str = [outputs[i]];
if (code(td, strings.concat("run_", tags[i]), runav) != 41) {
fail("self-contained", "implementation archive was not linked");
};
i += 1;
};
if (!testenv.same(testenv.readfile(outputs[0]), testenv.readfile(outputs[1]))
|| !testenv.same(testenv.readfile(outputs[0]), testenv.readfile(outputs[2]))) {
fail("self-contained", "clean C/C/WW executable outputs differ");
};
let cwork: str = strings.concat(outputs[0], ".sepwork/");
let cwork2: str = strings.concat(outputs[1], ".sepwork/");
let wwork: str = strings.concat(outputs[2], ".sepwork/");
let apiiface: str = testenv.readfile(strings.concat(cwork, "api.wwi"));
if (!testenv.same(apiiface, testenv.readfile(strings.concat(cwork2,
"api.wwi"))) || !testenv.same(apiiface, testenv.readfile(strings.concat(
wwork, "api.wwi")))) {
fail("self-contained", "C/C/WW api exports differ");
};
if (!testenv.has(apiiface, "//ww:module implementation\n")
|| !testenv.has(apiiface, "export type Buffer = [3]u8;")
|| !testenv.has(apiiface, "export type PublicBuffer = [4]u8;")
|| !testenv.has(apiiface, "export type CastBuffer = [4]u8;")
|| !testenv.has(apiiface, "export type Detail = struct")
|| testenv.occurrences(apiiface, "export type Member = struct") != 2) {
fail("self-contained", "api export lacks the recursive public type facts");
};
if (testenv.has(apiiface, "//ww:module dimensions\n")
|| testenv.has(apiiface, "WIDTH")
|| testenv.has(apiiface, "export def UNUSED_WIDTH")
|| testenv.has(apiiface, "PRIVATE_WIDTH")
|| testenv.has(apiiface, "export type Unused")
|| testenv.has(apiiface, "export def UNUSED")
|| testenv.has(apiiface, "export fn construct")
|| testenv.has(apiiface, "unrelated")
|| testenv.has(apiiface, "private_type")
|| testenv.has(apiiface, "private_value")
|| testenv.has(apiiface, "hidden")) {
fail("self-contained", "api export leaked unrelated or private declarations");
};
let expectedunit: str = strings.concat("//ww:module-reset main\n",
mainsrc, "\n");
let cunit: str = testenv.readfile(strings.concat(cwork, "main.unit.ww"));
let cunit2: str = testenv.readfile(strings.concat(cwork2, "main.unit.ww"));
let wunit: str = testenv.readfile(strings.concat(wwork, "main.unit.ww"));
if (!strings.hasprefix(cunit, expectedunit)
|| testenv.occurrences(cunit, "//ww:direct-export api ") != 1
|| testenv.pos(cunit, "//ww:direct-export api ") != expectedunit.len
|| !testenv.same(cunit, cunit2) || !testenv.same(cunit, wunit)) {
fail("self-contained", "consumer owner prefix/direct-export voucher mismatch");
};
if (!testenv.exists(strings.concat(cwork, "implementation.a"))
|| !testenv.exists(strings.concat(cwork, "dimensions.a"))) {
fail("self-contained", "reachable archives missing from link closure");
};
let packages: []str = [strings.concat(td, "/api-c1.a"),
strings.concat(td, "/api-c2.a"), strings.concat(td, "/api-w.a")];
i = 0;
for (i < packages.len) {
let pav: []str = [testenv.driver(drivers[i]), "build", "-I", td,
"-o", packages[i], api];
if (code(td, strings.concat("package_", tags[i]), pav) != 0) {
fail("self-contained", "ww build api failed");
};
i += 1;
};
if (!testenv.same(testenv.readfile(packages[0]), testenv.readfile(packages[1]))
|| !testenv.same(testenv.readfile(packages[0]), testenv.readfile(packages[2]))
|| !testenv.same(apiiface, testenv.readfile(strings.concat(packages[0],
".wwi"))) || !testenv.same(apiiface, testenv.readfile(strings.concat(
packages[1], ".wwi"))) || !testenv.same(apiiface,
testenv.readfile(strings.concat(packages[2], ".wwi")))) {
fail("self-contained", "deterministic package artifact/export mismatch");
};
testenv.writefile(strings.concat(td, "/deep-qualifier.ww"), strings.concat(
"package main;\nimport api;\n",
"fn main() i32 = { let m: implementation.Member; return 0; };\n"));
rejectstable(td, "deep-qualifier", strings.concat(td,
"/deep-qualifier.ww"), "unknown type 'implementation.Member'");
testenv.writefile(strings.concat(td, "/deep-value.ww"), strings.concat(
"package main;\nimport api;\n",
"fn main() i32 = { return construct(1); };\n"));
rejectstable(td, "deep-value", strings.concat(td, "/deep-value.ww"),
"undefined: construct");
testenv.writefile(strings.concat(td, "/deep-const.ww"), strings.concat(
"package main;\nimport api;\n",
"fn main() i32 = { return WIDTH; };\n"));
rejectstable(td, "deep-const", strings.concat(td, "/deep-const.ww"),
"undefined: WIDTH");
testenv.writefile(strings.concat(td, "/deep-type.ww"), strings.concat(
"package main;\nimport api;\n",
"fn main() i32 = { let d: Detail; return 0; };\n"));
rejectstable(td, "deep-type", strings.concat(td, "/deep-type.ww"),
"unknown type 'Detail'");
testenv.writefile(strings.concat(td, "/private-direct.ww"), strings.concat(
"package main;\nimport implementation;\n",
"fn main() i32 = { return implementation.hidden(); };\n"));
rejectstable(td, "private-direct", strings.concat(td,
"/private-direct.ww"), "has no exported declaration 'hidden'");
testenv.clean(td);
};
fn writediamond(td: str, reverse: bool) str = {
let shared: str = strings.concat(td, "/shared");
let left: str = strings.concat(td, "/left");
let right: str = strings.concat(td, "/right");
let main: str = strings.concat(td, "/main");
if (reverse) { mkdir(left); mkdir(main); mkdir(shared); mkdir(right); }
else { mkdir(right); mkdir(shared); mkdir(main); mkdir(left); };
let zsrc: str = strings.concat(
"package shared;\n",
"fn hidden() i32 = { return 91; };\n");
let asrc: str = strings.concat(
"package shared;\n",
"export type token = struct { value: i32, };\n",
"export fn base() i32 = { return 3; };\n");
if (reverse) {
testenv.writefile(strings.concat(shared, "/a.ww"), asrc);
testenv.writefile(strings.concat(shared, "/z.ww"), zsrc);
} else {
testenv.writefile(strings.concat(shared, "/z.ww"), zsrc);
testenv.writefile(strings.concat(shared, "/a.ww"), asrc);
};
testenv.writefile(strings.concat(left, "/left.ww"), strings.concat(
"package left;\nimport shared;\n",
"export fn make() shared.token = { return shared.token{value=shared.base() + 1}; };\n"));
testenv.writefile(strings.concat(right, "/right.ww"), strings.concat(
"package right;\nimport shared;\n",
"export fn value(t: shared.token) i32 = { return t.value + shared.base() + 2; };\n"));
testenv.writefile(strings.concat(main, "/main.ww"), strings.concat(
"package main;\nimport right;\nimport left;\n",
"fn main() i32 = { return right.value(left.make()); };\n"));
return main;
};
@test fn direct_and_diamond() void = {
let td: str = testenv.fresh();
let treea: str = strings.concat(td, "/tree-a"); mkdir(treea);
let treeb: str = strings.concat(td, "/tree-b"); mkdir(treeb);
let main: str = writediamond(treea, false);
let shuffled: str = writediamond(treeb, true);
let drivers: []str = ["ww", "ww_ww"];
let tags: []str = ["c", "w"];
let i: i32 = 0;
for (i < drivers.len) {
let out: str = strings.concat(td, "/diamond-", tags[i]);
if (build(treea, drivers[i], strings.concat("diamond_", tags[i]),
main, out) != 0) {
fail("diamond", strings.concat(drivers[i], " build failed"));
};
let av: []str = [out];
if (code(td, strings.concat("run_diamond_", tags[i]), av) != 9) {
fail("diamond", "linked program returned the wrong value");
};
let scratch: str = strings.concat(out, ".sepwork/");
if (!testenv.exists(strings.concat(scratch, "shared.a"))
|| testenv.exists(strings.concat(scratch, "shared.shared.a"))) {
fail("diamond", "shared package was not interned exactly once");
};
let unit: str = testenv.readfile(strings.concat(scratch,
"main.unit.ww"));
let rootbody: str = testenv.readfile(strings.concat(main, "/main.ww"));
let wantroot: str = strings.concat("//ww:module-reset main\n", rootbody,
"\n");
let leftvoucher: str = "//ww:direct-export left ";
let rightvoucher: str = "//ww:direct-export right ";
if (!strings.hasprefix(unit, wantroot) || testenv.has(unit,
"//ww:module ")
|| testenv.occurrences(unit, "//ww:direct-export ") != 2
|| testenv.occurrences(unit, leftvoucher) != 1
|| testenv.occurrences(unit, rightvoucher) != 1
|| testenv.pos(unit, leftvoucher) != wantroot.len
|| testenv.pos(unit, leftvoucher) >= testenv.pos(unit, rightvoucher)) {
fail("diamond", "root owner prefix/sorted direct-export vouchers changed");
};
let sharedunit: str = testenv.readfile(strings.concat(scratch,
"shared.unit.ww"));
let ap: i32 = testenv.pos(sharedunit, "export type token");
let zp: i32 = testenv.pos(sharedunit, "fn hidden");
if (ap < 0 || zp < 0 || ap >= zp) {
fail("diamond", "package sources were not byte-sorted");
};
let leftiface: str = testenv.readfile(strings.concat(scratch,
"left.wwi"));
let rightiface: str = testenv.readfile(strings.concat(scratch,
"right.wwi"));
if (testenv.occurrences(leftiface,
"import __wwi_736861726564 shared;") != 1) {
fail("diamond", "direct import is missing or duplicated in export data");
};
if (testenv.occurrences(leftiface, "export type token") != 1
|| testenv.occurrences(rightiface, "export type token") != 1
|| testenv.has(unit, "export type token")) {
fail("diamond", "origin fact closure did not merge deterministically");
};
i += 1;
};
let shuffledout: str = strings.concat(td, "/diamond-c-shuffled");
if (build(treeb, "ww", "diamond_c_shuffled", shuffled,
shuffledout) != 0) {
fail("diamond", "shuffled directory build failed");
};
let firstout: str = strings.concat(td, "/diamond-c");
if (!testenv.same(testenv.readfile(firstout),
testenv.readfile(shuffledout))) {
fail("diamond", "shuffled directory enumeration changed output");
};
let firstscratch: str = strings.concat(firstout, ".sepwork/");
let shuffledscratch: str = strings.concat(shuffledout, ".sepwork/");
if (!testenv.same(testenv.readfile(strings.concat(firstscratch,
"main.unit.ww")), testenv.readfile(strings.concat(shuffledscratch,
"main.unit.ww"))) || !testenv.same(testenv.readfile(strings.concat(
firstscratch, "shared.unit.ww")), testenv.readfile(strings.concat(
shuffledscratch, "shared.unit.ww")))) {
fail("diamond", "shuffled enumeration changed package units");
};
testenv.clean(td);
};
@test fn toolchain_library_root_stage_parity() void = {
let td: str = testenv.fresh();
let source: str = strings.concat(td, "/source library");
let app: str = strings.concat(source, "/main");
let dep: str = strings.concat(source, "/dep");
let leaf: str = strings.concat(source, "/leaf");
let runtime: str = strings.concat(td, "/runtime library");
let decoy: str = strings.concat(runtime, "/main");
let tools: str = strings.concat(td, "/tool wrappers");
let cwd: str = strings.concat(td, "/isolated cwd");
let defaultapp: str = strings.concat(td, "/default app");
let fallbackbuf: []u8 = alloc([], os.PATH_MAX: u64)!;
fallbackbuf.len = os.PATH_MAX;
let fi: i32 = 0;
for (fi < fallbackbuf.len) { fallbackbuf[fi] = 120u8; fi += 1; };
let fallback: str = strings.frombytes(fallbackbuf);
mkdir(source); mkdir(app); mkdir(dep); mkdir(leaf);
mkdir(runtime); mkdir(decoy); mkdir(tools); mkdir(cwd); mkdir(defaultapp);
testenv.writefile(strings.concat(leaf, "/leaf.ww"), strings.concat(
"package leaf;\n",
"export fn value() i32 = { return 40; };\n"));
testenv.writefile(strings.concat(dep, "/dep.ww"), strings.concat(
"package dep;\nimport leaf;\n",
"export fn value() i32 = { return leaf.value() + 2; };\n"));
testenv.writefile(strings.concat(app, "/app.ww"), strings.concat(
"package main;\nimport dep;\n",
"fn main() i32 = { return dep.value(); };\n"));
testenv.writefile(strings.concat(decoy, "/app.ww"),
"package main;\nfn main() i32 = { return 7; };\n");
testenv.writefile(strings.concat(defaultapp, "/main.ww"), strings.concat(
"package main;\nimport types;\n",
"fn main() i32 = { return (types.I8_MAX: i32) - 85; };\n"));
testenv.writefile(strings.concat(runtime, "/libwwrt.a"),
testenv.readfile(strings.concat(testenv.repo(), "/out/lib/libwwrt.a")));
let compiler: str = strings.concat(tools, "/w6c wrapper.sh");
let assembler: str = strings.concat(tools, "/w6a wrapper.sh");
let linker: str = strings.concat(tools, "/w6l wrapper.sh");
testenv.writeexecutable(compiler, strings.concat(
"#!/bin/sh\n",
"printf 'BEGIN' >> \"$WW_ROOT_COMPILER_TRACE\"\n",
"for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ",
"\"$WW_ROOT_COMPILER_TRACE\"; done\n",
"printf '\\n' >> \"$WW_ROOT_COMPILER_TRACE\"\n",
"exec \"$WW_ROOT_REAL_COMPILER\" \"$@\"\n"));
testenv.writeexecutable(assembler, strings.concat(
"#!/bin/sh\n",
"printf 'BEGIN' >> \"$WW_ROOT_ASSEMBLER_TRACE\"\n",
"for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ",
"\"$WW_ROOT_ASSEMBLER_TRACE\"; done\n",
"printf '\\n' >> \"$WW_ROOT_ASSEMBLER_TRACE\"\n",
"exec \"$WW_ROOT_REAL_ASSEMBLER\" \"$@\"\n"));
testenv.writeexecutable(linker, strings.concat(
"#!/bin/sh\n",
"printf 'BEGIN' >> \"$WW_ROOT_LINKER_TRACE\"\n",
"for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ",
"\"$WW_ROOT_LINKER_TRACE\"; done\n",
"printf '\\n' >> \"$WW_ROOT_LINKER_TRACE\"\n",
"exec \"$WW_ROOT_REAL_LINKER\" \"$@\"\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 modes: []str = ["archive", "overlong-fallback"];
let runtimes: []str = [runtime, fallback];
let bin: str = strings.concat(td, "/published binary");
let workroot: str = strings.concat(bin, ".sepwork");
let work: str = strings.concat(workroot, "/");
let mi: i32 = 0;
for (mi < modes.len) {
let referenceunit: str = "";
let referencewwi: str = "";
let referencearchive: str = "";
let referencerootwwi: str = "";
let referencerootarchive: str = "";
let referenceinitunit: str = "";
let referenceinitasm: str = "";
let referenceinitobj: str = "";
let referencebin: str = "";
let referencecompiler: str = "";
let referenceassembler: str = "";
let referencelinker: str = "";
let si: i32 = 0;
for (si < stages.len) {
let prefix: str = strings.concat(modes[mi], "-", tags[si]);
let compilertrace: str = strings.concat(td, "/", prefix,
" compiler trace");
let assemblertrace: str = strings.concat(td, "/", prefix,
" assembler trace");
let linkertrace: str = strings.concat(td, "/", prefix,
" linker trace");
testenv.writefile(compilertrace, "");
testenv.writefile(assemblertrace, "");
testenv.writefile(linkertrace, "");
let env: []str = roottraceenv(source, runtimes[mi],
compiler, assembler, linker,
compilertrace, assemblertrace, linkertrace,
testenv.driver(compilers[si]),
testenv.driver(assemblers[si]),
testenv.driver(linkers[si]));
let av: []str = [testenv.driver(stages[si]), "build", "-o", bin,
"main"];
let out: testenv.commandout;
testenv.runcommandenv(cwd, td, strings.concat("library-root-", prefix),
av, env, tmo(), &out);
if (out.termination != exec.termination.EXIT || out.code != 0
|| out.stderr.len != 0) {
fail("library-roots", strings.concat(prefix, " build failed: ",
out.stderr));
};
let rootunit: str = testenv.readfile(strings.concat(work,
"main.unit.ww"));
let depunit: str = testenv.readfile(strings.concat(work,
"dep.unit.ww"));
if (!testenv.has(rootunit, "//ww:module-reset main\npackage main;")
|| testenv.has(rootunit, "//ww:module ")
|| !testenv.has(depunit, "//ww:module-reset dep\npackage dep;")
|| testenv.has(depunit, "//ww:module ")) {
fail("library-roots", "package units contain non-owned export text");
};
if (!testenv.exists(strings.concat(work, "leaf.wwi"))
|| !testenv.exists(strings.concat(work, "leaf.a"))
|| !testenv.exists(strings.concat(work, "dep.wwi"))
|| !testenv.exists(strings.concat(work, "dep.a"))
|| !testenv.exists(strings.concat(work, "main.o"))
|| !testenv.exists(strings.concat(work, "main.wwi"))
|| !testenv.exists(strings.concat(work, "main.a"))
|| !testenv.exists(strings.concat(work, "main.init.unit.ww"))
|| !testenv.exists(strings.concat(work, "main.init.s"))
|| !testenv.exists(strings.concat(work, "main.init.o"))) {
fail("library-roots", "package artifacts do not match root ownership");
};
let initunit: str = testenv.readfile(strings.concat(work,
"main.init.unit.ww"));
if (!testenv.same(initunit, strings.concat(
"//ww:init-root __ww..pkg.p.main.v0.r0.init\n",
"//ww:init-call __ww..pkg.p.leaf.v0.r0.init\n",
"//ww:init-call __ww..pkg.p.dep.v0.r0.init\n",
"//ww:init-call __ww..pkg.p.main.v0.r0.init\n"))) {
fail("library-roots", "root dispatcher unit order changed");
};
let ctrace: str = testenv.readfile(compilertrace);
let atrace: str = testenv.readfile(assemblertrace);
let ltrace: str = testenv.readfile(linkertrace);
if (testenv.occurrences(ctrace, strings.concat("<", work,
"leaf.unit.ww>")) != 1
|| testenv.occurrences(ctrace, strings.concat("<", work,
"dep.unit.ww>")) != 1
|| testenv.occurrences(ctrace, strings.concat("<", work,
"main.unit.ww>")) != 1
|| testenv.pos(ctrace, strings.concat("<", work,
"leaf.unit.ww>")) >= testenv.pos(ctrace, strings.concat("<",
work, "dep.unit.ww>"))
|| testenv.pos(ctrace, strings.concat("<", work,
"dep.unit.ww>")) >= testenv.pos(ctrace, strings.concat("<",
work, "main.unit.ww>"))) {
fail("library-roots", "compiler actions were not deterministic postorder");
};
if (!testenv.has(ctrace, strings.concat(
"BEGIN<--package-init-symbol><__ww..pkg.p.leaf.v0.r0.init>",
"<-c><-I><", work, "leaf.wwi><-o><", work,
"leaf.s><", work, "leaf.unit.ww>"))
|| !testenv.has(ctrace, strings.concat(
"BEGIN<--package-init-symbol><__ww..pkg.p.dep.v0.r0.init>",
"<-c><--import><leaf><", work, "leaf.wwi><-I><", work,
"dep.wwi><-o><", work,
"dep.s><", work, "dep.unit.ww>"))
|| !testenv.has(ctrace, strings.concat(
"BEGIN<--entry><--package-init-symbol>",
"<__ww..pkg.p.main.v0.r0.init><--init-dispatch-symbol>",
"<__ww..dispatch><-c><--import><dep><", work,
"dep.wwi><-I><", work, "main.wwi><-o><", work,
"main.s><", work, "main.unit.ww>"))
|| testenv.occurrences(atrace, "\n") != 4
|| !testenv.has(atrace, strings.concat("BEGIN<-o><", work,
"dep.o><", work, "dep.s>"))
|| !testenv.has(atrace, strings.concat("BEGIN<-o><", work,
"main.init.o><", work, "main.init.s>"))) {
fail("library-roots", "compiler or assembler argv changed");
};
if (testenv.occurrences(ltrace, "\n") != 1
|| !testenv.has(ltrace, strings.concat("BEGIN<-o><", bin, ".new><",
work, "main.a>"))
|| testenv.pos(ltrace, strings.concat("<", work, "dep.a>")) < 0
|| testenv.pos(ltrace, strings.concat("<", work, "leaf.a>"))
< testenv.pos(ltrace, strings.concat("<", work, "dep.a>"))
|| testenv.has(ltrace, ".wwi>")) {
fail("library-roots", "linker did not receive the reachable archive closure");
};
if (mi == 0) {
if (!testenv.has(ltrace, strings.concat("<", runtime,
"/libwwrt.a>"))
|| testenv.has(ltrace, strings.concat("<", testenv.repo(),
"/out/lib/libwwrt.a>"))) {
fail("library-roots", "WW_LIB did not select the runtime archive");
};
} else {
if (!testenv.has(ltrace, strings.concat("<", testenv.repo(),
"/out/bin/../obj/rt/start.o>"))
|| !testenv.has(ltrace, strings.concat("<", testenv.repo(),
"/out/bin/../obj/rt/syscall.o>"))) {
fail("library-roots", "missing runtime archive did not use object fallback");
};
};
let runav: []str = [bin];
if (code(cwd, strings.concat("run-library-root-", prefix), runav)
!= 42) {
fail("library-roots", "WW_SRCLIB did not win over the WW_LIB decoy");
};
if (si == 0) {
referenceunit = strings.dup(rootunit);
referencewwi = strings.dup(testenv.readfile(strings.concat(work,
"dep.wwi")));
referencearchive = strings.dup(testenv.readfile(strings.concat(work,
"dep.a")));
referencerootwwi = strings.dup(testenv.readfile(strings.concat(work,
"main.wwi")));
referencerootarchive = strings.dup(testenv.readfile(strings.concat(work,
"main.a")));
referenceinitunit = strings.dup(initunit);
referenceinitasm = strings.dup(testenv.readfile(strings.concat(work,
"main.init.s")));
referenceinitobj = strings.dup(testenv.readfile(strings.concat(work,
"main.init.o")));
referencebin = strings.dup(testenv.readfile(bin));
referencecompiler = strings.dup(ctrace);
referenceassembler = strings.dup(atrace);
referencelinker = strings.dup(ltrace);
} else {
if (!testenv.same(referenceunit, rootunit)
|| !testenv.same(referencewwi, testenv.readfile(strings.concat(
work, "dep.wwi")))
|| !testenv.same(referencearchive, testenv.readfile(strings.concat(
work, "dep.a")))
|| !testenv.same(referencerootwwi,
testenv.readfile(strings.concat(work, "main.wwi")))
|| !testenv.same(referencerootarchive,
testenv.readfile(strings.concat(work, "main.a")))
|| !testenv.same(referenceinitunit, initunit)
|| !testenv.same(referenceinitasm,
testenv.readfile(strings.concat(work, "main.init.s")))
|| !testenv.same(referenceinitobj,
testenv.readfile(strings.concat(work, "main.init.o")))
|| !testenv.same(referencebin, testenv.readfile(bin))
|| !testenv.same(referencecompiler, ctrace)
|| !testenv.same(referenceassembler, atrace)
|| !testenv.same(referencelinker, ltrace)) {
fail("library-roots", "Cstage and WWstage products or argv differ");
};
};
if (mi == 0) {
let missing: []str = [testenv.driver(stages[si]), "build", "-o",
strings.concat(td, "/missing binary"), "absent"];
testenv.runcommandenv(cwd, td, strings.concat("library-missing-",
tags[si]), missing, env, tmo(), &out);
if (out.termination != exec.termination.EXIT || out.code != 1
|| !testenv.same(out.stderr,
"ww build: cannot find module absent\n")) {
fail("library-roots", "bare-target diagnostic differs by stage");
};
};
testenv.clean(workroot);
assert(os.remove(bin) == 0);
si += 1;
};
mi += 1;
};
let emptyunitreference: str = "";
let emptywwireference: str = "";
let emptyarchivereference: str = "";
let emptybinreference: str = "";
let emptycompilerreference: str = "";
let emptyassemblerreference: str = "";
let emptylinkerreference: str = "";
let emptyout: str = strings.concat(td, "/empty published binary");
let emptyidentity: str = testenv.localpackageidentity(defaultapp, "main");
let si: i32 = 0;
for (si < stages.len) {
let outpath: str = emptyout;
let emptyworkroot: str = strings.concat(outpath, ".sepwork");
let emptywork: str = strings.concat(emptyworkroot, "/");
let compilertrace: str = strings.concat(td, "/empty-", tags[si],
" compiler trace");
let assemblertrace: str = strings.concat(td, "/empty-", tags[si],
" assembler trace");
let linkertrace: str = strings.concat(td, "/empty-", tags[si],
" linker trace");
testenv.writefile(compilertrace, "");
testenv.writefile(assemblertrace, "");
testenv.writefile(linkertrace, "");
let emptyenv: []str = roottraceenv("", "",
compiler, assembler, linker,
compilertrace, assemblertrace, linkertrace,
testenv.driver(compilers[si]),
testenv.driver(assemblers[si]),
testenv.driver(linkers[si]));
let av: []str = [testenv.driver(stages[si]), "build", "-o", outpath,
defaultapp];
let out: testenv.commandout;
testenv.runcommandenv(cwd, td, strings.concat("empty-library-",
tags[si]), av, emptyenv, tmo(), &out);
let rootunit: str = testenv.readfile(strings.concat(emptywork,
emptyidentity, ".unit.ww"));
let ctrace: str = testenv.readfile(compilertrace);
let atrace: str = testenv.readfile(assemblertrace);
let ltrace: str = testenv.readfile(linkertrace);
let runav: []str = [outpath];
if (out.termination != exec.termination.EXIT || out.code != 0
|| out.stderr.len != 0
|| code(cwd, strings.concat("run-empty-library-", tags[si]),
runav) != 42) {
fail("library-roots", "empty override did not use default roots");
};
if (!testenv.has(rootunit, strings.concat("//ww:module-reset ",
emptyidentity, "\npackage main;"))
|| testenv.has(rootunit, "//ww:module ")
|| !testenv.exists(strings.concat(emptywork, "types.wwi"))
|| !testenv.exists(strings.concat(emptywork, "types.a"))
|| testenv.occurrences(ctrace, strings.concat("<", emptywork,
"types.unit.ww>")) != 1
|| testenv.occurrences(atrace, "\n") != 3) {
fail("library-roots", "empty WW_SRCLIB did not use the default source root");
};
if (testenv.occurrences(ltrace, "\n") != 1
|| !testenv.has(ltrace, strings.concat("<", emptywork, "types.a>"))
|| !testenv.has(ltrace, strings.concat("<", testenv.repo(),
"/out/bin/../lib/libwwrt.a>"))
|| testenv.has(ltrace, "/../obj/rt/start.o>")
|| testenv.has(ltrace, "/../obj/rt/syscall.o>")) {
fail("library-roots", "empty WW_LIB did not use the default runtime archive");
};
if (si == 0) {
emptyunitreference = strings.dup(rootunit);
emptywwireference = strings.dup(testenv.readfile(strings.concat(
emptywork, "types.wwi")));
emptyarchivereference = strings.dup(testenv.readfile(strings.concat(
emptywork, "types.a")));
emptybinreference = strings.dup(testenv.readfile(outpath));
emptycompilerreference = strings.dup(ctrace);
emptyassemblerreference = strings.dup(atrace);
emptylinkerreference = strings.dup(ltrace);
} else { if (!testenv.same(emptyunitreference, rootunit)
|| !testenv.same(emptywwireference, testenv.readfile(strings.concat(
emptywork, "types.wwi")))
|| !testenv.same(emptyarchivereference, testenv.readfile(
strings.concat(emptywork, "types.a")))
|| !testenv.same(emptybinreference, testenv.readfile(outpath))
|| !testenv.same(emptycompilerreference, ctrace)
|| !testenv.same(emptyassemblerreference, atrace)
|| !testenv.same(emptylinkerreference, ltrace)) {
fail("library-roots", "empty-root fallback changed stage output or argv");
}; };
testenv.clean(emptyworkroot);
assert(os.remove(outpath) == 0);
si += 1;
};
testenv.clean(td);
};
// Unit bytes cannot identify driver-owned graph, archive, and commit semantics.
// Keep their executable content beside the compiler/assembler identities so a
// warm voucher never crosses a builder change.
@test fn persistent_build_reuse_tracks_driver_identity() void = {
let td: str = testenv.fresh();
let source: str = strings.concat(td, "/source tree");
let app: str = strings.concat(source, "/main");
let dep: str = strings.concat(source, "/dep");
let leaf: str = strings.concat(source, "/leaf");
let tools: str = strings.concat(td, "/tool wrappers");
let cdrivers: str = strings.concat(td, "/c driver copy");
let wdrivers: str = strings.concat(td, "/ww driver copy");
let cwd: str = strings.concat(td, "/isolated cwd");
let work: str = strings.concat(td, "/persistent work");
let bin: str = strings.concat(td, "/published binary");
mkdir(source); mkdir(app); mkdir(dep); mkdir(leaf); mkdir(tools);
mkdir(cdrivers); mkdir(wdrivers); mkdir(cwd); mkdir(work);
testenv.writefile(strings.concat(leaf, "/leaf.ww"), strings.concat(
"package leaf;\n",
"export fn value() i32 = { return 40; };\n"));
testenv.writefile(strings.concat(dep, "/dep.ww"), strings.concat(
"package dep;\nimport leaf;\n",
"export fn value() i32 = { return leaf.value() + 2; };\n"));
testenv.writefile(strings.concat(app, "/main.ww"), strings.concat(
"package main;\nimport dep;\n",
"fn main() i32 = { return dep.value(); };\n"));
let compiler: str = strings.concat(tools, "/w6c wrapper.sh");
let assembler: str = strings.concat(tools, "/w6a wrapper.sh");
let linker: str = strings.concat(tools, "/w6l wrapper.sh");
testenv.writeexecutable(compiler, strings.concat(
"#!/bin/sh\n",
"printf 'BEGIN' >> \"$WW_ROOT_COMPILER_TRACE\"\n",
"for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ",
"\"$WW_ROOT_COMPILER_TRACE\"; done\n",
"printf '\\n' >> \"$WW_ROOT_COMPILER_TRACE\"\n",
"exec \"$WW_ROOT_REAL_COMPILER\" \"$@\"\n"));
testenv.writeexecutable(assembler, strings.concat(
"#!/bin/sh\n",
"printf 'BEGIN' >> \"$WW_ROOT_ASSEMBLER_TRACE\"\n",
"for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ",
"\"$WW_ROOT_ASSEMBLER_TRACE\"; done\n",
"printf '\\n' >> \"$WW_ROOT_ASSEMBLER_TRACE\"\n",
"exec \"$WW_ROOT_REAL_ASSEMBLER\" \"$@\"\n"));
testenv.writeexecutable(linker, strings.concat(
"#!/bin/sh\n",
"printf 'BEGIN' >> \"$WW_ROOT_LINKER_TRACE\"\n",
"for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ",
"\"$WW_ROOT_LINKER_TRACE\"; done\n",
"printf '\\n' >> \"$WW_ROOT_LINKER_TRACE\"\n",
"exec \"$WW_ROOT_REAL_LINKER\" \"$@\"\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 copied: []str = [strings.concat(cdrivers, "/ww"),
strings.concat(wdrivers, "/ww_ww")];
let runtime: str = strings.concat(testenv.repo(), "/out/lib");
let referenceunit: str = "";
let referencewwi: str = "";
let referencearchive: str = "";
let referencebin: str = "";
let referencecompiler: str = "";
let referenceassembler: str = "";
let referencelinker: str = "";
let referenceerror: str = "";
let si: i32 = 0;
for (si < stages.len) {
let compilertrace: str = strings.concat(td, "/", tags[si],
" compiler trace");
let assemblertrace: str = strings.concat(td, "/", tags[si],
" assembler trace");
let linkertrace: str = strings.concat(td, "/", tags[si],
" linker trace");
testenv.writefile(compilertrace, "");
testenv.writefile(assemblertrace, "");
testenv.writefile(linkertrace, "");
let driverbytes: str = testenv.readfile(testenv.driver(stages[si]));
testenv.writeexecutable(copied[si], driverbytes);
let env: []str = roottraceenv(source, runtime,
compiler, assembler, linker,
compilertrace, assemblertrace, linkertrace,
testenv.driver(compilers[si]),
testenv.driver(assemblers[si]),
testenv.driver(linkers[si]));
let av: []str = [copied[si], "build", "-w", work,
"-o", bin, "main"];
let out: testenv.commandout;
testenv.runcommandenv(cwd, td, strings.concat("driver-cold-",
tags[si]), av, env, tmo(), &out);
if (out.termination != exec.termination.EXIT || out.code != 0
|| out.stderr.len != 0) {
fail("driver-identity", strings.concat(tags[si],
" cold build failed: ", out.stderr));
};
let rootunitpath: str = strings.concat(work, "/main.unit.ww");
let depunitpath: str = strings.concat(work, "/dep.unit.ww");
let rootunit: str = testenv.readfile(rootunitpath);
let depunit: str = testenv.readfile(depunitpath);
if (!testenv.has(rootunit, "//ww:module-reset main\npackage main;")
|| testenv.has(rootunit, "//ww:module ")
|| !testenv.has(depunit, "//ww:module-reset dep\npackage dep;")
|| testenv.has(depunit, "//ww:module ")) {
fail("driver-identity", "warm units contain non-owned export text");
};
if (!testenv.exists(strings.concat(work, "/leaf.wwi"))
|| !testenv.exists(strings.concat(work, "/leaf.a"))
|| !testenv.exists(strings.concat(work, "/dep.wwi"))
|| !testenv.exists(strings.concat(work, "/dep.a"))
|| !testenv.exists(strings.concat(work, "/main.wwi"))
|| !testenv.exists(strings.concat(work, "/main.a"))
|| !testenv.exists(strings.concat(work, "/main.init.unit.ww"))
|| !testenv.exists(strings.concat(work, "/main.init.s"))
|| !testenv.exists(strings.concat(work, "/main.init.o"))
|| !testenv.same(testenv.readfile(strings.concat(work,
"/.wwtool.ww")), testenv.readfile(copied[si]))
|| !testenv.same(testenv.readfile(strings.concat(work,
"/.wwtool.w6c")), testenv.readfile(compiler))
|| !testenv.same(testenv.readfile(strings.concat(work,
"/.wwtool.w6a")), testenv.readfile(assembler))
|| !testenv.same(testenv.readfile(strings.concat(work,
"/.wwtool.stamp")),
"ww workdir fmt 17 mode build asm 0\n")) {
fail("driver-identity", "persistent artifacts or identities are incomplete");
};
let coldwwi: str = testenv.readfile(strings.concat(work, "/dep.wwi"));
let coldarchive: str = testenv.readfile(strings.concat(work, "/dep.a"));
let coldbin: str = testenv.readfile(bin);
let coldcompiler: str = testenv.readfile(compilertrace);
let coldassembler: str = testenv.readfile(assemblertrace);
let coldlinker: str = testenv.readfile(linkertrace);
if (testenv.occurrences(coldcompiler, "\n") != 3
|| !testenv.has(coldcompiler, strings.concat(
"BEGIN<--package-init-symbol><__ww..pkg.p.leaf.v0.r0.init>",
"<-c><-I><", work, "/leaf.wwi.new><-o><", work,
"/leaf.s.new><", work, "/leaf.unit.new>"))
|| !testenv.has(coldcompiler, strings.concat(
"BEGIN<--package-init-symbol><__ww..pkg.p.dep.v0.r0.init>",
"<-c><--import><leaf><", work, "/leaf.wwi.new><-I><", work,
"/dep.wwi.new><-o><", work,
"/dep.s.new><", work, "/dep.unit.new>"))
|| !testenv.has(coldcompiler, strings.concat(
"BEGIN<--entry><--package-init-symbol>",
"<__ww..pkg.p.main.v0.r0.init><--init-dispatch-symbol>",
"<__ww..dispatch><-c><--import><dep><", work,
"/dep.wwi.new><-I><", work, "/main.wwi.new><-o><", work,
"/main.s.new><", work,
"/main.unit.new>"))
|| testenv.occurrences(coldassembler, "\n") != 4
|| !testenv.has(coldassembler, strings.concat(
"BEGIN<-o><", work, "/dep.o.new><", work,
"/dep.s.new>"))
|| !testenv.has(coldassembler, strings.concat(
"BEGIN<-o><", work, "/main.init.o.new><", work,
"/main.init.s.new>"))) {
fail("driver-identity", "cold compiler or assembler argv changed");
};
if (testenv.occurrences(coldlinker, "\n") != 1
|| !testenv.has(coldlinker, strings.concat("BEGIN<-o><", bin,
".new><", work, "/main.a.new>"))
|| testenv.pos(coldlinker, strings.concat("<", work,
"/dep.a.new>")) < 0
|| testenv.pos(coldlinker, strings.concat("<", work,
"/leaf.a.new>")) < testenv.pos(coldlinker, strings.concat("<",
work, "/dep.a.new>"))
|| !testenv.has(coldlinker, strings.concat("<", runtime,
"/libwwrt.a>"))
|| testenv.has(coldlinker, ".wwi>")) {
fail("driver-identity", "cold link omitted the reachable archive closure");
};
let runav: []str = [bin];
if (code(cwd, strings.concat("driver-cold-run-", tags[si]), runav)
!= 42) {
fail("driver-identity", "cold binary returned the wrong value");
};
testenv.writefile(strings.concat(leaf, "/extra.ww"), strings.concat(
"package leaf;\n",
"export fn unchanged_api() i32 = { return 1; };\n"));
testenv.runcommandenv(cwd, td, strings.concat("driver-export-change-",
tags[si]), av, env, tmo(), &out);
if (out.termination != exec.termination.EXIT || out.code != 0
|| out.stderr.len != 0) {
fail("driver-identity", "dependency export-change build failed");
};
let changedexportcompiler: str = testenv.readfile(compilertrace);
let changedexportassembler: str = testenv.readfile(assemblertrace);
if (testenv.occurrences(changedexportcompiler, strings.concat("<", work,
"/leaf.unit.new>")) != 2
|| testenv.occurrences(changedexportcompiler, strings.concat("<", work,
"/dep.unit.new>")) != 2
|| testenv.occurrences(changedexportcompiler, strings.concat("<", work,
"/main.unit.new>")) != 1
|| testenv.occurrences(changedexportcompiler, "\n") != 5
|| testenv.occurrences(changedexportassembler, "\n") != 6) {
fail("driver-identity", "changed export did not stop at an unchanged importer export");
};
if (code(cwd, strings.concat("driver-export-change-run-", tags[si]),
runav) != 42) {
fail("driver-identity", "export-change binary returned the wrong value");
};
let changedexportbin: str = testenv.readfile(bin);
let beforewarmcompiler: str = strings.dup(changedexportcompiler);
let beforewarmassembler: str = strings.dup(changedexportassembler);
testenv.runcommandenv(cwd, td, strings.concat("driver-warm-",
tags[si]), av, env, tmo(), &out);
if (out.termination != exec.termination.EXIT || out.code != 0
|| out.stderr.len != 0
|| !testenv.same(beforewarmcompiler, testenv.readfile(compilertrace))
|| !testenv.same(beforewarmassembler, testenv.readfile(assemblertrace))
|| testenv.occurrences(testenv.readfile(linkertrace), "\n") != 3
|| !testenv.same(rootunit, testenv.readfile(rootunitpath))
|| !testenv.same(changedexportbin, testenv.readfile(bin))) {
fail("driver-identity", "unchanged warm build did not reuse packages");
};
assert(os.remove(strings.concat(leaf, "/extra.ww")) == 0);
testenv.runcommandenv(cwd, td, strings.concat("driver-export-restore-",
tags[si]), av, env, tmo(), &out);
if (out.termination != exec.termination.EXIT || out.code != 0
|| out.stderr.len != 0
|| testenv.occurrences(testenv.readfile(compilertrace), strings.concat(
"<", work, "/leaf.unit.new>")) != 3
|| testenv.occurrences(testenv.readfile(compilertrace), strings.concat(
"<", work, "/dep.unit.new>")) != 3
|| testenv.occurrences(testenv.readfile(compilertrace), strings.concat(
"<", work, "/main.unit.new>")) != 1
|| testenv.occurrences(testenv.readfile(compilertrace), "\n") != 7
|| testenv.occurrences(testenv.readfile(assemblertrace), "\n") != 8
|| !testenv.same(coldbin, testenv.readfile(bin))) {
fail("driver-identity", "restored export did not rebuild only its direct importer");
};
assert(os.remove(copied[si]) == 0);
testenv.writeexecutable(copied[si], strings.concat(driverbytes,
"\nww driver identity mutation\n"));
testenv.runcommandenv(cwd, td, strings.concat("driver-changed-",
tags[si]), av, env, tmo(), &out);
if (out.termination != exec.termination.EXIT || out.code != 0
|| out.stderr.len != 0) {
fail("driver-identity", strings.concat(tags[si],
" changed-driver build failed: ", out.stderr));
};
let changedcompiler: str = testenv.readfile(compilertrace);
let changedassembler: str = testenv.readfile(assemblertrace);
let changedlinker: str = testenv.readfile(linkertrace);
if (testenv.occurrences(changedcompiler, strings.concat("<", work,
"/leaf.unit.new>")) != 4
|| testenv.occurrences(changedcompiler, strings.concat("<", work,
"/dep.unit.new>")) != 4
|| testenv.occurrences(changedcompiler, strings.concat("<", work,
"/main.unit.new>")) != 2
|| testenv.occurrences(changedcompiler, "\n") != 10
|| testenv.occurrences(changedassembler, "\n") != 12
|| testenv.occurrences(changedlinker, "\n") != 5) {
fail("driver-identity", "driver change reused stale package actions");
};
if (!testenv.same(testenv.readfile(strings.concat(work,
"/.wwtool.ww")), testenv.readfile(copied[si]))
|| !testenv.same(rootunit, testenv.readfile(rootunitpath))
|| !testenv.same(coldwwi, testenv.readfile(strings.concat(work,
"/dep.wwi")))
|| !testenv.same(coldarchive, testenv.readfile(strings.concat(work,
"/dep.a")))
|| !testenv.same(coldbin, testenv.readfile(bin))
|| code(cwd, strings.concat("driver-changed-run-", tags[si]),
runav) != 42) {
fail("driver-identity", "driver invalidation changed package products");
};
let beforecompiler: str = strings.dup(changedcompiler);
let beforeassembler: str = strings.dup(changedassembler);
let beforelinker: str = strings.dup(changedlinker);
let missingwork: str = strings.concat(td, "/missing workdir");
let missingbin: str = strings.concat(td, "/missing binary");
let badav: []str = [copied[si], "build", "-w", missingwork,
"-o", missingbin, "main"];
testenv.runcommandenv(cwd, td, strings.concat("driver-missing-",
tags[si]), badav, env, tmo(), &out);
let wantdiag: str = strings.concat("ww: workdir ", missingwork,
" is not a directory\n");
if (out.termination != exec.termination.EXIT || out.code != 1
|| !testenv.same(out.stderr, wantdiag)
|| !testenv.same(beforecompiler, testenv.readfile(compilertrace))
|| !testenv.same(beforeassembler, testenv.readfile(assemblertrace))
|| !testenv.same(beforelinker, testenv.readfile(linkertrace))
|| testenv.exists(missingbin)) {
fail("driver-identity", "invalid workdir crossed the tool boundary");
};
if (si == 0) {
referenceunit = strings.dup(rootunit);
referencewwi = strings.dup(coldwwi);
referencearchive = strings.dup(coldarchive);
referencebin = strings.dup(coldbin);
referencecompiler = strings.dup(changedcompiler);
referenceassembler = strings.dup(changedassembler);
referencelinker = strings.dup(changedlinker);
referenceerror = strings.dup(out.stderr);
} else { if (!testenv.same(referenceunit, rootunit)
|| !testenv.same(referencewwi, coldwwi)
|| !testenv.same(referencearchive, coldarchive)
|| !testenv.same(referencebin, coldbin)
|| !testenv.same(referencecompiler, changedcompiler)
|| !testenv.same(referenceassembler, changedassembler)
|| !testenv.same(referencelinker, changedlinker)
|| !testenv.same(referenceerror, out.stderr)) {
fail("driver-identity", "Cstage and WWstage warm behavior differs");
}; };
testenv.clean(work);
assert(os.remove(bin) == 0);
if (si + 1 < stages.len) { mkdir(work); };
si += 1;
};
testenv.clean(td);
};
@test fn builtin_type_precedence() void = {
let td: str = testenv.fresh();
let dep: str = strings.concat(td, "/builtinshadow"); mkdir(dep);
let main: str = strings.concat(td, "/main"); mkdir(main);
testenv.writefile(strings.concat(dep, "/dep.ww"), strings.concat(
"package builtinshadow;\n",
"export type i32 = i8;\n",
"export fn width() i32 = { return size(i32): i32; };\n"));
testenv.writefile(strings.concat(main, "/main.ww"), strings.concat(
"package main;\nimport builtinshadow;\n",
"fn main() i32 = { return builtinshadow.width(); };\n"));
let drivers: []str = ["ww", "ww_ww"];
let tags: []str = ["c", "w"];
let outputs: []str = ["", ""];
let i: i32 = 0;
for (i < drivers.len) {
outputs[i] = strings.concat(td, "/builtin-", tags[i]);
if (build(td, drivers[i], strings.concat("builtin_", tags[i]),
main, outputs[i]) != 0) {
fail("builtin-type", strings.concat(drivers[i], " build failed"));
};
let av: []str = [outputs[i]];
if (code(td, strings.concat("run_builtin_", tags[i]), av) != 4) {
fail("builtin-type", strings.concat(drivers[i],
" let an imported type shadow intrinsic i32"));
};
i += 1;
};
if (!testenv.same(testenv.readfile(outputs[0]),
testenv.readfile(outputs[1]))) {
fail("builtin-type", "C/WW products differ");
};
testenv.clean(td);
};
@test fn export_visibility() void = {
let td: str = testenv.fresh();
let main: str = writediamond(td, false);
// The successful diamond already proves a direct exported declaration.
testenv.writefile(strings.concat(td, "/private.ww"), strings.concat(
"package main;\nimport shared;\n",
"fn main() i32 = { return shared.hidden(); };\n"));
rejectstable(td, "private", strings.concat(td, "/private.ww"),
"has no exported declaration 'hidden'");
testenv.writefile(strings.concat(td, "/transitive.ww"), strings.concat(
"package main;\nimport left;\n",
"fn main() i32 = { return shared.base(); };\n"));
rejectstable(td, "transitive", strings.concat(td, "/transitive.ww"),
"undefined: shared");
testenv.writefile(strings.concat(td, "/transitive-bare-value.ww"),
strings.concat("package main;\nimport left;\n",
"fn main() i32 = { return base(); };\n"));
rejectstable(td, "transitive-bare-value",
strings.concat(td, "/transitive-bare-value.ww"), "undefined: base");
testenv.writefile(strings.concat(td, "/transitive-bare-type.ww"),
strings.concat("package main;\nimport left;\n",
"fn main() i32 = { let x: token; return 0; };\n"));
rejectstable(td, "transitive-bare-type",
strings.concat(td, "/transitive-bare-type.ww"),
"unknown type 'token'");
// Keep the helper's main directory live so the graph also has a normal
// successful consumer in this fixture tree.
assert(testenv.exists(main));
testenv.clean(td);
};
@test fn import_diagnostics() void = {
let td: str = testenv.fresh();
let missing: str = strings.concat(td, "/missing"); mkdir(missing);
testenv.writefile(strings.concat(missing, "/main.ww"), strings.concat(
"package main;\nimport nowhere;\n",
"fn main() i32 = { return 0; };\n"));
rejectstable(td, "missing", missing, "cannot find package nowhere");
let self: str = strings.concat(td, "/self"); mkdir(self);
testenv.writefile(strings.concat(self, "/main.ww"), strings.concat(
"package main;\nimport self;\n",
"fn main() i32 = { return 0; };\n"));
rejectstable(td, "self", self, "self-import");
let malformed: str = strings.concat(td, "/malformed"); mkdir(malformed);
testenv.writefile(strings.concat(malformed, "/main.ww"), strings.concat(
"package main;\nimport ;\n",
"fn main() i32 = { return 0; };\n"));
rejectstable(td, "malformed", malformed,
"main.ww:2:8: error: expected identifier");
let attributed: str = strings.concat(td, "/attributed"); mkdir(attributed);
testenv.writefile(strings.concat(attributed, "/main.ww"), strings.concat(
"package main;\n@trace import nowhere;\n",
"fn main() i32 = { return 0; };\n"));
rejectstable(td, "attributed", attributed,
"import cannot be exported or attributed");
let exported: str = strings.concat(td, "/exported"); mkdir(exported);
testenv.writefile(strings.concat(exported, "/main.ww"), strings.concat(
"package main;\nexport import nowhere;\n",
"fn main() i32 = { return 0; };\n"));
rejectstable(td, "exported", exported,
"main.ww:2:8: error: import cannot be exported or attributed");
let conflict: str = strings.concat(td, "/conflict"); mkdir(conflict);
testenv.writefile(strings.concat(conflict, "/a.ww"),
"package main;\nfn main() i32 = { return 0; };\n");
testenv.writefile(strings.concat(conflict, "/z.ww"),
"package other;\nfn spare() void = { };\n");
rejectstable(td, "conflict", conflict, "conflicting package names");
let real: str = strings.concat(td, "/real"); mkdir(real);
testenv.writefile(strings.concat(real, "/real.ww"), strings.concat(
"package real;\n",
"export fn value() i32 = { return 1; };\n"));
let alias: str = strings.concat(td, "/alias");
assert(os.symlink(real, alias) == 0);
let aliasmain: str = strings.concat(td, "/alias-main"); mkdir(aliasmain);
testenv.writefile(strings.concat(aliasmain, "/main.ww"), strings.concat(
"package main;\nimport real;\nimport alias;\n",
"fn main() i32 = { return 0; };\n"));
rejectstable(td, "identity-alias", aliasmain, "identities");
testenv.clean(td);
};
@test fn package_artifact_identity_and_flag_contract() void = {
let td: str = testenv.fresh();
let root: str = strings.concat(td, "/root"); mkdir(root);
let foo: str = strings.concat(root, "/foo"); mkdir(foo);
let bar: str = strings.concat(foo, "/bar"); mkdir(bar);
testenv.writefile(strings.concat(bar, "/bar.ww"), strings.concat(
"package bar;\n",
"export fn nestedvalue() i32 = { return 41; };\n"));
let ca: str = strings.concat(td, "/nested-c.a");
let wa: str = strings.concat(td, "/nested-w.a");
let cav: []str = [testenv.driver("ww"), "build", "-I", root,
"-o", ca, "foo.bar"];
let wav: []str = [testenv.driver("ww_ww"), "build", "-I", root,
"-o", wa, "foo.bar"];
if (code(td, "nested_package_c", cav) != 0
|| code(td, "nested_package_w", wav) != 0) {
fail("package-identity", "nested logical package build failed");
};
if (!testenv.same(testenv.readfile(ca), testenv.readfile(wa))
|| !testenv.same(testenv.readfile(strings.concat(ca, ".wwi")),
testenv.readfile(strings.concat(wa, ".wwi")))) {
fail("package-identity", "nested C/WW package products differ");
};
let nestedasm: str = testenv.readfile(strings.concat(ca,
".sepwork/foo.bar.s"));
if (!testenv.has(nestedasm, "foo.bar.nestedvalue")) {
fail("package-identity", "archive lost the full logical import path");
};
let drivers: []str = ["ww", "ww_ww"];
let i: i32 = 0;
for (i < drivers.len) {
let badout: str = strings.concat(td, "/bad-flags-", drivers[i]);
let av: []str = [testenv.driver(drivers[i]), "build", "-p",
"-I", root, "-o", badout, "foo.bar"];
let co: testenv.commandout;
command(td, strings.concat("package_flag_contract_", drivers[i]),
av, &co);
if (co.termination != exec.termination.EXIT || co.code == 0
|| !testenv.has(co.stderr, "ww build: unknown flag")) {
fail("package-flags", strings.concat(drivers[i],
" accepted the removed package-action flag"));
};
i += 1;
};
testenv.clean(td);
};
@test fn package_kind_selects_build_action() void = {
let td: str = testenv.fresh();
let source: str = strings.concat(td, "/source");
let foo: str = strings.concat(source, "/foo");
let dep: str = strings.concat(foo, "/dep");
let bar: str = strings.concat(foo, "/bar");
let commanddir: str = strings.concat(foo, "/cmd");
let cyclea: str = strings.concat(foo, "/cyclea");
let cycleb: str = strings.concat(foo, "/cycleb");
let tools: str = strings.concat(td, "/tools");
mkdir(source); mkdir(foo); mkdir(dep); mkdir(bar); mkdir(commanddir);
mkdir(cyclea); mkdir(cycleb); mkdir(tools);
let depsrc: str = strings.concat(dep, "/dep.ww");
let depbase: str = strings.concat(
"package dep;\n",
"// DEP_SOURCE\n",
"fn hidden() i32 = { return 40; };\n",
"export fn value() i32 = { return hidden(); };\n");
let depprivate: str = strings.concat(
"package dep;\n",
"// DEP_SOURCE\n",
"fn hidden() i32 = { return 41; };\n",
"export fn value() i32 = { return hidden(); };\n");
let depexported: str = strings.concat(depprivate,
"export fn additional() i32 = { return 1; };\n");
testenv.writefile(depsrc, depbase);
testenv.writefile(strings.concat(bar, "/a.ww"), strings.concat(
"package bar;\nimport foo.dep;\n",
"// ROOT_A\n",
"export fn value() i32 = { return dep.value() + 2; };\n"));
testenv.writefile(strings.concat(bar, "/z.ww"), strings.concat(
"package bar;\n",
"// ROOT_Z\n",
"export fn other() i32 = { return 9; };\n"));
testenv.writefile(strings.concat(commanddir, "/main.ww"), strings.concat(
"package main;\nimport foo.bar;\n",
"// COMMAND_OWNER\n",
"fn main() i32 = { return bar.value(); };\n"));
testenv.writefile(strings.concat(cyclea, "/a.ww"), strings.concat(
"package cyclea;\nimport foo.cycleb;\n",
"export fn value() i32 = { return cycleb.value(); };\n"));
testenv.writefile(strings.concat(cycleb, "/b.ww"), strings.concat(
"package cycleb;\nimport foo.cyclea;\n",
"export fn value() i32 = { return cyclea.value(); };\n"));
let compiler: str = strings.concat(tools, "/w6c.sh");
let assembler: str = strings.concat(tools, "/w6a.sh");
let linker: str = strings.concat(tools, "/w6l.sh");
testenv.writeexecutable(compiler, strings.concat(
"#!/bin/sh\n",
"printf 'BEGIN' >> \"$WW_ROOT_COMPILER_TRACE\"\n",
"for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ",
"\"$WW_ROOT_COMPILER_TRACE\"; done\n",
"printf '\\n' >> \"$WW_ROOT_COMPILER_TRACE\"\n",
"exec \"$WW_ROOT_REAL_COMPILER\" \"$@\"\n"));
testenv.writeexecutable(assembler, strings.concat(
"#!/bin/sh\n",
"printf 'BEGIN' >> \"$WW_ROOT_ASSEMBLER_TRACE\"\n",
"for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ",
"\"$WW_ROOT_ASSEMBLER_TRACE\"; done\n",
"printf '\\n' >> \"$WW_ROOT_ASSEMBLER_TRACE\"\n",
"exec \"$WW_ROOT_REAL_ASSEMBLER\" \"$@\"\n"));
testenv.writeexecutable(linker, strings.concat(
"#!/bin/sh\n",
"printf 'BEGIN' >> \"$WW_ROOT_LINKER_TRACE\"\n",
"for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ",
"\"$WW_ROOT_LINKER_TRACE\"; done\n",
"printf '\\n' >> \"$WW_ROOT_LINKER_TRACE\"\n",
"exec \"$WW_ROOT_REAL_LINKER\" \"$@\"\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 suffixes: []str = [".unit.ww", ".wwi", ".s", ".o", ".a"];
let initsuffixes: []str = [".init.unit.ww", ".init.s", ".init.o"];
let rootreference: []str = ["", "", "", "", ""];
let depreference: []str = ["", "", "", "", ""];
let commandreference: []str = ["", "", "", "", ""];
let commandinitreference: []str = ["", "", ""];
let nooutrootreference: []str = ["", "", "", "", ""];
let nooutdepreference: []str = ["", "", "", "", ""];
let publishreference: str = "";
let exportreference: str = "";
let binaryreference: str = "";
let diagnosticreference: str = "";
let cyclediagreference: str = "";
let cyclepathdiagreference: str = "";
let pathdiagreference: str = "";
let runtime: str = strings.concat(testenv.repo(), "/out/lib");
let si: i32 = 0;
for (si < stages.len) {
let work: str = strings.concat(td, "/work-", tags[si]);
let outpath: str = strings.concat(td, "/libbar-", tags[si], ".a");
let compilertrace: str = strings.concat(td, "/compiler-", tags[si]);
let assemblertrace: str = strings.concat(td, "/assembler-", tags[si]);
let linkertrace: str = strings.concat(td, "/linker-", tags[si]);
mkdir(work);
testenv.writefile(compilertrace, "");
testenv.writefile(assemblertrace, "");
testenv.writefile(linkertrace, "");
let env: []str = roottraceenv(source, runtime,
compiler, assembler, linker,
compilertrace, assemblertrace, linkertrace,
testenv.driver(compilers[si]),
testenv.driver(assemblers[si]),
testenv.driver(linkers[si]));
let av: []str = [testenv.driver(stages[si]), "build",
"-w", work, "-I", source, "-o", outpath, "foo.bar"];
let out: testenv.commandout;
testenv.runcommandenv(td, td, strings.concat("auto-cold-", tags[si]),
av, env, tmo(), &out);
if (out.termination != exec.termination.EXIT || out.code != 0
|| out.stderr.len != 0) {
fail("package-autoaction", strings.concat(tags[si],
" cold library build failed: ", out.stderr));
};
let ctrace: str = testenv.readfile(compilertrace);
let atrace: str = testenv.readfile(assemblertrace);
let wantcompiler: str = strings.concat(
"BEGIN<--package-init-symbol>",
"<__ww..pkg.p.foo.dep.v0.r0.init><-c><-I><", work,
"/foo.dep.wwi.new><-o><", work,
"/foo.dep.s.new><", work, "/foo.dep.unit.new>\n",
"BEGIN<--package-init-symbol>",
"<__ww..pkg.p.foo.bar.v0.r0.init><-c>",
"<--import><foo.dep><", work,
"/foo.dep.wwi.new><-I><", work,
"/foo.bar.wwi.new><-o><", work,
"/foo.bar.s.new><", work, "/foo.bar.unit.new>\n");
let wantassembler: str = strings.concat(
"BEGIN<-o><", work, "/foo.dep.o.new><", work,
"/foo.dep.s.new>\n",
"BEGIN<-o><", work, "/foo.bar.o.new><", work,
"/foo.bar.s.new>\n");
if (!testenv.same(ctrace, wantcompiler)
|| !testenv.same(atrace, wantassembler)
|| testenv.readfile(linkertrace).len != 0) {
fail("package-autoaction",
"library action tool arguments are not compile-only/direct");
};
let rootunit: str = testenv.readfile(strings.concat(work,
"/foo.bar.unit.ww"));
if (testenv.pos(rootunit, "// ROOT_A") < 0
|| testenv.pos(rootunit, "// ROOT_Z")
<= testenv.pos(rootunit, "// ROOT_A")
|| testenv.has(rootunit, "DEP_SOURCE")
|| testenv.occurrences(rootunit,
"//ww:module-reset foo.bar\n") != 2) {
fail("package-autoaction",
"library unit is not owner-only and byte-sorted");
};
if (!testenv.exists(outpath)
|| !testenv.exists(strings.concat(outpath, ".wwi"))
|| !testenv.same(testenv.readfile(outpath),
testenv.readfile(strings.concat(work, "/foo.bar.a")))
|| !testenv.same(testenv.readfile(strings.concat(outpath, ".wwi")),
testenv.readfile(strings.concat(work, "/foo.bar.wwi")))) {
fail("package-autoaction", "explicit package publication changed bytes");
};
let ai: i32 = 0;
for (ai < suffixes.len) {
let rootbytes: str = testenv.readfile(strings.concat(work,
"/foo.bar", suffixes[ai]));
let depbytes: str = testenv.readfile(strings.concat(work,
"/foo.dep", suffixes[ai]));
if (si == 0) {
rootreference[ai] = strings.dup(rootbytes);
depreference[ai] = strings.dup(depbytes);
} else { if (!testenv.same(rootreference[ai], rootbytes)
|| !testenv.same(depreference[ai], depbytes)) {
fail("package-autoaction",
"Cstage and WWstage package artifacts differ");
}; };
ai += 1;
};
if (si == 0) {
publishreference = strings.dup(testenv.readfile(outpath));
exportreference = strings.dup(testenv.readfile(strings.concat(
outpath, ".wwi")));
} else { if (!testenv.same(publishreference,
testenv.readfile(outpath)) || !testenv.same(exportreference,
testenv.readfile(strings.concat(outpath, ".wwi")))) {
fail("package-autoaction", "published stage artifacts differ");
}; };
// The other AutoAction half remains declaration-selected: package main
// alone receives --entry and its complete archive-only link closure.
testenv.clean(compilertrace); testenv.writefile(compilertrace, "");
testenv.clean(assemblertrace); testenv.writefile(assemblertrace, "");
testenv.clean(linkertrace); testenv.writefile(linkertrace, "");
let commandwork: str = strings.concat(td, "/command-work-", tags[si]);
let commandbin: str = strings.concat(td, "/command-", tags[si]);
mkdir(commandwork);
let commandav: []str = [testenv.driver(stages[si]), "build",
"-w", commandwork, "-I", source, "-o", commandbin, "foo.cmd"];
testenv.runcommandenv(td, td, strings.concat("auto-command-", tags[si]),
commandav, env, tmo(), &out);
ctrace = testenv.readfile(compilertrace);
atrace = testenv.readfile(assemblertrace);
let commandlink: str = testenv.readfile(linkertrace);
let wantcommandcompiler: str = strings.concat(
"BEGIN<--package-init-symbol>",
"<__ww..pkg.p.foo.dep.v0.r0.init><-c><-I><", commandwork,
"/foo.dep.wwi.new><-o><", commandwork,
"/foo.dep.s.new><", commandwork, "/foo.dep.unit.new>\n",
"BEGIN<--package-init-symbol>",
"<__ww..pkg.p.foo.bar.v0.r0.init><-c>",
"<--import><foo.dep><", commandwork,
"/foo.dep.wwi.new><-I><", commandwork,
"/foo.bar.wwi.new><-o><", commandwork,
"/foo.bar.s.new><", commandwork, "/foo.bar.unit.new>\n",
"BEGIN<--entry><--package-init-symbol>",
"<__ww..pkg.p.foo.cmd.v0.r0.init><--init-dispatch-symbol>",
"<__ww..dispatch><-c><--import><foo.bar><", commandwork,
"/foo.bar.wwi.new><-I><", commandwork,
"/foo.cmd.wwi.new><-o><", commandwork,
"/foo.cmd.s.new><", commandwork, "/foo.cmd.unit.new>\n");
let wantcommandassembler: str = strings.concat(
"BEGIN<-o><", commandwork, "/foo.dep.o.new><", commandwork,
"/foo.dep.s.new>\n",
"BEGIN<-o><", commandwork, "/foo.bar.o.new><", commandwork,
"/foo.bar.s.new>\n",
"BEGIN<-o><", commandwork, "/foo.cmd.o.new><", commandwork,
"/foo.cmd.s.new>\n",
"BEGIN<-o><", commandwork, "/foo.cmd.init.o.new><", commandwork,
"/foo.cmd.init.s.new>\n");
let wantcommandlink: str = strings.concat("BEGIN<-o><", commandbin,
".new><", commandwork, "/foo.cmd.a.new><", commandwork,
"/foo.bar.a.new><", commandwork, "/foo.dep.a.new><", runtime,
"/libwwrt.a>\n");
if (out.termination != exec.termination.EXIT || out.code != 0
|| out.stderr.len != 0
|| !testenv.same(ctrace, wantcommandcompiler)
|| !testenv.same(atrace, wantcommandassembler)
|| !testenv.same(commandlink, wantcommandlink)
|| testenv.has(commandlink, ".wwi>")) {
fail("package-autoaction",
"main action arguments or archive closure changed");
};
let commandunit: str = testenv.readfile(strings.concat(commandwork,
"/foo.cmd.unit.ww"));
if (!testenv.has(commandunit, "// COMMAND_OWNER")
|| testenv.has(commandunit, "// ROOT_A")
|| testenv.has(commandunit, "// DEP_SOURCE")) {
fail("package-autoaction", "command unit contains foreign sources");
};
ai = 0;
for (ai < suffixes.len) {
let commandbytes: str = testenv.readfile(strings.concat(commandwork,
"/foo.cmd", suffixes[ai]));
if (si == 0) { commandreference[ai] = strings.dup(commandbytes); }
else { if (!testenv.same(commandreference[ai], commandbytes)) {
fail("package-autoaction",
"Cstage and WWstage command artifacts differ");
}; };
ai += 1;
};
ai = 0;
for (ai < initsuffixes.len) {
let initbytes: str = testenv.readfile(strings.concat(commandwork,
"/foo.cmd", initsuffixes[ai]));
if (si == 0) {
commandinitreference[ai] = strings.dup(initbytes);
} else { if (!testenv.same(commandinitreference[ai], initbytes)) {
fail("package-autoaction",
"Cstage and WWstage dispatcher artifacts differ");
}; };
ai += 1;
};
let commandrun: []str = [commandbin];
if (code(td, strings.concat("auto-command-run-", tags[si]),
commandrun) != 42) {
fail("package-autoaction", "main action binary returned wrong value");
};
// The public run command must make the same declaration-driven choice;
// its private temp paths make exact argv intentionally inapplicable here.
let drivercommandrun: []str = [testenv.driver(stages[si]), "run",
"-I", source, "foo.cmd"];
testenv.runcommand(td, td, strings.concat("auto-driver-command-run-",
tags[si]), drivercommandrun, tmo(), &out);
if (out.termination != exec.termination.EXIT || out.code != 42
|| out.stdout.len != 0 || out.stderr.len != 0) {
fail("package-autoaction", "ww run rejected or misran package main");
};
if (si == 0) {
binaryreference = strings.dup(testenv.readfile(commandbin));
} else { if (!testenv.same(binaryreference,
testenv.readfile(commandbin))) {
fail("package-autoaction", "linked stage binaries differ");
}; };
testenv.clean(compilertrace); testenv.writefile(compilertrace, "");
testenv.clean(assemblertrace); testenv.writefile(assemblertrace, "");
testenv.clean(linkertrace); testenv.writefile(linkertrace, "");
let nooutwork: str = strings.concat(td, "/no-output-work-", tags[si]);
mkdir(nooutwork);
let noout: []str = [testenv.driver(stages[si]), "build",
"-w", nooutwork, "-I", source, "foo.bar"];
testenv.runcommandenv(td, td, strings.concat("auto-no-output-", tags[si]),
noout, env, tmo(), &out);
ctrace = testenv.readfile(compilertrace);
atrace = testenv.readfile(assemblertrace);
let wantnooutcompiler: str = strings.concat(
"BEGIN<--package-init-symbol>",
"<__ww..pkg.p.foo.dep.v0.r0.init><-c><-I><", nooutwork,
"/foo.dep.wwi.new><-o><", nooutwork,
"/foo.dep.s.new><", nooutwork, "/foo.dep.unit.new>\n",
"BEGIN<--package-init-symbol>",
"<__ww..pkg.p.foo.bar.v0.r0.init><-c>",
"<--import><foo.dep><", nooutwork,
"/foo.dep.wwi.new><-I><", nooutwork,
"/foo.bar.wwi.new><-o><", nooutwork,
"/foo.bar.s.new><", nooutwork, "/foo.bar.unit.new>\n");
let wantnooutassembler: str = strings.concat(
"BEGIN<-o><", nooutwork, "/foo.dep.o.new><", nooutwork,
"/foo.dep.s.new>\n",
"BEGIN<-o><", nooutwork, "/foo.bar.o.new><", nooutwork,
"/foo.bar.s.new>\n");
if (out.termination != exec.termination.EXIT || out.code != 0
|| out.stderr.len != 0 || testenv.exists(strings.concat(td, "/bar"))
|| !testenv.same(ctrace, wantnooutcompiler)
|| !testenv.same(atrace, wantnooutassembler)
|| testenv.readfile(linkertrace).len != 0) {
fail("package-autoaction",
"cold no-output library build did not remain compile-only");
};
ai = 0;
for (ai < suffixes.len) {
let nooutroot: str = testenv.readfile(strings.concat(nooutwork,
"/foo.bar", suffixes[ai]));
let nooutdep: str = testenv.readfile(strings.concat(nooutwork,
"/foo.dep", suffixes[ai]));
if (!testenv.same(nooutroot, rootreference[ai])
|| !testenv.same(nooutdep, depreference[ai])) {
fail("package-autoaction",
"publication request changed package artifact bytes");
};
if (si == 0) {
nooutrootreference[ai] = strings.dup(nooutroot);
nooutdepreference[ai] = strings.dup(nooutdep);
} else { if (!testenv.same(nooutrootreference[ai], nooutroot)
|| !testenv.same(nooutdepreference[ai], nooutdep)) {
fail("package-autoaction",
"cold no-output artifacts differ by stage");
}; };
ai += 1;
};
// An explicitly empty -o is observably the same no-publication request
// in both drivers and therefore reuses the same semantic actions.
testenv.clean(compilertrace); testenv.writefile(compilertrace, "");
testenv.clean(assemblertrace); testenv.writefile(assemblertrace, "");
testenv.clean(linkertrace); testenv.writefile(linkertrace, "");
let emptyout: []str = [testenv.driver(stages[si]), "build",
"-w", nooutwork, "-I", source, "-o", "", "foo.bar"];
testenv.runcommandenv(td, td, strings.concat("auto-empty-output-",
tags[si]), emptyout, env, tmo(), &out);
if (out.termination != exec.termination.EXIT || out.code != 0
|| out.stderr.len != 0
|| testenv.readfile(compilertrace).len != 0
|| testenv.readfile(assemblertrace).len != 0
|| testenv.readfile(linkertrace).len != 0) {
fail("package-autoaction",
"empty -o changed no-publication action semantics");
};
// Publication is not semantic action identity: an already-populated
// package workdir is reusable when the next request omits -o.
testenv.clean(compilertrace); testenv.writefile(compilertrace, "");
testenv.clean(assemblertrace); testenv.writefile(assemblertrace, "");
testenv.clean(linkertrace); testenv.writefile(linkertrace, "");
let warmnoout: []str = [testenv.driver(stages[si]), "build",
"-w", work, "-I", source, "foo.bar"];
testenv.runcommandenv(td, td, strings.concat("auto-warm-no-output-",
tags[si]), warmnoout, env, tmo(), &out);
if (out.termination != exec.termination.EXIT || out.code != 0
|| out.stderr.len != 0
|| testenv.readfile(compilertrace).len != 0
|| testenv.readfile(assemblertrace).len != 0
|| testenv.readfile(linkertrace).len != 0) {
fail("package-autoaction",
"publication request entered persistent action identity");
};
testenv.clean(compilertrace); testenv.writefile(compilertrace, "");
testenv.clean(assemblertrace); testenv.writefile(assemblertrace, "");
testenv.clean(linkertrace); testenv.writefile(linkertrace, "");
let pathwork: str = strings.concat(td, "/path-work-", tags[si]);
mkdir(pathwork);
let longout: str = strings.concat(td, "/");
for (longout.len < 4090) { longout = strings.concat(longout, "x"); };
let pathav: []str = [testenv.driver(stages[si]), "build",
"-w", pathwork, "-I", source, "-o", longout, "foo.bar"];
testenv.runcommandenv(td, td, strings.concat("auto-output-path-", tags[si]),
pathav, env, tmo(), &out);
let wantpathdiag: str = "ww: package output path is too long\n";
if (out.termination != exec.termination.EXIT || out.code != 1
|| !testenv.same(out.stderr, wantpathdiag)
|| testenv.readfile(compilertrace).len != 0
|| testenv.readfile(assemblertrace).len != 0
|| testenv.readfile(linkertrace).len != 0
|| testenv.exists(strings.concat(pathwork, "/foo.dep.unit.ww"))
|| testenv.exists(strings.concat(pathwork, "/foo.bar.unit.ww"))) {
fail("package-autoaction",
"package publication path failed after the tool boundary");
};
if (si == 0) { pathdiagreference = strings.dup(out.stderr); }
else { if (!testenv.same(pathdiagreference, out.stderr)) {
fail("package-autoaction", "output path diagnostics differ by stage");
}; };
// Output publication is unused under -S, so even this spelling must not
// participate in an assembly-only action or its failure behavior.
testenv.clean(compilertrace); testenv.writefile(compilertrace, "");
testenv.clean(assemblertrace); testenv.writefile(assemblertrace, "");
testenv.clean(linkertrace); testenv.writefile(linkertrace, "");
let asmwork: str = strings.concat(td, "/asm-work-", tags[si]);
mkdir(asmwork);
let asmav: []str = [testenv.driver(stages[si]), "build", "-S",
"-w", asmwork, "-I", source, "-o", longout, "foo.bar"];
testenv.runcommandenv(td, td, strings.concat("auto-asm-long-output-",
tags[si]), asmav, env, tmo(), &out);
let wantasmcompiler: str = strings.concat(
"BEGIN<--package-init-symbol>",
"<__ww..pkg.p.foo.dep.v0.r0.init><-c><-I><", asmwork,
"/foo.dep.wwi.new><-o><", asmwork,
"/foo.dep.s.new><", asmwork, "/foo.dep.unit.new>\n",
"BEGIN<--package-init-symbol>",
"<__ww..pkg.p.foo.bar.v0.r0.init><-c>",
"<--import><foo.dep><", asmwork,
"/foo.dep.wwi.new><-I><", asmwork,
"/foo.bar.wwi.new><-o><", asmwork,
"/foo.bar.s.new><", asmwork, "/foo.bar.unit.new>\n");
if (out.termination != exec.termination.EXIT || out.code != 0
|| out.stderr.len != 0
|| !testenv.same(testenv.readfile(compilertrace), wantasmcompiler)
|| testenv.readfile(assemblertrace).len != 0
|| testenv.readfile(linkertrace).len != 0
|| testenv.exists(strings.concat(asmwork, "/foo.dep.o"))
|| testenv.exists(strings.concat(asmwork, "/foo.dep.a"))
|| testenv.exists(strings.concat(asmwork, "/foo.bar.o"))
|| testenv.exists(strings.concat(asmwork, "/foo.bar.a"))) {
fail("package-autoaction",
"assembly-only action consumed an unused publication path");
};
testenv.clean(compilertrace); testenv.writefile(compilertrace, "");
testenv.clean(assemblertrace); testenv.writefile(assemblertrace, "");
testenv.clean(linkertrace); testenv.writefile(linkertrace, "");
let runav: []str = [testenv.driver(stages[si]), "run",
"-I", source, "foo.bar"];
testenv.runcommandenv(td, td, strings.concat("auto-run-reject-", tags[si]),
runav, env, tmo(), &out);
let wantdiag: str = "ww: package foo.bar is not a main package\n";
if (out.termination != exec.termination.EXIT || out.code != 1
|| !testenv.same(out.stderr, wantdiag)
|| testenv.readfile(compilertrace).len != 0
|| testenv.readfile(assemblertrace).len != 0
|| testenv.readfile(linkertrace).len != 0) {
fail("package-autoaction", "run crossed the non-main tool boundary");
};
if (si == 0) { diagnosticreference = strings.dup(out.stderr); }
else { if (!testenv.same(diagnosticreference, out.stderr)) {
fail("package-autoaction", "run diagnostics differ by stage");
}; };
let cyclerun: []str = [testenv.driver(stages[si]), "run",
"-I", source, "foo.cyclea"];
testenv.runcommandenv(td, td, strings.concat("auto-run-cycle-", tags[si]),
cyclerun, env, tmo(), &out);
if (out.termination != exec.termination.EXIT || out.code != 1
|| !testenv.has(out.stderr,
"ww: dependency cycle: foo.cyclea -> foo.cycleb -> foo.cyclea\n")
|| testenv.has(out.stderr, "is not a main package")
|| testenv.readfile(compilertrace).len != 0
|| testenv.readfile(assemblertrace).len != 0
|| testenv.readfile(linkertrace).len != 0) {
fail("package-autoaction",
"run command-kind validation masked a graph failure");
};
if (si == 0) { cyclediagreference = strings.dup(out.stderr); }
else { if (!testenv.same(cyclediagreference, out.stderr)) {
fail("package-autoaction", "run cycle diagnostics differ by stage");
}; };
let cyclepathwork: str = strings.concat(td, "/cycle-path-work-",
tags[si]);
mkdir(cyclepathwork);
let cyclepathav: []str = [testenv.driver(stages[si]), "build",
"-w", cyclepathwork, "-I", source, "-o", longout,
"foo.cyclea"];
testenv.runcommandenv(td, td, strings.concat("auto-cycle-path-", tags[si]),
cyclepathav, env, tmo(), &out);
if (out.termination != exec.termination.EXIT || out.code != 1
|| !testenv.has(out.stderr, "ww: dependency cycle: ")
|| !testenv.has(out.stderr, "foo.cyclea")
|| !testenv.has(out.stderr, "foo.cycleb")
|| testenv.has(out.stderr, "package output path is too long")
|| testenv.readfile(compilertrace).len != 0
|| testenv.readfile(assemblertrace).len != 0
|| testenv.readfile(linkertrace).len != 0) {
fail("package-autoaction",
strings.concat("publication path validation masked a graph failure: ",
out.stderr));
};
if (si == 0) { cyclepathdiagreference = strings.dup(out.stderr); }
else { if (!testenv.same(cyclepathdiagreference, out.stderr)) {
fail("package-autoaction",
"cycle/output-path diagnostic differs by stage");
}; };
assert(os.remove(depsrc) == 0);
testenv.writefile(depsrc, depprivate);
testenv.runcommandenv(td, td, strings.concat("auto-private-", tags[si]),
av, env, tmo(), &out);
ctrace = testenv.readfile(compilertrace);
atrace = testenv.readfile(assemblertrace);
let wantprivatecompiler: str = strings.concat(
"BEGIN<--package-init-symbol>",
"<__ww..pkg.p.foo.dep.v0.r0.init><-c><-I><", work,
"/foo.dep.wwi.new><-o><", work,
"/foo.dep.s.new><", work, "/foo.dep.unit.new>\n");
let wantprivateassembler: str = strings.concat(
"BEGIN<-o><", work, "/foo.dep.o.new><", work,
"/foo.dep.s.new>\n");
if (out.termination != exec.termination.EXIT || out.code != 0
|| !testenv.same(ctrace, wantprivatecompiler)
|| !testenv.same(atrace, wantprivateassembler)
|| testenv.readfile(linkertrace).len != 0
|| !testenv.same(rootreference[4], testenv.readfile(strings.concat(
work, "/foo.bar.a")))) {
fail("package-autoaction",
"private dependency change escaped its export boundary");
};
testenv.clean(compilertrace); testenv.writefile(compilertrace, "");
testenv.clean(assemblertrace); testenv.writefile(assemblertrace, "");
testenv.clean(linkertrace); testenv.writefile(linkertrace, "");
assert(os.remove(depsrc) == 0);
testenv.writefile(depsrc, depexported);
testenv.runcommandenv(td, td, strings.concat("auto-export-", tags[si]),
av, env, tmo(), &out);
ctrace = testenv.readfile(compilertrace);
atrace = testenv.readfile(assemblertrace);
let wantexportcompiler: str = strings.concat(
"BEGIN<--package-init-symbol>",
"<__ww..pkg.p.foo.dep.v0.r0.init><-c><-I><", work,
"/foo.dep.wwi.new><-o><", work,
"/foo.dep.s.new><", work, "/foo.dep.unit.new>\n",
"BEGIN<--package-init-symbol>",
"<__ww..pkg.p.foo.bar.v0.r0.init><-c>",
"<--import><foo.dep><", work,
"/foo.dep.wwi.new><-I><", work,
"/foo.bar.wwi.new><-o><", work,
"/foo.bar.s.new><", work, "/foo.bar.unit.new>\n");
let wantexportassembler: str = strings.concat(
"BEGIN<-o><", work, "/foo.dep.o.new><", work,
"/foo.dep.s.new>\n",
"BEGIN<-o><", work, "/foo.bar.o.new><", work,
"/foo.bar.s.new>\n");
if (out.termination != exec.termination.EXIT || out.code != 0
|| !testenv.same(ctrace, wantexportcompiler)
|| !testenv.same(atrace, wantexportassembler)
|| testenv.readfile(linkertrace).len != 0) {
fail("package-autoaction",
"exported dependency change did not reach its direct importer");
};
testenv.clean(compilertrace); testenv.writefile(compilertrace, "");
testenv.clean(assemblertrace); testenv.writefile(assemblertrace, "");
testenv.clean(linkertrace); testenv.writefile(linkertrace, "");
testenv.runcommandenv(td, td, strings.concat("auto-warm-", tags[si]),
av, env, tmo(), &out);
if (out.termination != exec.termination.EXIT || out.code != 0
|| out.stderr.len != 0
|| testenv.readfile(compilertrace).len != 0
|| testenv.readfile(assemblertrace).len != 0
|| testenv.readfile(linkertrace).len != 0) {
fail("package-autoaction", "warm library build did not fully reuse");
};
assert(os.remove(depsrc) == 0);
testenv.writefile(depsrc, depbase);
si += 1;
};
testenv.clean(td);
};
@test fn generated_test_hook_is_not_a_user_exemption() void = {
let td: str = testenv.fresh();
let src: str = strings.concat(td, "/case.ww");
let iface: str = strings.concat(td, "/test.wwi");
testenv.writefile(src, strings.concat(
"package probe;\nimport test;\n",
"fn bad() void = { test.run(); };\n",
"@test fn one() void = {};\n"));
testenv.writefile(iface, strings.concat(
"//ww:module test\n",
"package test;\n",
"export fn placeholder() void;\n"));
let compilers: []str = ["w6c", "w6c_ww"];
let i: i32 = 0;
for (i < compilers.len) {
let asm: str = strings.concat(td, "/case-", compilers[i], ".s");
let av: []str = [testenv.driver(compilers[i]), "-T", "-c",
"--import", "test", iface, "-o", asm, src];
let co: testenv.commandout;
command(td, strings.concat("test_hook_", compilers[i]), av, &co);
if (co.termination != exec.termination.EXIT || co.code == 0
|| !testenv.has(co.stderr,
"package 'test' has no exported declaration 'run'")) {
fail("test-hook", strings.concat(compilers[i],
" exempted a user-authored test.run"));
};
i += 1;
};
testenv.clean(td);
};
fn writecyclepkg(td: str, name: str, next: str) void = {
let dir: str = strings.concat(td, "/", name); mkdir(dir);
testenv.writefile(strings.concat(dir, "/p.ww"), strings.concat(
"package ", name, ";\nimport ", next, ";\n",
"export fn value() i32 = { return 1; };\n"));
};
@test fn cycle_diagnostics() void = {
let td: str = testenv.fresh();
let two: str = strings.concat(td, "/two-main"); mkdir(two);
testenv.writefile(strings.concat(two, "/main.ww"),
"package main;\nimport aa;\nfn main() i32 = { return 0; };\n");
writecyclepkg(td, "aa", "bb");
writecyclepkg(td, "bb", "aa");
rejectstable(td, "cycle-two", two,
"dependency cycle: aa -> bb -> aa");
rejectstable(td, "cycle-root", strings.concat(td, "/aa"),
"dependency cycle: aa -> bb -> aa");
// A second isolated root set avoids reusing the two-package graph.
let longroot: str = strings.concat(td, "/long-main"); mkdir(longroot);
testenv.writefile(strings.concat(longroot, "/main.ww"),
"package main;\nimport ca;\nfn main() i32 = { return 0; };\n");
writecyclepkg(td, "ca", "cb");
writecyclepkg(td, "cb", "cc");
writecyclepkg(td, "cc", "ca");
rejectstable(td, "cycle-long", longroot,
"dependency cycle: ca -> cb -> cc -> ca");
testenv.clean(td);
};