From cc6799c3b1f67fa659fe8fb40065bb51af4c04d9 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Thu, 13 Aug 2026 02:48:17 +0900 Subject: [PATCH] test: prove canonical directory package identity --- test/package/package_test.ww | 568 +++++++++++++++++++++++++++-------- test/sep/importdir_test.ww | 49 ++- test/sep/localbuild_test.ww | 22 +- test/testenv/testenv.ww | 33 ++ test/xmod/direnum_test.ww | 7 +- 5 files changed, 530 insertions(+), 149 deletions(-) diff --git a/test/package/package_test.ww b/test/package/package_test.ww index 5595216f..102d92d9 100644 --- a/test/package/package_test.ww +++ b/test/package/package_test.ww @@ -257,10 +257,42 @@ fn workescape(s: str) str = { return strings.frombytes(out); }; +fn localidentity(dir: str, leaf: str) str = { + let out: []u8 = alloc([], (dir.len * 4 + leaf.len + 16): u64)!; + let prefix: str = "__wwlocal.p"; + let i: i32 = 0; + for (i < prefix.len) { append(out, prefix[i]); i += 1; }; + let hex: str = "0123456789abcdef"; + i = 0; + for (i < dir.len) { + let c: u8 = dir[i]; + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9')) { + append(out, c); + } else if (c == '_') { + append(out, '_'); append(out, 'u'); + } else if (c == '/') { + append(out, '_'); append(out, 's'); + } else { + let high: i32 = (c / 16u8): i32; + let low: i32 = (c % 16u8): i32; + append(out, '_'); append(out, 'x'); + append(out, hex[high]); append(out, hex[low]); + }; + i += 1; + }; + append(out, '.'); + i = 0; + for (i < leaf.len) { append(out, leaf[i]); i += 1; }; + return strings.frombytes(out); +}; + @test fn deterministic_routing_and_filters() void = { let root: str = fresh(); let target: str = packagepath("routing"); - let av: []str = [driver("ww"), "test", "-list", target]; + let sourceroot: str = strings.concat(repo(), "/test/package"); + let av: []str = [driver("ww"), "test", "-I", sourceroot, + "-list", target]; let out: commandout; runcommand(root, "list", av, (30i64 * (time.second: i64)): time.duration, &out); @@ -273,7 +305,7 @@ fn workescape(s: str) str = { assert(occurrences(out.stdout, "routing.same_package\n") == 1); assert(occurrences(out.stdout, "routing_test.external_package\n") == 1); - let rav: []str = [driver("ww"), "test", target]; + let rav: []str = [driver("ww"), "test", "-I", sourceroot, target]; runcommand(root, "run", rav, (30i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -287,7 +319,7 @@ fn workescape(s: str) str = { let fav: []str = [driver("ww"), "test", "-run", "routing.private_helper_first", "-filter", "external_package", - target]; + "-I", sourceroot, target]; runcommand(root, "filters", fav, (30i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -295,7 +327,8 @@ fn workescape(s: str) str = { assert(!has(out.stdout, "routing.same_package ...")); assert(has(out.stdout, "routing_test.external_package ... ok\n")); - let nav: []str = [driver("ww"), "test", "-run", "no-such-*", target]; + let nav: []str = [driver("ww"), "test", "-I", sourceroot, + "-run", "no-such-*", target]; runcommand(root, "nomatch", nav, (30i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -306,7 +339,7 @@ fn workescape(s: str) str = { "2 discovered, 0 selected, 0 started, 0 completed\n") == 1); let lnav: []str = [driver("ww"), "test", "-list", "-run", - "no-such-*", target]; + "no-such-*", "-I", sourceroot, target]; runcommand(root, "list-nomatch", lnav, (30i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -594,17 +627,19 @@ fn workescape(s: str) str = { @test fn cstage_wwstage_behavior_and_assembly_match() void = { let root: str = fresh(); - writefile(strings.concat(root, "/route.ww"), + let route: str = strings.concat(root, "/route"); + assert(os.mkdir(route, 448i32) == 0); + writefile(strings.concat(route, "/route.ww"), "package route;\nexport fn value() int = { return 7; };\n"); - writefile(strings.concat(root, "/a_test.ww"), + writefile(strings.concat(route, "/a_test.ww"), "package route;\n@test fn white() void = { assert(value() == 7); };\n"); - writefile(strings.concat(root, "/z_test.ww"), + writefile(strings.concat(route, "/z_test.ww"), "package route_test;\nimport route;\n@test fn external() void = { assert(route.value() == 7); };\n"); let outc: commandout; let outw: commandout; - let listc: []str = [driver("ww"), "test", "-list", root]; - let listw: []str = [driver("ww_ww"), "test", "-list", root]; + let listc: []str = [driver("ww"), "test", "-I", root, "-list", route]; + let listw: []str = [driver("ww_ww"), "test", "-I", root, "-list", route]; runcommand(root, "list-c", listc, (30i64 * (time.second: i64)): time.duration, &outc); runcommand(root, "list-ww", listw, @@ -625,9 +660,9 @@ fn workescape(s: str) str = { assert(has(outc.stderr, "ww test: -run needs an argument\n")); let timeoutc: []str = [driver("ww"), "test", - "-timeout-ms=4000000", root]; + "-timeout-ms=4000000", "-I", root, route]; let timeoutw: []str = [driver("ww_ww"), "test", - "-timeout-ms=4000000", root]; + "-timeout-ms=4000000", "-I", root, route]; runcommand(root, "timeout-misuse-c", timeoutc, time.second, &outc); runcommand(root, "timeout-misuse-ww", timeoutw, time.second, &outw); expectexit(&outc, 2); @@ -636,31 +671,31 @@ fn workescape(s: str) str = { assert(same(outc.stderr, outw.stderr)); assert(has(outc.stderr, "usage: wwtest package")); - let cc: []str = [driver("ww"), "test", "-c", root]; + let cc: []str = [driver("ww"), "test", "-c", "-I", root, route]; runcommand(root, "compile-c", cc, (30i64 * (time.second: i64)): time.duration, &outc); expectexit(&outc, 0); - assert(has(outc.stdout, strings.concat(" -> ", root, "/route.test\n"))); + assert(has(outc.stdout, strings.concat(" -> ", route, "/route.test\n"))); assert(has(outc.stdout, - strings.concat(" -> ", root, "/route_test.test\n"))); - let cwhite: str = readfile(strings.concat(root, + strings.concat(" -> ", route, "/route_test.test\n"))); + let cwhite: str = readfile(strings.concat(route, "/route.test.sepwork/route-internal-test.s")); - let cexternal: str = readfile(strings.concat(root, + let cexternal: str = readfile(strings.concat(route, "/route.test.sepwork/route_test-external-test.s")); // The explicit package `-c` outputs are caller-owned artifacts. Release // the shared C-stage tree before asking the WW driver to acquire the same // stem; the driver never deletes a pre-existing `.sepwork` path. - clean(strings.concat(root, "/route.test.sepwork")); + clean(strings.concat(route, "/route.test.sepwork")); - let wc: []str = [driver("ww_ww"), "test", "-c", root]; + let wc: []str = [driver("ww_ww"), "test", "-c", "-I", root, route]; runcommand(root, "compile-ww", wc, (30i64 * (time.second: i64)): time.duration, &outw); expectexit(&outw, 0); assert(same(outc.stdout, outw.stdout)); assert(same(outc.stderr, outw.stderr)); - assert(same(cwhite, readfile(strings.concat(root, + assert(same(cwhite, readfile(strings.concat(route, "/route.test.sepwork/route-internal-test.s")))); - assert(same(cexternal, readfile(strings.concat(root, + assert(same(cexternal, readfile(strings.concat(route, "/route.test.sepwork/route_test-external-test.s")))); clean(root); }; @@ -777,6 +812,28 @@ fn workescape(s: str) str = { append(traceenv, strings.concat("WW_PACKAGE_LINKER_TRACE=", linkertrace)); append(traceenv, strings.concat("WW_PACKAGE_W6C=", driver("w6c"))); append(traceenv, strings.concat("WW_PACKAGE_W6L=", driver("w6l"))); + let wwtraceenv: []str = alloc([], (baseenv.len + 6): u64)!; + ei = 0; + for (ei < baseenv.len) { + if (!strings.hasprefix(baseenv[ei], "WW_W6C=") + && !strings.hasprefix(baseenv[ei], "WW_W6L=") + && !strings.hasprefix(baseenv[ei], + "WW_PACKAGE_COMPILER_TRACE=") + && !strings.hasprefix(baseenv[ei], + "WW_PACKAGE_LINKER_TRACE=") + && !strings.hasprefix(baseenv[ei], "WW_PACKAGE_W6C=") + && !strings.hasprefix(baseenv[ei], "WW_PACKAGE_W6L=")) { + append(wwtraceenv, baseenv[ei]); + }; + ei += 1; + }; + append(wwtraceenv, strings.concat("WW_W6C=", compilerwrapper)); + append(wwtraceenv, strings.concat("WW_W6L=", linkerwrapper)); + append(wwtraceenv, strings.concat("WW_PACKAGE_COMPILER_TRACE=", + compilertrace)); + append(wwtraceenv, strings.concat("WW_PACKAGE_LINKER_TRACE=", linkertrace)); + append(wwtraceenv, strings.concat("WW_PACKAGE_W6C=", driver("w6c_ww"))); + append(wwtraceenv, strings.concat("WW_PACKAGE_W6L=", driver("w6l_ww"))); let drivers: []str = ["ww", "ww", "ww_ww"]; let tags: []str = ["c1", "c2", "ww"]; let referenceout: str = ""; @@ -1012,8 +1069,7 @@ fn workescape(s: str) str = { let warmstdout: str = strings.dup(out.stdout); let warmstderr: str = strings.dup(out.stderr); let warmtrace: str = readfile(compilertrace); - let warmwork: str = strings.concat(warmroot, "/d_", workescape(pkg), - "_p_pkg/"); + let warmwork: str = strings.concat(warmroot, "/d_", workescape(pkg), "/"); assert(has(readfile(strings.concat(warmwork, "__same.unit.ww")), "package __same;")); assert(has(readfile(strings.concat(warmwork, @@ -1038,14 +1094,17 @@ fn workescape(s: str) str = { externalbin, strings.concat(root, "/order-external.status"), "--ww-package-test", "same", "pkg", pkg, samebin, strings.concat(root, "/order-same.status"), pkg]; + let cordertracebefore: str = strings.dup(readfile(compilertrace)); + let corderlinkbefore: str = strings.dup(readfile(linkertrace)); runcommandenv(root, "variant-order-reversed", orderav, traceenv, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); - let ordertrace: str = readfile(compilertrace); - let coldcounts: []i32 = [occurrences(ordertrace, "/pkg.unit.new"), - occurrences(ordertrace, "/pkg-internal-test.unit.new"), - occurrences(ordertrace, "/pkg_test-external-test.unit.new")]; - assert(coldcounts[0] == 2 && coldcounts[1] == 2 && coldcounts[2] == 2); + let cordertraceafter: str = strings.dup(readfile(compilertrace)); + let corderlinkafter: str = strings.dup(readfile(linkertrace)); + let creversedtrace: str = strings.dup(strings.sub(cordertraceafter, + cordertracebefore.len, cordertraceafter.len)); + let creversedlink: str = strings.dup(strings.sub(corderlinkafter, + corderlinkbefore.len, corderlinkafter.len)); let orderworkdir: str = strings.concat(orderwork, "/"); let orderactions: []str = ["pkg", "pkg-internal-test", "pkg_test-external-test", "pkg-internal-test-main", @@ -1062,6 +1121,30 @@ fn workescape(s: str) str = { }; oi += 1; }; + clean(orderwork); + assert(os.mkdir(orderwork, 448i32) == 0); + let orderforwardav: []str = [driver("ww"), "test", "-c", "-w", + orderwork, "-I", root, + "--ww-package-test", "same", "pkg", pkg, + samebin, strings.concat(root, "/order-same.status"), + "--ww-package-test", "external", "pkg_test", pkg, + externalbin, strings.concat(root, "/order-external.status"), pkg]; + let cforwardtracebefore: str = strings.dup(readfile(compilertrace)); + let cforwardlinkbefore: str = strings.dup(readfile(linkertrace)); + runcommandenv(root, "variant-order-forward", orderforwardav, traceenv, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let cforwardtraceafter: str = strings.dup(readfile(compilertrace)); + let cforwardlinkafter: str = strings.dup(readfile(linkertrace)); + assert(same(creversedtrace, strings.sub(cforwardtraceafter, + cforwardtracebefore.len, cforwardtraceafter.len))); + assert(same(creversedlink, strings.sub(cforwardlinkafter, + cforwardlinkbefore.len, cforwardlinkafter.len))); + let ordertrace: str = readfile(compilertrace); + let coldcounts: []i32 = [occurrences(ordertrace, "/pkg.unit.new"), + occurrences(ordertrace, "/pkg-internal-test.unit.new"), + occurrences(ordertrace, "/pkg_test-external-test.unit.new")]; + assert(coldcounts[0] == 3 && coldcounts[1] == 3 && coldcounts[2] == 3); let orderwwwork: str = strings.concat(root, "/order-ww-work"); let orderwwsame: str = strings.concat(root, "/order-ww-same.test"); let orderwwexternal: str = strings.concat(root, "/order-ww-external.test"); @@ -1072,9 +1155,36 @@ fn workescape(s: str) str = { orderwwexternal, strings.concat(root, "/order-ww-external.status"), "--ww-package-test", "same", "pkg", pkg, orderwwsame, strings.concat(root, "/order-ww-same.status"), pkg]; - runcommand(root, "variant-order-reversed-ww", orderwwav, + let wordertracebefore: str = strings.dup(readfile(compilertrace)); + let worderlinkbefore: str = strings.dup(readfile(linkertrace)); + runcommandenv(root, "variant-order-reversed-ww", orderwwav, wwtraceenv, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); + let wordertraceafter: str = strings.dup(readfile(compilertrace)); + let worderlinkafter: str = strings.dup(readfile(linkertrace)); + let wreversedtrace: str = strings.dup(strings.sub(wordertraceafter, + wordertracebefore.len, wordertraceafter.len)); + let wreversedlink: str = strings.dup(strings.sub(worderlinkafter, + worderlinkbefore.len, worderlinkafter.len)); + clean(orderwwwork); + assert(os.mkdir(orderwwwork, 448i32) == 0); + let orderwwforward: []str = [driver("ww_ww"), "test", "-c", "-w", + orderwwwork, "-I", root, + "--ww-package-test", "same", "pkg", pkg, + orderwwsame, strings.concat(root, "/order-ww-same.status"), + "--ww-package-test", "external", "pkg_test", pkg, + orderwwexternal, strings.concat(root, "/order-ww-external.status"), pkg]; + let wforwardtracebefore: str = strings.dup(readfile(compilertrace)); + let wforwardlinkbefore: str = strings.dup(readfile(linkertrace)); + runcommandenv(root, "variant-order-forward-ww", orderwwforward, wwtraceenv, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let wforwardtraceafter: str = strings.dup(readfile(compilertrace)); + let wforwardlinkafter: str = strings.dup(readfile(linkertrace)); + assert(same(wreversedtrace, strings.sub(wforwardtraceafter, + wforwardtracebefore.len, wforwardtraceafter.len))); + assert(same(wreversedlink, strings.sub(wforwardlinkafter, + wforwardlinkbefore.len, wforwardlinkafter.len))); oi = 0; for (oi < orderactions.len) { let suffixes: []str = [".unit.ww", ".wwi", ".a"]; @@ -1087,6 +1197,11 @@ fn workescape(s: str) str = { }; oi += 1; }; + let tracedorders: str = readfile(compilertrace); + coldcounts[0] = occurrences(tracedorders, "/pkg.unit.new"); + coldcounts[1] = occurrences(tracedorders, "/pkg-internal-test.unit.new"); + coldcounts[2] = occurrences(tracedorders, + "/pkg_test-external-test.unit.new"); runcommandenv(root, "variant-warm-reuse", warmav, traceenv, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -1611,13 +1726,15 @@ fn workescape(s: str) str = { let warmparallel: []str = [driver("ww"), "test", "-j", "4", "-w", warmroot, "-I", suite, "-I", root, strings.concat(suite, "/...")]; - runcommandenv(root, "multidir-warm-reuse", warmparallel, traceenv, + let warmequivalentc: []str = [driver("ww"), "test", "-j", "4", "-w", + warmroot, "-I", suite, "-I", root, + strings.concat(suite, "/./...")]; + runcommandenv(root, "multidir-warm-reuse", warmequivalentc, traceenv, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); - assert(same(warmstdout, out.stdout)); assert(same(warmstderr, out.stderr)); assert(same(warmtrace, readfile(compilertrace))); - let warmkey: str = strings.concat("/d_", workescape(suite), "_p_alpha/"); + let warmkey: str = strings.concat("/d_", workescape(suite), "/"); let warmwork: str = strings.concat(warmroot, warmkey); assert(os.exists(strings.concat(warmwork, "alpha-internal-test.unit.ww"))); @@ -1632,10 +1749,12 @@ fn workescape(s: str) str = { assert(same(warmstdout, out.stdout)); assert(same(warmstderr, out.stderr)); let warmwwtrace: str = strings.dup(readfile(wwcompilertrace)); - runcommandenv(root, "multidir-warm-ww-reuse", warmww, wwtraceenv, + let warmwwequivalent: []str = [wwtracedriver, "test", "-j", "4", "-w", + warmwwroot, "-I", suite, "-I", root, + strings.concat(suite, "/./...")]; + runcommandenv(root, "multidir-warm-ww-reuse", warmwwequivalent, wwtraceenv, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); - assert(same(warmstdout, out.stdout)); assert(same(warmstderr, out.stderr)); assert(same(warmwwtrace, readfile(wwcompilertrace))); let warmwwwork: str = strings.concat(warmwwroot, warmkey); @@ -1719,6 +1838,78 @@ fn workescape(s: str) str = { wwtraceenv, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); assert(same(wafterchange, readfile(wwcompilertrace))); + + // Exercise the inverse discovery order explicitly. aaroot is selected and + // loaded before zimporter, which later imports it; gamma above is first + // discovered through beta and only later visited as a selected root. Both + // orders must compile one production action, and a root-only build must emit + // byte-identical owner unit, export, and archive. + let aaroot: str = strings.concat(root, "/aaroot"); + let zimporter: str = strings.concat(root, "/zimporter"); + assert(os.mkdir(aaroot, 448i32) == 0); + assert(os.mkdir(zimporter, 448i32) == 0); + writefile(strings.concat(aaroot, "/aaroot.ww"), strings.concat( + "package aaroot;\n// ROOT_FIRST_OWNER\n", + "export fn value() i32 = { return 42; };\n")); + writefile(strings.concat(zimporter, "/zimporter.ww"), strings.concat( + "package zimporter;\nimport aaroot;\n", + "export fn value() i32 = { return aaroot.value(); };\n")); + let rootrefs: []str = ["", "", ""]; + let rootstages: []str = ["ww", "ww_ww"]; + let rsi: i32 = 0; + for (rsi < rootstages.len) { + let uniondriver: str = driver(rootstages[rsi]); + if (rsi == 1) { uniondriver = wwtracedriver; }; + let rootout: str = strings.concat(root, "/root-only-", + rootstages[rsi], ".a"); + let rootav: []str = [driver(rootstages[rsi]), "build", "-p", + "-I", root, "-o", rootout, aaroot]; + runcommand(root, strings.concat("root-only-", rootstages[rsi]), rootav, + (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let rootwork: str = strings.concat(rootout, ".sepwork/"); + let depbin: str = strings.concat(root, "/root-first-", + rootstages[rsi], "-dep.test"); + let userbin: str = strings.concat(root, "/root-first-", + rootstages[rsi], "-user.test"); + let unionav: []str = [uniondriver, "test", "-c", + "-I", root, + "--ww-package-test", "production", "aaroot", aaroot, depbin, + strings.concat(root, "/root-first-", rootstages[rsi], + "-dep.status"), + "--ww-package-test", "production", "zimporter", zimporter, + userbin, strings.concat(root, "/root-first-", rootstages[rsi], + "-user.status"), aaroot]; + let beforetrace: str = ""; + if (rsi == 0) { beforetrace = strings.dup(readfile(compilertrace)); } + else { beforetrace = strings.dup(readfile(wwcompilertrace)); }; + if (rsi == 0) { + runcommandenv(root, "root-first-c", unionav, traceenv, + (120i64 * (time.second: i64)): time.duration, &out); + } else { + runcommandenv(root, "root-first-ww", unionav, wwtraceenv, + (120i64 * (time.second: i64)): time.duration, &out); + }; + expectexit(&out, 0); + let aftertrace: str = ""; + if (rsi == 0) { aftertrace = strings.dup(readfile(compilertrace)); } + else { aftertrace = strings.dup(readfile(wwcompilertrace)); }; + assert(occurrences(aftertrace, "/aaroot.unit.ww") + == occurrences(beforetrace, "/aaroot.unit.ww") + 1); + let unionwork: str = strings.concat(depbin, ".sepwork/"); + let suffixes: []str = [".unit.ww", ".wwi", ".a"]; + let xi: i32 = 0; + for (xi < suffixes.len) { + let rootbytes: str = readfile(strings.concat(rootwork, "aaroot", + suffixes[xi])); + assert(same(rootbytes, readfile(strings.concat(unionwork, "aaroot", + suffixes[xi])))); + if (rsi == 0) { rootrefs[xi] = strings.dup(rootbytes); } + else { assert(same(rootrefs[xi], rootbytes)); }; + xi += 1; + }; + rsi += 1; + }; clean(wwtracebin); clean(root); }; @@ -1754,6 +1945,34 @@ fn workescape(s: str) str = { "package good;\nfn value() i32 = { return 42; };\n"); writefile(strings.concat(good, "/good_test.ww"), "package good;\n@test fn unrelated_runs() void = { assert(value() == 42); };\n"); + let tooltrace: str = strings.concat(root, "/context-tool.trace"); + let compilerwrapper: str = strings.concat(root, "/context-w6c.sh"); + let linkerwrapper: str = strings.concat(root, "/context-w6l.sh"); + writefile(tooltrace, ""); + writeexecutable(compilerwrapper, strings.concat( + "#!/bin/sh\nprintf 'compile\\n' >> \"$WW_CONTEXT_TRACE\"\n", + "exec \"$WW_CONTEXT_W6C\" \"$@\"\n")); + writeexecutable(linkerwrapper, strings.concat( + "#!/bin/sh\nprintf 'link\\n' >> \"$WW_CONTEXT_TRACE\"\n", + "exec \"$WW_CONTEXT_W6L\" \"$@\"\n")); + let baseenv: []str = os.getenvs(); + let traceenv: []str = alloc([], (baseenv.len + 5): u64)!; + let ei: i32 = 0; + for (ei < baseenv.len) { + if (!strings.hasprefix(baseenv[ei], "WW_W6C=") + && !strings.hasprefix(baseenv[ei], "WW_W6L=") + && !strings.hasprefix(baseenv[ei], "WW_CONTEXT_TRACE=") + && !strings.hasprefix(baseenv[ei], "WW_CONTEXT_W6C=") + && !strings.hasprefix(baseenv[ei], "WW_CONTEXT_W6L=")) { + append(traceenv, baseenv[ei]); + }; + ei += 1; + }; + append(traceenv, strings.concat("WW_W6C=", compilerwrapper)); + append(traceenv, strings.concat("WW_W6L=", linkerwrapper)); + append(traceenv, strings.concat("WW_CONTEXT_TRACE=", tooltrace)); + append(traceenv, strings.concat("WW_CONTEXT_W6C=", driver("w6c"))); + append(traceenv, strings.concat("WW_CONTEXT_W6L=", driver("w6l"))); let stages: []str = ["ww", "ww_ww"]; let jobs: []str = ["1", "4"]; let diagnostics: []str = ["", ""]; @@ -1763,8 +1982,29 @@ fn workescape(s: str) str = { for (i < stages.len) { let av: []str = [driver(stages[i]), "test", "-j", jobs[i], "-I", root, strings.concat(suite, "/...")]; - runcommand(root, strings.concat("context-conflict-", stages[i]), av, - (120i64 * (time.second: i64)): time.duration, &out); + let stageenv: []str = traceenv; + if (i == 1) { + let wwenv: []str = alloc([], (baseenv.len + 5): u64)!; + stageenv = wwenv; + ei = 0; + for (ei < baseenv.len) { + if (!strings.hasprefix(baseenv[ei], "WW_W6C=") + && !strings.hasprefix(baseenv[ei], "WW_W6L=") + && !strings.hasprefix(baseenv[ei], "WW_CONTEXT_TRACE=") + && !strings.hasprefix(baseenv[ei], "WW_CONTEXT_W6C=") + && !strings.hasprefix(baseenv[ei], "WW_CONTEXT_W6L=")) { + append(stageenv, baseenv[ei]); + }; + ei += 1; + }; + append(stageenv, strings.concat("WW_W6C=", compilerwrapper)); + append(stageenv, strings.concat("WW_W6L=", linkerwrapper)); + append(stageenv, strings.concat("WW_CONTEXT_TRACE=", tooltrace)); + append(stageenv, strings.concat("WW_CONTEXT_W6C=", driver("w6c_ww"))); + append(stageenv, strings.concat("WW_CONTEXT_W6L=", driver("w6l_ww"))); + }; + runcommandenv(root, strings.concat("context-conflict-", stages[i]), av, + stageenv, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 1); assert(has(out.stderr, strings.concat( "ww: package _leaf resolves to directories ", aleaf, @@ -1782,14 +2022,15 @@ fn workescape(s: str) str = { i += 1; }; assert(same(diagnostics[0], diagnostics[1])); + assert(readfile(tooltrace).len == 0); clean(root); }; @test fn mixed_named_test_and_toolchain_test_actions_do_not_collide() void = { let root: str = fresh(); let suite: str = strings.concat(root, "/suite"); - let named: str = strings.concat(suite, "/a_named"); - let consumer: str = strings.concat(suite, "/b_consumer"); + let named: str = strings.concat(suite, "/test"); + let consumer: str = strings.concat(suite, "/zconsumer"); assert(os.mkdir(suite, 448i32) == 0); assert(os.mkdir(named, 448i32) == 0); assert(os.mkdir(consumer, 448i32) == 0); @@ -1805,16 +2046,16 @@ fn workescape(s: str) str = { "@test fn named_external_runs() void = {", " assert(test.value() == 42); };\n")); writefile(strings.concat(consumer, "/consumer.ww"), strings.concat( - "package consumer;\n", + "package zconsumer;\n", "// MIXED_GENERATED_TOOLCHAIN_TEST_SUPPORT\n", "export fn value() i32 = { return 42; };\n")); writefile(strings.concat(consumer, "/same_test.ww"), strings.concat( - "package consumer;\n@test fn consumer_same_runs() void = {", + "package zconsumer;\n@test fn consumer_same_runs() void = {", " assert(value() == 42); };\n")); writefile(strings.concat(consumer, "/external_test.ww"), strings.concat( - "package consumer_test;\nimport consumer;\n", + "package zconsumer_test;\nimport zconsumer;\n", "@test fn consumer_external_runs() void = {", - " assert(consumer.value() == 42); };\n")); + " assert(zconsumer.value() == 42); };\n")); let linktrace: str = strings.concat(root, "/mixed-link.trace"); let linkwrapper: str = strings.concat(root, "/mixed-w6l.sh"); writefile(linktrace, ""); @@ -1837,10 +2078,10 @@ fn workescape(s: str) str = { append(linkenv, strings.concat("WW_MIXED_W6L=", driver("w6l"))); let bins: []str = [strings.concat(named, "/test.test"), strings.concat(named, "/test_test.test"), - strings.concat(consumer, "/consumer.test"), - strings.concat(consumer, "/consumer_test.test")]; + strings.concat(consumer, "/zconsumer.test"), + strings.concat(consumer, "/zconsumer_test.test")]; let keys: []str = ["test-internal-test", "test_test-external-test", - "consumer-internal-test", "consumer_test-external-test"]; + "zconsumer-internal-test", "zconsumer_test-external-test"]; let expected: []str = ["named_same_runs ... ok\n", "named_external_runs ... ok\n", "consumer_same_runs ... ok\n", "consumer_external_runs ... ok\n"]; @@ -1981,7 +2222,8 @@ fn workescape(s: str) str = { let bin: str = strings.concat(target, "/target.test"); let workroot: str = strings.concat(bin, ".sepwork"); let work: str = strings.concat(workroot, "/"); - let cargv: []str = [driver("ww"), "test", "-c", "-I", root, target]; + let cargv: []str = [driver("ww"), "test", "-c", "-I", d3, + "-I", root, target]; let out: commandout; runcommandenv(root, "long-link-c", cargv, env, (120i64 * (time.second: i64)): time.duration, &out); @@ -2010,7 +2252,8 @@ fn workescape(s: str) str = { expectexit(&out, 0); assert(has(out.stdout, "complete_long_closure ... ok\n")); clean(workroot); clean(bin); - let wargv: []str = [driver("ww_ww"), "test", "-c", "-I", root, target]; + let wargv: []str = [driver("ww_ww"), "test", "-c", "-I", d3, + "-I", root, target]; runcommand(root, "long-link-ww", wargv, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -2307,8 +2550,8 @@ fn workescape(s: str) str = { let out: commandout; let i: i32 = 0; for (i < stages.len) { - let av: []str = [driver(stages[i]), "test", "-c", "-o", bin, - pkg]; + let av: []str = [driver(stages[i]), "test", "-c", "-I", root, + "-o", bin, pkg]; runcommand(root, strings.concat("named-test-build-", stages[i]), av, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -2366,7 +2609,7 @@ fn workescape(s: str) str = { let base: str = strings.concat(source, "/ww_root_parity_base_7f3"); let left: str = strings.concat(source, "/ww_root_parity_left_7f3"); let right: str = strings.concat(source, "/ww_root_parity_right_7f3"); - let target: str = strings.concat(source, "/ww_root_parity_target_7f3"); + let target: str = strings.concat(source, "/main"); let tools: str = strings.concat(root, "/tool wrappers"); assert(os.mkdir(source, 448i32) == 0); assert(os.mkdir(runtime, 448i32) == 0); @@ -2700,25 +2943,19 @@ fn workescape(s: str) str = { let right: str = strings.concat(root, "/right"); let conflict: str = strings.concat(root, "/conflict"); let privatepkg: str = strings.concat(root, "/privatepkg"); - let exportroot: str = strings.concat(root, "/exportroot"); - let exportdep: str = strings.concat(root, "/dupexport"); let identityroot: str = strings.concat(root, "/identityroot"); let identityactual: str = strings.concat(root, "/identityactual"); let identityalias: str = strings.concat(root, "/identityalias"); - let dupleft: str = strings.concat(root, "/dupleft"); - let dupright: str = strings.concat(root, "/dupright"); + let reserved: str = strings.concat(root, "/reservedprobe"); assert(os.mkdir(missing, 448i32) == 0); assert(os.mkdir(cycle, 448i32) == 0); assert(os.mkdir(left, 448i32) == 0); assert(os.mkdir(right, 448i32) == 0); assert(os.mkdir(conflict, 448i32) == 0); assert(os.mkdir(privatepkg, 448i32) == 0); - assert(os.mkdir(exportroot, 448i32) == 0); - assert(os.mkdir(exportdep, 448i32) == 0); assert(os.mkdir(identityroot, 448i32) == 0); assert(os.mkdir(identityactual, 448i32) == 0); - assert(os.mkdir(dupleft, 448i32) == 0); - assert(os.mkdir(dupright, 448i32) == 0); + assert(os.mkdir(reserved, 448i32) == 0); writefile(strings.concat(missing, "/missing.ww"), "package missing;\nfn local() void = { };\n"); writefile(strings.concat(missing, "/missing_test.ww"), strings.concat( @@ -2747,11 +2984,6 @@ fn workescape(s: str) str = { writefile(strings.concat(privatepkg, "/external_test.ww"), strings.concat( "package privatepkg_test;\nimport privatepkg;\n", "@test fn private_use() void = { assert(privatepkg.hidden() == 2); };\n")); - writefile(strings.concat(exportroot, "/exportroot.ww"), strings.concat( - "package dupexport;\nimport dupexport;\n", - "export fn value() i32 = { return dupexport.value(); };\n")); - writefile(strings.concat(exportdep, "/dupexport.ww"), - "package dupexport;\nexport fn value() i32 = { return 1; };\n"); writefile(strings.concat(identityactual, "/identityactual.ww"), "package identityactual;\nexport fn value() i32 = { return 1; };\n"); let linkout: commandout; @@ -2761,16 +2993,9 @@ fn workescape(s: str) str = { writefile(strings.concat(identityroot, "/identityroot.ww"), strings.concat( "package identityroot;\nimport identityactual;\n", "import identityalias;\nexport fn value() i32 = { return 1; };\n")); - writefile(strings.concat(dupleft, "/dupproduct.ww"), - "package dupproduct;\nexport fn value() i32 = { return 1; };\n"); - writefile(strings.concat(dupleft, "/external_test.ww"), strings.concat( - "package dupproduct_test;\nimport dupproduct;\n", - "@test fn left() void = { assert(dupproduct.value() == 1); };\n")); - writefile(strings.concat(dupright, "/dupproduct.ww"), - "package dupproduct;\nexport fn value() i32 = { return 2; };\n"); - writefile(strings.concat(dupright, "/external_test.ww"), strings.concat( - "package dupproduct_test;\nimport dupproduct;\n", - "@test fn right() void = { assert(dupproduct.value() == 2); };\n")); + writefile(strings.concat(reserved, "/reservedprobe.ww"), strings.concat( + "package reservedprobe;\nimport __wwlocal.fake;\n", + "export fn value() i32 = { return 1; };\n")); rejectpackagestable(root, "missing", missing, "cannot find package nowhere"); rejectpackagestable(root, "cycle", cycle, "ww: dependency cycle:"); @@ -2778,7 +3003,6 @@ fn workescape(s: str) str = { "conflicting package names conflict and other"); rejectpackagestable(root, "private", privatepkg, "package 'privatepkg' has no exported declaration 'hidden'"); - let exportdiagnostics: []str = ["", ""]; let stages: []str = ["ww", "ww_ww"]; let tooltrace: str = strings.concat(root, "/identity-tool.trace"); let compilerwrapper: str = strings.concat(root, "/identity-w6c.sh"); @@ -2810,21 +3034,6 @@ fn workescape(s: str) str = { append(traceenv, strings.concat("WW_IDENTITY_W6L=", driver("w6l"))); let out: commandout; let i: i32 = 0; - for (i < stages.len) { - let av: []str = [driver(stages[i]), "build", "-p", "-I", root, - "-o", strings.concat(root, "/bad-export-", stages[i], ".a"), - exportroot]; - runcommandenv(root, - strings.concat("duplicate-export-module-", stages[i]), - av, traceenv, (60i64 * (time.second: i64)): time.duration, &out); - assert(out.termination == exec.termination.EXIT && out.code != 0); - assert(has(out.stderr, strings.concat( - "ww: package dupexport resolves to directories ", exportdep, - " and ", exportroot, "\n"))); - exportdiagnostics[i] = strings.dup(primarydiagnostic(out.stderr)); - i += 1; - }; - assert(same(exportdiagnostics[0], exportdiagnostics[1])); let identitydiagnostics: []str = ["", ""]; i = 0; for (i < stages.len) { @@ -2842,29 +3051,24 @@ fn workescape(s: str) str = { i += 1; }; assert(same(identitydiagnostics[0], identitydiagnostics[1])); - let productdiagnostics: []str = ["", ""]; + // A canonical directory acquiring two import identities rejects before + // compiler or linker invocation. The converse is covered by the shared + // multi-context resolution test above. + assert(readfile(tooltrace).len == 0); + let reserveddiagnostics: []str = ["", ""]; i = 0; for (i < stages.len) { - let av: []str = [driver(stages[i]), "test", "-c", "-I", root, - "--ww-package-test", "external", "dupproduct_test", dupright, - strings.concat(root, "/dup-right-", stages[i], ".test"), - strings.concat(root, "/dup-right-", stages[i], ".status"), - "--ww-package-test", "external", "dupproduct_test", dupleft, - strings.concat(root, "/dup-left-", stages[i], ".test"), - strings.concat(root, "/dup-left-", stages[i], ".status"), root]; - runcommandenv(root, strings.concat("duplicate-product-identity-", - stages[i]), av, traceenv, - (60i64 * (time.second: i64)): time.duration, &out); + let av: []str = [driver(stages[i]), "build", "-p", "-I", root, + "-o", strings.concat(root, "/bad-reserved-", stages[i], ".a"), + reserved]; + runcommandenv(root, strings.concat("reserved-local-import-", stages[i]), + av, traceenv, (60i64 * (time.second: i64)): time.duration, &out); assert(out.termination == exec.termination.EXIT && out.code != 0); - assert(has(out.stderr, strings.concat( - "ww: package dupproduct resolves to directories ", dupleft, - " and ", dupright, "\n"))); - productdiagnostics[i] = strings.dup(out.stderr); + assert(has(out.stderr, "package path __wwlocal.fake is reserved")); + reserveddiagnostics[i] = strings.dup(primarydiagnostic(out.stderr)); i += 1; }; - assert(same(productdiagnostics[0], productdiagnostics[1])); - // Both identity directions reject while the command-global package - // universe is still unambiguous, before compiler or linker invocation. + assert(same(reserveddiagnostics[0], reserveddiagnostics[1])); assert(readfile(tooltrace).len == 0); clean(root); }; @@ -3070,22 +3274,28 @@ fn workescape(s: str) str = { @test fn persistent_package_request_workdir_is_stable() void = { let root: str = fresh(); let tree: str = strings.concat(root, "/tree"); - let a: str = strings.concat(tree, "/a"); - let ab: str = strings.concat(tree, "/a_b"); - let work: str = strings.concat(root, "/work"); + let adir: str = strings.concat(tree, "/a"); + let bdir: str = strings.concat(tree, "/b"); + let a: str = strings.concat(adir, "/foo"); + let b: str = strings.concat(bdir, "/foo"); + let workc: str = strings.concat(root, "/work-c"); + let workww: str = strings.concat(root, "/work-ww"); assert(os.mkdir(tree, 448i32) == 0); + assert(os.mkdir(adir, 448i32) == 0); + assert(os.mkdir(bdir, 448i32) == 0); assert(os.mkdir(a, 448i32) == 0); - assert(os.mkdir(ab, 448i32) == 0); - assert(os.mkdir(work, 448i32) == 0); - writefile(strings.concat(a, "/a.ww"), - "package b_c;\nfn value() i32 = { return 1; };\n"); - writefile(strings.concat(a, "/a_test.ww"), - "package b_c;\n@test fn first() void = { assert(value() == 1); };\n"); - writefile(strings.concat(ab, "/ab.ww"), - "package c;\nfn value() i32 = { return 2; };\n"); - writefile(strings.concat(ab, "/ab_test.ww"), - "package c;\n@test fn second() void = { assert(value() == 2); };\n"); - let av: []str = [driver("ww"), "test", "-j", "2", "-w", work, + assert(os.mkdir(b, 448i32) == 0); + assert(os.mkdir(workc, 448i32) == 0); + assert(os.mkdir(workww, 448i32) == 0); + writefile(strings.concat(a, "/foo.ww"), + "package foo;\nfn value() i32 = { return 1; };\n"); + writefile(strings.concat(a, "/foo_test.ww"), + "package foo;\n@test fn first() void = { assert(value() == 1); };\n"); + writefile(strings.concat(b, "/foo.ww"), + "package foo;\nfn value() i32 = { return 2; };\n"); + writefile(strings.concat(b, "/foo_test.ww"), + "package foo;\n@test fn second() void = { assert(value() == 2); };\n"); + let av: []str = [driver("ww"), "test", "-j", "2", "-w", workc, strings.concat(tree, "/...")]; let out: commandout; runcommand(root, "workkey-cold", av, @@ -3093,18 +3303,110 @@ fn workescape(s: str) str = { expectexit(&out, 0); let cold: str = strings.dup(out.stdout); assert(has(cold, strings.concat("ok ", a, - " [b_c, same-package]\n"))); - assert(has(cold, strings.concat("ok ", ab, - " [c, same-package]\n"))); - let key: str = strings.concat(work, "/d_", workescape(tree), "_p_", - workescape("b_c")); - assert(os.exists(key)); - assert(os.exists(strings.concat(key, "/b_c-internal-test.unit.ww"))); - assert(os.exists(strings.concat(key, "/c-internal-test.unit.ww"))); + " [foo, same-package]\n"))); + assert(has(cold, strings.concat("ok ", b, + " [foo, same-package]\n"))); + let ckey: str = strings.concat(workc, "/d_", workescape(tree)); + assert(os.exists(ckey)); + let aaction: str = "a.foo-internal-test"; + let baction: str = "b.foo-internal-test"; + assert(os.exists(strings.concat(ckey, "/", aaction, ".unit.ww"))); + assert(os.exists(strings.concat(ckey, "/", baction, ".unit.ww"))); + let aunit: str = readfile(strings.concat(ckey, "/", aaction, ".unit.ww")); + let bunit: str = readfile(strings.concat(ckey, "/", baction, ".unit.ww")); + let awwi: str = readfile(strings.concat(ckey, "/", aaction, ".wwi")); + let bwwi: str = readfile(strings.concat(ckey, "/", baction, ".wwi")); + let aa: str = readfile(strings.concat(ckey, "/", aaction, ".a")); + let ba: str = readfile(strings.concat(ckey, "/", baction, ".a")); + assert(has(aunit, "return 1") && !has(aunit, "return 2")); + assert(has(bunit, "return 2") && !has(bunit, "return 1")); runcommand(root, "workkey-warm", av, (60i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); assert(same(cold, out.stdout)); + let wwav: []str = [driver("ww_ww"), "test", "-j", "1", "-w", workww, + strings.concat(tree, "/...")]; + runcommand(root, "workkey-ww", wwav, + (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(same(cold, out.stdout)); + let wwkey: str = strings.concat(workww, "/d_", workescape(tree)); + assert(same(aunit, readfile(strings.concat(wwkey, "/", aaction, + ".unit.ww")))); + assert(same(bunit, readfile(strings.concat(wwkey, "/", baction, + ".unit.ww")))); + assert(same(awwi, readfile(strings.concat(wwkey, "/", aaction, ".wwi")))); + assert(same(bwwi, readfile(strings.concat(wwkey, "/", baction, ".wwi")))); + assert(same(aa, readfile(strings.concat(wwkey, "/", aaction, ".a")))); + assert(same(ba, readfile(strings.concat(wwkey, "/", baction, ".a")))); + // The canonical coordinator key and warm action set are unchanged by an + // equivalent recursive-root spelling. + let equivalentav: []str = [driver("ww_ww"), "test", "-j", "2", "-w", + workww, strings.concat(tree, "/./...")]; + runcommand(root, "workkey-equivalent", equivalentav, + (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, strings.concat("ok ", tree, + "/./a/foo [foo, same-package]\n"))); + assert(has(out.stdout, strings.concat("ok ", tree, + "/./b/foo [foo, same-package]\n"))); + assert(os.exists(wwkey)); + assert(same(aunit, readfile(strings.concat(wwkey, "/", aaction, + ".unit.ww")))); + assert(same(bunit, readfile(strings.concat(wwkey, "/", baction, + ".unit.ww")))); + assert(same(awwi, readfile(strings.concat(wwkey, "/", aaction, ".wwi")))); + assert(same(bwwi, readfile(strings.concat(wwkey, "/", baction, ".wwi")))); + assert(same(aa, readfile(strings.concat(wwkey, "/", aaction, ".a")))); + assert(same(ba, readfile(strings.concat(wwkey, "/", baction, ".a")))); + + // Outside every active import root, equal declaration leaves remain two + // deterministic command-local identities derived from canonical dirs. + let localleft: str = strings.concat(root, "/local-left"); + let localright: str = strings.concat(root, "/local-right"); + assert(os.mkdir(localleft, 448i32) == 0); + assert(os.mkdir(localright, 448i32) == 0); + writefile(strings.concat(localleft, "/foo.ww"), + "package foo;\nexport fn value() i32 = { return 11; };\n"); + writefile(strings.concat(localright, "/foo.ww"), + "package foo;\nexport fn value() i32 = { return 22; };\n"); + let localids: []str = [localidentity(localleft, "foo"), + localidentity(localright, "foo")]; + assert(!same(localids[0], localids[1])); + let stages: []str = ["ww", "ww_ww"]; + let localrefs: []str = ["", "", "", "", "", ""]; + let si: i32 = 0; + for (si < stages.len) { + let leftout: str = strings.concat(root, "/local-", stages[si], + "-left.test"); + let rightout: str = strings.concat(root, "/local-", stages[si], + "-right.test"); + let lav: []str = [driver(stages[si]), "test", "-c", + "--ww-package-test", "production", "foo", localleft, leftout, + strings.concat(root, "/local-", stages[si], "-left.status"), + "--ww-package-test", "production", "foo", localright, rightout, + strings.concat(root, "/local-", stages[si], "-right.status"), + localleft]; + runcommand(root, strings.concat("local-command-union-", stages[si]), + lav, (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let localwork: str = strings.concat(leftout, ".sepwork/"); + let li: i32 = 0; + for (li < localids.len) { + let suffixes: []str = [".unit.ww", ".wwi", ".a"]; + let xi: i32 = 0; + for (xi < suffixes.len) { + let artifact: str = readfile(strings.concat(localwork, + localids[li], suffixes[xi])); + let refi: i32 = li * 3 + xi; + if (si == 0) { localrefs[refi] = strings.dup(artifact); } + else { assert(same(localrefs[refi], artifact)); }; + xi += 1; + }; + li += 1; + }; + si += 1; + }; clean(root); }; @@ -3414,8 +3716,8 @@ fn runtimepath(relative: str) str = { for (i < 2) { let named: str = strings.concat(outdir, "/out_", drvs[i], ".bin"); - let coav: []str = [driver(drvs[i]), "test", "-c", "-o", - named, pdir]; + let coav: []str = [driver(drvs[i]), "test", "-c", "-I", root, + "-o", named, pdir]; runcommand(root, strings.concat("nameco_", drvs[i]), coav, (30i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); @@ -3434,8 +3736,8 @@ fn runtimepath(relative: str) str = { expectexit(&out, 0); assert(has(out.stdout, "seven ... ok")); - let nocav: []str = [driver(drvs[i]), "test", "-o", named, - pdir]; + let nocav: []str = [driver(drvs[i]), "test", "-I", root, + "-o", named, pdir]; runcommand(root, strings.concat("noc_", drvs[i]), nocav, (30i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 2); diff --git a/test/sep/importdir_test.ww b/test/sep/importdir_test.ww index 4993b078..a65bd86e 100644 --- a/test/sep/importdir_test.ww +++ b/test/sep/importdir_test.ww @@ -88,6 +88,7 @@ fn samefile(a: str, b: str, why: str) void = { let cwork: str = strings.concat(stems[0], ".sepwork/"); let cwork2: str = strings.concat(stems[1], ".sepwork/"); let wwork: str = strings.concat(stems[2], ".sepwork/"); + let appidentity: str = testenv.localpackageidentity(app, "main"); let foounit: str = testenv.readfile(strings.concat(cwork, "example.foo.unit.ww")); let wantfoo: str = strings.concat("//ww:module-reset example.foo\n", afoosrc, @@ -97,14 +98,15 @@ fn samefile(a: str, b: str, why: str) void = { || testenv.has(foounit, "//ww:module ")) { fail("foo unit is not exactly its sorted, owned source set"); }; - let wantroot: str = strings.concat("//ww:module-reset main\n", appsrc, + let wantroot: str = strings.concat("//ww:module-reset ", appidentity, + "\n", appsrc, "\n"); if (!testenv.same(wantroot, testenv.readfile(strings.concat(cwork, - "main.unit.ww")))) { + appidentity, ".unit.ww")))) { fail("root unit is not exactly its owned source"); }; let keys: []str = ["example.base", "example.bar", "example.foo", - "main"]; + appidentity]; let suffixes: []str = [".unit.ww", ".wwi", ".a"]; i = 0; for (i < keys.len) { @@ -122,6 +124,47 @@ fn samefile(a: str, b: str, why: str) void = { samefile(stems[0], stems[1], "C executables are not deterministic"); samefile(stems[0], stems[2], "C/WW executables differ"); + // The same canonical directory is now observed as an imported dependency, + // a literal root, an explicit logical root, an equivalent path spelling, + // and a symlink spelling. Every route must publish the one example.foo + // action with byte-identical owner unit, export, and archive. + let fooalias: str = strings.concat(td, "/foo-alias"); + assert(os.symlink(foo, fooalias) == 0); + let rootouts: []str = [strings.concat(td, "/foo-root-c"), + strings.concat(td, "/foo-root-w"), + strings.concat(td, "/foo-logical-c"), + strings.concat(td, "/foo-logical-w"), + strings.concat(td, "/foo-equivalent-c"), + strings.concat(td, "/foo-equivalent-w"), + strings.concat(td, "/foo-relative-c"), + strings.concat(td, "/foo-relative-w"), + strings.concat(td, "/foo-alias-c"), + strings.concat(td, "/foo-alias-w")]; + let rootdrivers: []str = ["ww", "ww_ww", "ww", "ww_ww", "ww", + "ww_ww", "ww", "ww_ww", "ww", "ww_ww"]; + let roottags: []str = ["literal-c", "literal-w", "logical-c", + "logical-w", "equivalent-c", "equivalent-w", "relative-c", + "relative-w", "alias-c", "alias-w"]; + let roottargets: []str = [foo, foo, "example.foo", "example.foo", + strings.concat(foo, "/../foo"), strings.concat(foo, "/../foo"), + "late/example/foo", "late/example/foo", fooalias, fooalias]; + i = 0; + for (i < rootouts.len) { + let rav: []str = [testenv.driver(rootdrivers[i]), "build", "-p", + "-I", early, "-I", late, "-o", rootouts[i], roottargets[i]]; + expectcode(td, strings.concat("foo_root_", roottags[i]), rav, 0); + let rw: str = strings.concat(rootouts[i], ".sepwork/"); + let rsuffixes: []str = [".unit.ww", ".wwi", ".a"]; + let ri: i32 = 0; + for (ri < rsuffixes.len) { + let artifact: str = strings.concat("example.foo", rsuffixes[ri]); + samefile(strings.concat(cwork, artifact), strings.concat(rw, artifact), + "canonical foo action changed across root spellings"); + ri += 1; + }; + i += 1; + }; + let cerr: testenv.commandout; let werr: testenv.commandout; let cav: []str = [testenv.driver("ww"), "build", "-I", early, diff --git a/test/sep/localbuild_test.ww b/test/sep/localbuild_test.ww index 93272dbf..9f2a2e2d 100644 --- a/test/sep/localbuild_test.ww +++ b/test/sep/localbuild_test.ww @@ -132,7 +132,7 @@ fn rejectstable(dir: str, label: str, target: str, needle: str) void = { @test fn standalone_and_package_artifact() void = { let td: str = testenv.fresh(); - let app: str = strings.concat(td, "/app"); + let app: str = strings.concat(td, "/main"); mkdir(app); testenv.writefile(strings.concat(app, "/main.ww"), strings.concat( "package main;\n", @@ -459,11 +459,11 @@ fn writediamond(td: str, reverse: bool) str = { @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, "/app"); + 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, "/app"); + 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"); @@ -556,7 +556,7 @@ fn writediamond(td: str, reverse: bool) str = { testenv.driver(assemblers[si]), testenv.driver(linkers[si])); let av: []str = [testenv.driver(stages[si]), "build", "-o", bin, - "app"]; + "main"]; let out: testenv.commandout; testenv.runcommandenv(cwd, td, strings.concat("library-root-", prefix), av, env, tmo(), &out); @@ -701,6 +701,7 @@ fn writediamond(td: str, reverse: bool) 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; @@ -727,7 +728,7 @@ fn writediamond(td: str, reverse: bool) str = { testenv.runcommandenv(cwd, td, strings.concat("empty-library-", tags[si]), av, emptyenv, tmo(), &out); let rootunit: str = testenv.readfile(strings.concat(emptywork, - "main.unit.ww")); + emptyidentity, ".unit.ww")); let ctrace: str = testenv.readfile(compilertrace); let atrace: str = testenv.readfile(assemblertrace); let ltrace: str = testenv.readfile(linkertrace); @@ -738,7 +739,8 @@ fn writediamond(td: str, reverse: bool) str = { runav) != 42) { fail("library-roots", "empty override did not use default roots"); }; - if (!testenv.has(rootunit, "//ww:module-reset main\npackage main;") + 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")) @@ -789,7 +791,7 @@ fn writediamond(td: str, reverse: bool) str = { @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, "/app"); + 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"); @@ -871,7 +873,7 @@ fn writediamond(td: str, reverse: bool) str = { testenv.driver(assemblers[si]), testenv.driver(linkers[si])); let av: []str = [copied[si], "build", "-w", work, - "-o", bin, "app"]; + "-o", bin, "main"]; let out: testenv.commandout; testenv.runcommandenv(cwd, td, strings.concat("driver-cold-", tags[si]), av, env, tmo(), &out); @@ -905,7 +907,7 @@ fn writediamond(td: str, reverse: bool) str = { "/.wwtool.w6a")), testenv.readfile(assembler)) || !testenv.same(testenv.readfile(strings.concat(work, "/.wwtool.stamp")), - "ww workdir fmt 7 mode build asm 0\n")) { + "ww workdir fmt 8 mode build asm 0\n")) { fail("driver-identity", "persistent artifacts or identities are incomplete"); }; let coldwwi: str = testenv.readfile(strings.concat(work, "/dep.wwi")); @@ -1049,7 +1051,7 @@ fn writediamond(td: str, reverse: bool) str = { 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, "app"]; + "-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, diff --git a/test/testenv/testenv.ww b/test/testenv/testenv.ww index 7bd727f5..c76a55ad 100644 --- a/test/testenv/testenv.ww +++ b/test/testenv/testenv.ww @@ -217,6 +217,39 @@ export fn occurrences(haystack: str, needle: str) i32 = { return count; }; +// Expected semantic identity for a canonical absolute directory that is not +// representable below an active source root. Keep this independent observer +// byte-for-byte aligned with the documented driver protocol. +export fn localpackageidentity(dir: str, leaf: str) str = { + let out: []u8 = alloc([], (dir.len * 4 + leaf.len + 16): u64)!; + let prefix: str = "__wwlocal.p"; + let i: i32 = 0; + for (i < prefix.len) { append(out, prefix[i]); i += 1; }; + let hex: str = "0123456789abcdef"; + i = 0; + for (i < dir.len) { + let c: u8 = dir[i]; + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9')) { + append(out, c); + } else if (c == '_') { + append(out, '_'); append(out, 'u'); + } else if (c == '/') { + append(out, '_'); append(out, 's'); + } else { + let high: i32 = (c / 16u8): i32; + let low: i32 = (c % 16u8): i32; + append(out, '_'); append(out, 'x'); + append(out, hex[high]); append(out, hex[low]); + }; + i += 1; + }; + append(out, '.'); + i = 0; + for (i < leaf.len) { append(out, leaf[i]); i += 1; }; + return strings.frombytes(out); +}; + // Byte-lexicographic order, so directory listings sort identically on // every host regardless of locale. export fn lexless(a: str, b: str) bool = { diff --git a/test/xmod/direnum_test.ww b/test/xmod/direnum_test.ww index fbf02d5a..cc337727 100644 --- a/test/xmod/direnum_test.ww +++ b/test/xmod/direnum_test.ww @@ -17,7 +17,7 @@ package direnum_test; // // rootorder — a private rootok/ dir built DIRECTLY (z.ww ORDER-Z // value()=17 vs a.ww ORDER-A leading comment + main): build exit 0, -// the binary exits 17, and the driver's .sepwork/rootok.unit.ww +// the binary exits 17, and the driver's local-identity owner unit // stores ORDER-A strictly before ORDER-Z (byte-sorted deterministic // source order); the two stages' units are byte-identical. // @@ -128,6 +128,7 @@ fn rejectpair(label: str, td: str, target: str, needle: str) void = { "package rootok;\n// ORDER-A\n", "fn main() i32 = { return value(); };\n")); let drvs: []str = ["ww", "ww_ww"]; + let identity: str = testenv.localpackageidentity(rootok, "rootok"); let units: []str = ["", ""]; let s: i32 = 0; for (s < 2) { @@ -143,7 +144,7 @@ fn rejectpair(label: str, td: str, target: str, needle: str) void = { fail("rootorder", strings.concat(drvs[s], " exit != 17")); }; units[s] = testenv.readfile(strings.concat(out, - ".sepwork/rootok.unit.ww")); + ".sepwork/", identity, ".unit.ww")); let a: i32 = testenv.pos(units[s], "ORDER-A"); let z: i32 = testenv.pos(units[s], "ORDER-Z"); if (a < 0 || z < 0 || a >= z) { @@ -153,7 +154,7 @@ fn rejectpair(label: str, td: str, target: str, needle: str) void = { s += 1; }; if (!testenv.same(units[0], units[1])) { - fail("rootorder", "C/WW rootok.unit.ww differ"); + fail("rootorder", "C/WW local root units differ"); }; testenv.clean(td); };