From 7d25ad9f9fc52c10125a21c35cb8b846078f41de Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 12 Aug 2026 03:46:58 +0900 Subject: [PATCH] test: cover directory package test graphs --- test/package/package_test.ww | 650 +++++++++++++++++++++++++- test/package/routing/b_white_test.ww | 1 + test/package/routing/detail.ww | 5 + test/package/routing/external_test.ww | 1 + test/package/routing/routing_test.ww | 1 + 5 files changed, 651 insertions(+), 7 deletions(-) create mode 100644 test/package/routing/detail.ww diff --git a/test/package/package_test.ww b/test/package/package_test.ww index 1fba21b7..d57291f4 100644 --- a/test/package/package_test.ww +++ b/test/package/package_test.ww @@ -91,6 +91,26 @@ fn runcommand(root: str, name: str, argv: []str, out.stderr = readfile(c.stderrpath); }; +fn runcommandenv(root: str, name: str, argv: []str, env: []str, + lifetime: time.duration, out: *commandout) void = { + let c: exec.command; + c.path = argv[0]; + c.argv = argv; + c.env = env; + c.dir = repo(); + c.stdoutpath = strings.concat(root, "/", name, ".stdout"); + c.stderrpath = strings.concat(root, "/", name, ".stderr"); + c.deadline = time.add(time.now(time.clock.monotonic), lifetime); + c.grace = (100i64 * (time.millisecond: i64)): time.duration; + let r: exec.result; + exec.run(&c, &r); + assert(r.errno == 0 && r.cleanuperrno == 0); + out.termination = r.termination; + out.code = r.code; + out.stdout = readfile(c.stdoutpath); + out.stderr = readfile(c.stderrpath); +}; + fn clean(root: str) void = { let av: []str = ["/bin/rm", "-rf", "--", root]; let c: exec.command; @@ -154,10 +174,62 @@ fn expectexit(out: *commandout, code: i32) void = { assert(out.code == code); }; +fn primarydiagnostic(s: str) str = { + let begin: i32 = pos(s, ": error: "); + if (begin >= 0) { begin += 9; } + else { begin = pos(s, "ww: dependency cycle: "); }; + if (begin < 0) { return s; }; + let end: i32 = begin; + for (end < s.len && s[end] != '\n') { end += 1; }; + return strings.sub(s, begin, end); +}; + +fn rejectpackagestable(root: 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 diagnostics: []str = ["", "", "", ""]; + let outstem: str = strings.concat(root, "/reject-", label); + let i: i32 = 0; + for (i < drivers.len) { + let av: []str = [driver(drivers[i]), "test", "-c", "-o", + outstem, "-I", root, target]; + let out: commandout; + runcommand(root, strings.concat("reject-", label, "-", tags[i]), + av, (60i64 * (time.second: i64)): time.duration, &out); + assert(out.termination == exec.termination.EXIT && out.code != 0); + assert(has(out.stderr, needle)); + diagnostics[i] = strings.dup(primarydiagnostic(out.stderr)); + let scratch: str = strings.concat(outstem, ".sepwork"); + if (os.exists(scratch)) { clean(scratch); }; + if (os.exists(outstem)) { clean(outstem); }; + i += 1; + }; + assert(same(diagnostics[0], diagnostics[2])); + assert(same(diagnostics[1], diagnostics[3])); + assert(same(diagnostics[0], diagnostics[1])); +}; + fn packagepath(relative: str) str = { return strings.concat(repo(), "/test/package/", relative); }; +fn workescape(s: str) str = { + let out: []u8 = alloc([], (s.len * 2 + 1): u64)!; + let i: i32 = 0; + for (i < s.len) { + if (s[i] == '_') { + append(out, '_'); append(out, 'u'); + } else { if (s[i] == '/') { + append(out, '_'); append(out, 's'); + } else { + append(out, s[i]); + }; }; + i += 1; + }; + return strings.frombytes(out); +}; + @test fn deterministic_routing_and_filters() void = { let root: str = fresh(); let target: str = packagepath("routing"); @@ -240,14 +312,20 @@ fn packagepath(relative: str) str = { assert(os.mkdir(pkgdir, 448i32) == 0); writefile(strings.concat(pkgdir, "/noncanon.ww"), strings.concat( "package noncanon;\n", - "@test fn hidden() void = { assert(false); };\n")); - let av: []str = [driver("ww"), "test", pkgdir]; + "@test\n/* lexer whitespace is legal here */\n", + "fn hidden() void = { assert(false); };\n")); + let stages: []str = ["ww", "ww_ww"]; let out: commandout; - runcommand(root, "noncanon", av, - (30i64 * (time.second: i64)): time.duration, &out); - expectexit(&out, 1); - assert(has(out.stderr, "@test declaration outside *_test.ww")); - assert(!has(out.stdout, "hidden")); + let i: i32 = 0; + for (i < stages.len) { + let av: []str = [driver(stages[i]), "test", pkgdir]; + runcommand(root, strings.concat("noncanon-", stages[i]), av, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(has(out.stderr, "@test declaration outside *_test.ww")); + assert(!has(out.stdout, "hidden")); + i += 1; + }; clean(root); }; @@ -275,6 +353,105 @@ fn packagepath(relative: str) str = { (30i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 1); assert(has(out.stderr, "directory contains no WW package sources")); + + let invalid: str = strings.concat(root, "/invalid-no-tests"); + assert(os.mkdir(invalid, 448i32) == 0); + writefile(strings.concat(invalid, "/invalid.ww"), + "package invalid;\nimport nowhere;\n"); + let invalidav: []str = [driver("ww"), "test", invalid]; + runcommand(root, "invalid-no-tests", invalidav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(has(out.stderr, "cannot find package nowhere")); + assert(!has(out.stdout, "[no tests]")); + + // A test file is part of its variant even when it only supplies helpers. + // Its imports and package clause must therefore reach the shared loader; + // the coordinator cannot use textual @test presence as a source filter. + let helperbad: str = strings.concat(root, "/helperbad"); + assert(os.mkdir(helperbad, 448i32) == 0); + writefile(strings.concat(helperbad, "/helperbad.ww"), + "package helperbad;\nexport fn value() i32 = { return 1; };\n"); + writefile(strings.concat(helperbad, "/helper_test.ww"), + "package helperbad_test;\nimport nowhere;\nfn helper() void = { };\n"); + let stages: []str = ["ww", "ww_ww"]; + let si: i32 = 0; + for (si < stages.len) { + let helperav: []str = [driver(stages[si]), "test", helperbad]; + runcommand(root, strings.concat("helper-only-", stages[si]), helperav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(has(out.stderr, "cannot find package nowhere")); + si += 1; + }; + + let helperwrong: str = strings.concat(root, "/helperwrong"); + assert(os.mkdir(helperwrong, 448i32) == 0); + writefile(strings.concat(helperwrong, "/helperwrong.ww"), + "package helperwrong;\nfn value() void = { };\n"); + writefile(strings.concat(helperwrong, "/wrong_test.ww"), + "package unrelated;\nfn helper() void = { };\n"); + let wrongav: []str = [driver("ww"), "test", helperwrong]; + runcommand(root, "helper-wrong-package", wrongav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(has(out.stderr, + "test package must match production package or _test")); + + // The bootstrap drivers store package identities in the same 256-byte + // slot. Reject an overlong coordinator selector before either stage can + // truncate it into a different package graph. + let longdir: str = strings.concat(root, "/long-package"); + assert(os.mkdir(longdir, 448i32) == 0); + let chunk: str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + let longname: str = strings.concat(chunk, chunk, chunk, chunk, + chunk, chunk, chunk, chunk); + assert(longname.len == 256); + writefile(strings.concat(longdir, "/long.ww"), + strings.concat("package ", longname, ";\nfn value() void = { };\n")); + let longc: []str = [driver("ww"), "test", longdir]; + let longw: []str = [driver("ww_ww"), "test", longdir]; + let outc: commandout; + let outw: commandout; + runcommand(root, "long-package-c", longc, + (30i64 * (time.second: i64)): time.duration, &outc); + runcommand(root, "long-package-ww", longw, + (30i64 * (time.second: i64)): time.duration, &outw); + expectexit(&outc, 1); + expectexit(&outw, 1); + assert(same(outc.stdout, outw.stdout)); + assert(same(outc.stderr, outw.stderr)); + assert(has(outc.stderr, "invalid --ww-package-test variant")); + + let multiline: str = strings.concat(root, "/multiline-test"); + assert(os.mkdir(multiline, 448i32) == 0); + writefile(strings.concat(multiline, "/multiline.ww"), + "package multiline;\nfn value() i32 = { return 42; };\n"); + writefile(strings.concat(multiline, "/multiline_test.ww"), + strings.concat("package multiline;\n@test\n", + "/* lexer whitespace between attribute and declaration */\n", + "fn actually_runs() void = { assert(value() == 42); };\n")); + let multilineav: []str = [driver("ww"), "test", multiline]; + runcommand(root, "multiline-test", multilineav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "multiline.actually_runs ... ok\n")); + assert(!has(out.stdout, "[no tests]")); + + let commented: str = strings.concat(root, "/commented-test"); + assert(os.mkdir(commented, 448i32) == 0); + writefile(strings.concat(commented, "/commented.ww"), + "package commented;\nfn value() i32 = { return 1; };\n"); + writefile(strings.concat(commented, "/commented_test.ww"), strings.concat( + "package commented;\n/*\n", + "@test fn phantom() void = { assert(false); };\n", + "*/\nfn helper() void = { };\n")); + let commentedav: []str = [driver("ww"), "test", commented]; + runcommand(root, "commented-test", commentedav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, " [no tests]\n")); + assert(!has(out.stdout, "phantom")); clean(root); }; @@ -462,6 +639,403 @@ fn packagepath(relative: str) str = { clean(root); }; +@test fn directory_package_graph_variants() void = { + let root: str = fresh(); + let implementation: str = strings.concat(root, "/implementation"); + let api: str = strings.concat(root, "/api"); + let testonly: str = strings.concat(root, "/testonly"); + let externalonly: str = strings.concat(root, "/externalonly"); + let pkg: str = strings.concat(root, "/pkg"); + assert(os.mkdir(implementation, 448i32) == 0); + assert(os.mkdir(api, 448i32) == 0); + assert(os.mkdir(testonly, 448i32) == 0); + assert(os.mkdir(externalonly, 448i32) == 0); + assert(os.mkdir(pkg, 448i32) == 0); + writefile(strings.concat(implementation, "/implementation.ww"), + strings.concat("package implementation;\n", + "// IMPLEMENTATION_SOURCE\n", + "export fn value() i32 = { return 41; };\n")); + writefile(strings.concat(api, "/api.ww"), strings.concat( + "package api;\nimport implementation;\n", + "// API_SOURCE\n", + "export fn value() i32 = { return implementation.value(); };\n")); + writefile(strings.concat(api, "/api_test.ww"), strings.concat( + "package api;\n", + "TEST_DEPENDENCY_MUST_NOT_COMPILE\n")); + writefile(strings.concat(testonly, "/testonly.ww"), strings.concat( + "package testonly;\n", + "export fn value() i32 = { return 1; };\n")); + writefile(strings.concat(externalonly, "/externalonly.ww"), strings.concat( + "package externalonly;\n", + "export fn value() i32 = { return 1; };\n")); + writefile(strings.concat(pkg, "/a.ww"), strings.concat( + "package pkg;\nimport api;\n", + "// PACKAGE_PRODUCTION_A\n", + "export fn value() i32 = { return api.value(); };\n")); + writefile(strings.concat(pkg, "/z.ww"), strings.concat( + "package pkg;\n", + "// PACKAGE_PRODUCTION_Z\n", + "fn secret() i32 = { return 6; };\n", + "export fn second() i32 = { return 9; };\n")); + writefile(strings.concat(pkg, "/same_test.ww"), strings.concat( + "package pkg;\nimport testonly;\n", + "// SAME_TEST_SOURCE\n", + "@test fn same_graph() void = {\n", + " assert(value() + testonly.value() == 42);\n", + " assert(secret() == 6);\n};\n")); + writefile(strings.concat(pkg, "/external_test.ww"), strings.concat( + "package pkg_test;\nimport externalonly;\nimport pkg;\n", + "// EXTERNAL_TEST_SOURCE\n", + "@test fn external_graph() void = {\n", + " assert(pkg.value() + externalonly.value() == 42);\n", + " assert(pkg.second() == 9);\n};\n")); + + let prodout: str = strings.concat(root, "/production.a"); + let prodav: []str = [driver("ww"), "build", "-p", "-I", root, + "-o", prodout, pkg]; + let out: commandout; + runcommand(root, "production-build", prodav, + (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let prodwork: str = strings.concat(prodout, ".sepwork/"); + let produnit: str = readfile(strings.concat(prodwork, + "pkg.unit.ww")); + assert(has(produnit, "//ww:module api\n")); + assert(!has(produnit, "//ww:module implementation\n")); + assert(!has(produnit, "testonly")); + assert(!has(produnit, "externalonly")); + assert(!os.exists(strings.concat(prodwork, "testonly.unit.ww"))); + assert(!os.exists(strings.concat(prodwork, "externalonly.unit.ww"))); + assert(!has(readfile(strings.concat(prodwork, "api.unit.ww")), + "TEST_DEPENDENCY_MUST_NOT_COMPILE")); + + let samebin: str = strings.concat(pkg, "/pkg.test"); + let externalbin: str = strings.concat(pkg, "/pkg_test.test"); + let sameworkroot: str = strings.concat(samebin, ".sepwork"); + let externalworkroot: str = strings.concat(externalbin, ".sepwork"); + let samework: str = strings.concat(sameworkroot, "/"); + let externalwork: str = strings.concat(externalworkroot, "/"); + let drivers: []str = ["ww", "ww", "ww_ww"]; + let tags: []str = ["c1", "c2", "ww"]; + let referenceout: str = ""; + let referenceerr: str = ""; + let referencesame: str = ""; + let referenceexternal: str = ""; + let referencesameunit: str = ""; + let referenceexternalunit: str = ""; + let referenceproductionunit: str = ""; + let i: i32 = 0; + for (i < drivers.len) { + let av: []str = [driver(drivers[i]), "test", "-c", "-I", root, + pkg]; + runcommand(root, strings.concat("variant-build-", tags[i]), av, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let buildstdout: str = strings.dup(out.stdout); + let buildstderr: str = strings.dup(out.stderr); + let sameunit: str = readfile(strings.concat(samework, + "__root.unit.ww")); + let externalunit: str = readfile(strings.concat(externalwork, + "__root.unit.ww")); + let externalproduction: str = readfile(strings.concat(externalwork, + "pkg.unit.ww")); + assert(has(sameunit, "//ww:module api\n")); + assert(has(sameunit, "//ww:module testonly\n")); + assert(has(sameunit, "//ww:module test\n")); + assert(!has(sameunit, "//ww:module implementation\n")); + assert(!has(sameunit, "//ww:module externalonly\n")); + assert(!os.exists(strings.concat(samework, + "externalonly.unit.ww"))); + assert(has(sameunit, "PACKAGE_PRODUCTION_A")); + assert(has(sameunit, "PACKAGE_PRODUCTION_Z")); + assert(has(sameunit, "SAME_TEST_SOURCE")); + assert(!has(sameunit, "EXTERNAL_TEST_SOURCE")); + assert(has(externalunit, "//ww:module pkg\n")); + assert(has(externalunit, "//ww:module externalonly\n")); + assert(has(externalunit, "//ww:module test\n")); + assert(!has(externalunit, "//ww:module api\n")); + assert(!has(externalunit, "//ww:module implementation\n")); + assert(!has(externalunit, "//ww:module testonly\n")); + assert(has(externalunit, "EXTERNAL_TEST_SOURCE")); + assert(!has(externalunit, "PACKAGE_PRODUCTION_A")); + assert(!has(externalunit, "PACKAGE_PRODUCTION_Z")); + assert(has(externalproduction, "PACKAGE_PRODUCTION_A")); + assert(has(externalproduction, "PACKAGE_PRODUCTION_Z")); + assert(has(externalproduction, "//ww:module api\n")); + assert(!has(externalproduction, "//ww:module implementation\n")); + assert(!has(externalproduction, "SAME_TEST_SOURCE")); + assert(!has(externalproduction, "EXTERNAL_TEST_SOURCE")); + assert(!has(readfile(strings.concat(externalwork, "api.unit.ww")), + "TEST_DEPENDENCY_MUST_NOT_COMPILE")); + assert(os.exists(strings.concat(samework, "implementation.a"))); + assert(os.exists(strings.concat(externalwork, "implementation.a"))); + assert(os.exists(strings.concat(externalwork, "api.a"))); + assert(os.exists(strings.concat(externalwork, "pkg.a"))); + let runav: []str = [samebin]; + runcommand(root, strings.concat("variant-run-same-", tags[i]), runav, + (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "same_graph ... ok\n")); + let externalrun: []str = [externalbin]; + runcommand(root, strings.concat("variant-run-external-", tags[i]), + externalrun, (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "external_graph ... ok\n")); + if (i == 0) { + referenceout = buildstdout; + referenceerr = buildstderr; + referencesame = readfile(samebin); + referenceexternal = readfile(externalbin); + referencesameunit = strings.dup(sameunit); + referenceexternalunit = strings.dup(externalunit); + referenceproductionunit = strings.dup(externalproduction); + } else { + assert(same(referencesame, readfile(samebin))); + assert(same(referenceexternal, readfile(externalbin))); + assert(same(referencesameunit, sameunit)); + assert(same(referenceexternalunit, externalunit)); + assert(same(referenceproductionunit, externalproduction)); + assert(same(referenceout, buildstdout)); + assert(same(referenceerr, buildstderr)); + }; + if (i + 1 < drivers.len) { + clean(sameworkroot); clean(externalworkroot); + clean(samebin); clean(externalbin); + }; + i += 1; + }; + let aliasunit: str = strings.concat(root, "/alias.unit.ww"); + writefile(aliasunit, strings.concat( + "//ww:module-reset __wwtest\n", + "package test;\nexport fn value() i32 = { return 1; };\n")); + let aliasstages: []str = ["w6c", "w6c_ww"]; + let aliaserrs: []str = ["", ""]; + i = 0; + for (i < aliasstages.len) { + let asmout: str = strings.concat(root, "/alias-", aliasstages[i], + ".s"); + let rawav: []str = [driver(aliasstages[i]), "-c", "-o", asmout, + aliasunit]; + runcommand(root, strings.concat("alias-unowned-", aliasstages[i]), + rawav, (30i64 * (time.second: i64)): time.duration, &out); + assert(out.termination == exec.termination.EXIT && out.code != 0); + assert(has(out.stderr, "package") + && has(out.stderr, "does not match import path")); + let badav: []str = [driver(aliasstages[i]), "-c", + "--test-support-module", "arbitrary", "-o", asmout, aliasunit]; + runcommand(root, strings.concat("alias-arbitrary-", aliasstages[i]), + badav, (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 2); + assert(has(out.stderr, "invalid --test-support-module")); + aliaserrs[i] = strings.dup(out.stderr); + i += 1; + }; + assert(same(aliaserrs[0], aliaserrs[1])); + clean(root); +}; + +// The compiler-generated runner uses a reserved graph qualifier rather than +// claiming the user-visible import path `test`. A real multi-file production +// package named test can therefore be the normal direct dependency of its +// external test variant in both bootstrap stages. +@test fn production_package_named_test_remains_available() void = { + let root: str = fresh(); + let pkg: str = strings.concat(root, "/test"); + assert(os.mkdir(pkg, 448i32) == 0); + writefile(strings.concat(pkg, "/a.ww"), strings.concat( + "package test;\n", + "// USER_TEST_PACKAGE_A\n", + "export fn first() i32 = { return 19; };\n")); + writefile(strings.concat(pkg, "/z.ww"), strings.concat( + "package test;\n", + "// USER_TEST_PACKAGE_Z\n", + "export fn second() i32 = { return 23; };\n")); + writefile(strings.concat(pkg, "/external_test.ww"), strings.concat( + "package test_test;\nimport test;\n", + "// USER_TEST_EXTERNAL\n", + "@test fn package_name_is_not_reserved() void = {\n", + " assert(test.first() + test.second() == 42);\n};\n")); + + let bin: str = strings.concat(root, "/named-test.bin"); + let workroot: str = strings.concat(bin, ".sepwork"); + let work: str = strings.concat(workroot, "/"); + let stages: []str = ["ww", "ww_ww"]; + let referencebin: str = ""; + let referenceroot: str = ""; + let referenceprod: str = ""; + let referencesupport: str = ""; + let out: commandout; + let i: i32 = 0; + for (i < stages.len) { + let av: []str = [driver(stages[i]), "test", "-c", "-o", bin, + pkg]; + runcommand(root, strings.concat("named-test-build-", stages[i]), av, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let rootunit: str = readfile(strings.concat(work, "__root.unit.ww")); + let produnit: str = readfile(strings.concat(work, "test.unit.ww")); + let supportunit: str = readfile(strings.concat(work, + "__wwtest.unit.ww")); + assert(has(rootunit, "//ww:module test\n")); + assert(has(rootunit, "//ww:module __wwtest\n")); + assert(has(rootunit, "USER_TEST_EXTERNAL")); + assert(!has(rootunit, "USER_TEST_PACKAGE_A")); + assert(!has(rootunit, "USER_TEST_PACKAGE_Z")); + assert(has(produnit, "USER_TEST_PACKAGE_A")); + assert(has(produnit, "USER_TEST_PACKAGE_Z")); + assert(has(supportunit, "//ww:module-reset __wwtest\n")); + assert(has(supportunit, "package test;\n")); + assert(os.exists(strings.concat(work, "test.wwi"))); + assert(os.exists(strings.concat(work, "test.a"))); + assert(os.exists(strings.concat(work, "__wwtest.wwi"))); + assert(os.exists(strings.concat(work, "__wwtest.a"))); + let runav: []str = [bin]; + runcommand(root, strings.concat("named-test-run-", stages[i]), runav, + (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "package_name_is_not_reserved ... ok\n")); + if (i == 0) { + referencebin = readfile(bin); + referenceroot = strings.dup(rootunit); + referenceprod = strings.dup(produnit); + referencesupport = strings.dup(supportunit); + } else { + assert(same(referencebin, readfile(bin))); + assert(same(referenceroot, rootunit)); + assert(same(referenceprod, produnit)); + assert(same(referencesupport, supportunit)); + }; + if (i + 1 < stages.len) { + clean(workroot); + clean(bin); + }; + i += 1; + }; + clean(root); +}; + +@test fn diamond_test_dependency_compiles_once() void = { + let root: str = fresh(); + let shared: str = strings.concat(root, "/shared"); + let left: str = strings.concat(root, "/left"); + let right: str = strings.concat(root, "/right"); + let target: str = strings.concat(root, "/diamond"); + assert(os.mkdir(shared, 448i32) == 0); + assert(os.mkdir(left, 448i32) == 0); + assert(os.mkdir(right, 448i32) == 0); + assert(os.mkdir(target, 448i32) == 0); + writefile(strings.concat(shared, "/shared.ww"), + "package shared;\nexport fn value() i32 = { return 20; };\n"); + writefile(strings.concat(left, "/left.ww"), strings.concat( + "package left;\nimport shared;\n", + "export fn value() i32 = { return shared.value(); };\n")); + writefile(strings.concat(right, "/right.ww"), strings.concat( + "package right;\nimport shared;\n", + "export fn value() i32 = { return shared.value() + 1; };\n")); + writefile(strings.concat(target, "/diamond.ww"), + "package diamond;\nfn local() i32 = { return 1; };\n"); + writefile(strings.concat(target, "/diamond_test.ww"), strings.concat( + "package diamond;\nimport left;\nimport right;\n", + "@test fn one_shared_compile() void = {\n", + " assert(left.value() + right.value() + local() == 42);\n};\n")); + let trace: str = strings.concat(root, "/compiler.trace"); + let wrapper: str = strings.concat(root, "/trace-w6c.sh"); + writefile(trace, ""); + writefile(wrapper, strings.concat( + "printf '%s\\n' \"$*\" >> \"$WW_PACKAGE_TRACE\"\n", + "exec \"$WW_PACKAGE_W6C\" \"$@\"\n")); + let baseenv: []str = os.getenvs(); + let env: []str = alloc([], (baseenv.len + 3): u64)!; + let ei: i32 = 0; + for (ei < baseenv.len) { + if (!strings.hasprefix(baseenv[ei], "WW_W6C=") + && !strings.hasprefix(baseenv[ei], "WW_PACKAGE_TRACE=") + && !strings.hasprefix(baseenv[ei], "WW_PACKAGE_W6C=")) { + append(env, baseenv[ei]); + }; + ei += 1; + }; + append(env, strings.concat("WW_W6C=/bin/sh ", wrapper)); + append(env, strings.concat("WW_PACKAGE_TRACE=", trace)); + append(env, strings.concat("WW_PACKAGE_W6C=", driver("w6c"))); + let bin: str = strings.concat(root, "/diamond.test"); + let av: []str = [driver("ww"), "test", "-c", "-o", bin, + "-I", root, target]; + let out: commandout; + runcommandenv(root, "diamond-build", av, env, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(occurrences(readfile(trace), "shared.unit.ww") == 1); + let work: str = strings.concat(bin, ".sepwork/"); + let unit: str = readfile(strings.concat(work, "__root.unit.ww")); + assert(has(unit, "//ww:module left\n")); + assert(has(unit, "//ww:module right\n")); + assert(!has(unit, "//ww:module shared\n")); + assert(has(readfile(strings.concat(work, "left.unit.ww")), + "//ww:module shared\n")); + assert(has(readfile(strings.concat(work, "right.unit.ww")), + "//ww:module shared\n")); + assert(os.exists(strings.concat(work, "shared.a"))); + let runav: []str = [bin]; + runcommand(root, "diamond-run", runav, + (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "one_shared_compile ... ok\n")); + clean(root); +}; + +@test fn package_graph_diagnostics_are_stable() void = { + let root: str = fresh(); + let missing: str = strings.concat(root, "/missing"); + let cycle: str = strings.concat(root, "/cycle"); + let left: str = strings.concat(root, "/left"); + let right: str = strings.concat(root, "/right"); + let conflict: str = strings.concat(root, "/conflict"); + let privatepkg: str = strings.concat(root, "/privatepkg"); + 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); + writefile(strings.concat(missing, "/missing.ww"), + "package missing;\nfn local() void = { };\n"); + writefile(strings.concat(missing, "/missing_test.ww"), strings.concat( + "package missing;\nimport nowhere;\n", + "@test fn missing_import() void = { };\n")); + writefile(strings.concat(cycle, "/cycle.ww"), + "package cycle;\nfn local() void = { };\n"); + writefile(strings.concat(cycle, "/cycle_test.ww"), strings.concat( + "package cycle;\nimport left;\n", + "@test fn cycle_import() void = { };\n")); + writefile(strings.concat(left, "/left.ww"), + "package left;\nimport right;\nexport fn v() i32 = { return right.v(); };\n"); + writefile(strings.concat(right, "/right.ww"), + "package right;\nimport left;\nexport fn v() i32 = { return left.v(); };\n"); + writefile(strings.concat(conflict, "/a.ww"), + "package conflict;\nfn a() void = { };\n"); + writefile(strings.concat(conflict, "/z.ww"), + "package other;\nfn z() void = { };\n"); + writefile(strings.concat(conflict, "/conflict_test.ww"), + "package conflict;\n@test fn conflict_name() void = { };\n"); + writefile(strings.concat(privatepkg, "/a.ww"), strings.concat( + "package privatepkg;\n", + "export fn shown() i32 = { return 1; };\n")); + writefile(strings.concat(privatepkg, "/z.ww"), + "package privatepkg;\nfn hidden() i32 = { return 2; };\n"); + 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")); + rejectpackagestable(root, "missing", missing, + "cannot find package nowhere"); + rejectpackagestable(root, "cycle", cycle, "ww: dependency cycle:"); + rejectpackagestable(root, "conflict", conflict, + "conflicting package names conflict and other"); + rejectpackagestable(root, "private", privatepkg, + "package 'privatepkg' has no exported declaration 'hidden'"); + clean(root); +}; + // Invalid @test attribute shapes reject at build with stable text on // BOTH frontends (the -T synth checker owns them; the fixture corpus // cannot reach -T, so these rows live here). Fragments only — the @@ -522,6 +1096,13 @@ fn packagepath(relative: str) str = { assert(os.mkdir(strings.concat(tree, "/gamma"), 448i32) == 0); assert(os.mkdir(strings.concat(tree, "/.hidden"), 448i32) == 0); assert(os.mkdir(strings.concat(tree, "/_skip"), 448i32) == 0); + writefile(strings.concat(tree, "/a.ww"), + "package rootpkg;\nfn first() i32 = { return 1; };\n"); + writefile(strings.concat(tree, "/root_test.ww"), strings.concat( + "package rootpkg;\n", + "@test fn treeroot() void = { assert(first() + last() == 3); };\n")); + writefile(strings.concat(tree, "/z.ww"), + "package rootpkg;\nfn last() i32 = { return 2; };\n"); writefile(strings.concat(tree, "/alpha/alpha.ww"), "package alpha;\nexport fn value() int = { return 7; };\n"); writefile(strings.concat(tree, "/alpha/alpha_test.ww"), @@ -548,12 +1129,25 @@ fn packagepath(relative: str) str = { expectexit(&outw, 0); assert(same(outc.stdout, outw.stdout)); assert(same(outc.stderr, outw.stderr)); + assert(has(outc.stdout, strings.concat("ok ", tree, + " [rootpkg, same-package]\n"))); assert(has(outc.stdout, strings.concat("ok ", tree, "/alpha [alpha_test, external]\n"))); assert(has(outc.stdout, strings.concat("ok ", tree, "/beta/inner [inner, same-package]\n"))); assert(has(outc.stdout, strings.concat("? ", tree, "/gamma [no tests]\n"))); + let rootreport: i32 = pos(outc.stdout, strings.concat("ok ", tree, + " [rootpkg, same-package]\n")); + let alphareport: i32 = pos(outc.stdout, strings.concat("ok ", tree, + "/alpha [alpha_test, external]\n")); + let innerreport: i32 = pos(outc.stdout, strings.concat("ok ", tree, + "/beta/inner [inner, same-package]\n")); + let gammareport: i32 = pos(outc.stdout, strings.concat("? ", tree, + "/gamma [no tests]\n")); + assert(rootreport >= 0 && rootreport < alphareport + && alphareport < innerreport && innerreport < gammareport); + assert(occurrences(outc.stdout, strings.concat(tree, " [")) == 1); assert(!has(outc.stdout, ".hidden")); assert(!has(outc.stdout, "_skip")); @@ -640,6 +1234,48 @@ fn packagepath(relative: str) str = { clean(root); }; +@test fn persistent_package_workdirs_do_not_collide() 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"); + assert(os.mkdir(tree, 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, + strings.concat(tree, "/...")]; + let out: commandout; + runcommand(root, "workkey-cold", av, + (60i64 * (time.second: i64)): time.duration, &out); + 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 akey: str = strings.concat(work, "/d_", workescape(a), "_p_", + workescape("b_c")); + let abkey: str = strings.concat(work, "/d_", workescape(ab), "_p_", + workescape("c")); + assert(!same(akey, abkey)); + assert(os.exists(akey) && os.exists(abkey)); + runcommand(root, "workkey-warm", av, + (60i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(same(cold, out.stdout)); + clean(root); +}; + fn runtimepath(relative: str) str = { return strings.concat(repo(), "/test/package/runtime/", relative); }; diff --git a/test/package/routing/b_white_test.ww b/test/package/routing/b_white_test.ww index a3318f16..68a358d1 100644 --- a/test/package/routing/b_white_test.ww +++ b/test/package/routing/b_white_test.ww @@ -1,5 +1,6 @@ package routing; @test fn private_helper_first() void = { + assert(privateproduction() == 8); assert(privatevalue() == 8); }; diff --git a/test/package/routing/detail.ww b/test/package/routing/detail.ww new file mode 100644 index 00000000..babc600d --- /dev/null +++ b/test/package/routing/detail.ww @@ -0,0 +1,5 @@ +package routing; + +fn privateproduction() int = { return 8; }; + +export fn secondvalue() int = { return 9; }; diff --git a/test/package/routing/external_test.ww b/test/package/routing/external_test.ww index 7ada780f..cd9404a8 100644 --- a/test/package/routing/external_test.ww +++ b/test/package/routing/external_test.ww @@ -4,4 +4,5 @@ import routing; @test fn external_package() void = { assert(routing.value() == 7); + assert(routing.secondvalue() == 9); }; diff --git a/test/package/routing/routing_test.ww b/test/package/routing/routing_test.ww index b03d4977..8bbd0cb1 100644 --- a/test/package/routing/routing_test.ww +++ b/test/package/routing/routing_test.ww @@ -2,5 +2,6 @@ package routing; @test fn same_package() void = { assert(value() == 7); + assert(privateproduction() == 8); assert(privatevalue() == 8); };