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><", 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><", 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 18 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><", 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><", 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><", 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><", 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><", 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><", 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><", 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><", 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); };