From 3e7d2581cc826023c1a32572fe47cf655d124c1c Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 12 Aug 2026 17:13:07 +0900 Subject: [PATCH] test: cover directory-only import resolution Exercise both real drivers with an earlier single-file decoy and a later canonical package directory, exact sorted unit ownership, direct export composition, deterministic package artifacts, transitive linking, stable file-only rejection, and a directory-package test import. --- Makefile | 2 +- test/sep/importdir_test.ww | 163 +++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 test/sep/importdir_test.ww diff --git a/Makefile b/Makefile index 12fcd2cf..cef7e55a 100644 --- a/Makefile +++ b/Makefile @@ -371,7 +371,7 @@ BYTEID_WW_TARGETS = $(BYTEID_WW_TESTS:%=wwtest/%) SEP_WW_TESTS = test/sep/sepbuild_test.ww test/sep/sepimport_test.ww \ test/sep/seplink_test.ww test/sep/sepscratch_test.ww \ test/sep/septest_test.ww test/sep/m3sep_test.ww \ - test/sep/localbuild_test.ww + test/sep/localbuild_test.ww test/sep/importdir_test.ww SEP_WW_TARGETS = $(SEP_WW_TESTS:%=wwtest/%) # Ww-native lib env/OS arranger observers: the env contracts of the # lib/os and lib/dirs suites (getenv cohorts, XDG cohorts, the dirs diff --git a/test/sep/importdir_test.ww b/test/sep/importdir_test.ww new file mode 100644 index 00000000..f1fe5e44 --- /dev/null +++ b/test/sep/importdir_test.ww @@ -0,0 +1,163 @@ +package importdir_test; + +import os; +import os.exec; +import strings; +import testenv; +import time; + +fn fail(why: str) void = { + let m: str = strings.concat("importdir FAIL: ", why, "\n"); + os.write(2, m.ptr, m.len: u64); + assert(false); +}; + +fn mkdir(path: str) void = { assert(os.mkdir(path, 493) == 0); }; + +fn command(td: str, name: str, argv: []str, + out: *testenv.commandout) void = { + testenv.runcommand(td, td, name, argv, + (240i64 * (time.second: i64)): time.duration, out); +}; + +fn expectcode(td: str, name: str, argv: []str, want: i32) void = { + let out: testenv.commandout; + command(td, name, argv, &out); + if (out.termination != exec.termination.EXIT || out.code != want) { + fail(strings.concat(name, " exit != expected: ", out.stderr)); + }; +}; + +fn samefile(a: str, b: str, why: str) void = { + if (!testenv.same(testenv.readfile(a), testenv.readfile(b))) { fail(why); }; +}; + +@test fn import_is_directory_package() void = { + let td: str = testenv.fresh(); + let early: str = strings.concat(td, "/early"); + let late: str = strings.concat(td, "/late"); + let earlyex: str = strings.concat(early, "/example"); + let lateex: str = strings.concat(late, "/example"); + let base: str = strings.concat(lateex, "/base"); + let bar: str = strings.concat(lateex, "/bar"); + let foo: str = strings.concat(lateex, "/foo"); + let app: str = strings.concat(td, "/app"); + let checks: str = strings.concat(td, "/checks"); + mkdir(early); mkdir(late); mkdir(earlyex); mkdir(lateex); + mkdir(base); mkdir(bar); mkdir(foo); mkdir(app); mkdir(checks); + + let decoy: str = strings.concat( + "package foo;\n// DECOY_FILE\n", + "export fn value() i32 = { return 99; };\n"); + let basesrc: str = strings.concat( + "package base;\n", + "export fn value() i32 = { return 37; };\n"); + let barsrc: str = strings.concat( + "package bar;\nimport example.base;\n", + "export fn value() i32 = { return base.value() + 3; };\n"); + let afoosrc: str = strings.concat( + "package foo;\n// A_OWNED\n", + "fn localdelta() i32 = { return 2; };\n"); + let zfoosrc: str = strings.concat( + "package foo;\nimport example.bar;\n// Z_OWNED\n", + "export fn value() i32 = { return bar.value() + localdelta(); };\n"); + let appsrc: str = strings.concat( + "package main;\nimport example.foo;\n", + "fn main() i32 = { return foo.value(); };\n"); + testenv.writefile(strings.concat(earlyex, "/foo.ww"), decoy); + testenv.writefile(strings.concat(base, "/base.ww"), basesrc); + testenv.writefile(strings.concat(bar, "/bar.ww"), barsrc); + testenv.writefile(strings.concat(foo, "/a.ww"), afoosrc); + testenv.writefile(strings.concat(foo, "/z.ww"), zfoosrc); + testenv.writefile(strings.concat(app, "/main.ww"), appsrc); + + let stems: []str = [strings.concat(td, "/c1"), + strings.concat(td, "/c2"), strings.concat(td, "/ww")]; + let drivers: []str = ["ww", "ww", "ww_ww"]; + let tags: []str = ["c1", "c2", "ww"]; + let i: i32 = 0; + for (i < stems.len) { + let av: []str = [testenv.driver(drivers[i]), "build", + "-I", early, "-I", late, "-o", stems[i], app]; + expectcode(td, strings.concat("build_", tags[i]), av, 0); + let rav: []str = [stems[i]]; + expectcode(td, strings.concat("run_", tags[i]), rav, 42); + i += 1; + }; + + 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 bariface: str = testenv.readfile(strings.concat(cwork, "example.bar.wwi")); + let fooiface: str = testenv.readfile(strings.concat(cwork, "example.foo.wwi")); + let foounit: str = testenv.readfile(strings.concat(cwork, + "example.foo.unit.ww")); + let wantfoo: str = strings.concat("//ww:module example.bar\n", bariface, + "\n//ww:module-reset example.foo\n", afoosrc, + "\n//ww:module-reset example.foo\n", zfoosrc, "\n"); + if (!testenv.same(foounit, wantfoo) + || testenv.has(foounit, "DECOY_FILE")) { + fail("foo unit is not its sorted, owned source set plus direct bar export"); + }; + let wantroot: str = strings.concat("//ww:module example.foo\n", fooiface, + "\n//ww:module-reset\n", appsrc, "\n"); + if (!testenv.same(wantroot, testenv.readfile(strings.concat(cwork, + "__root.unit.ww")))) { + fail("root unit is not direct-foo-only"); + }; + let keys: []str = ["example.base", "example.bar", "example.foo"]; + let suffixes: []str = [".wwi", ".a"]; + i = 0; + for (i < keys.len) { + let j: i32 = 0; + for (j < suffixes.len) { + let rel: str = strings.concat(keys[i], suffixes[j]); + samefile(strings.concat(cwork, rel), strings.concat(cwork2, rel), + strings.concat(rel, " changed across clean C builds")); + samefile(strings.concat(cwork, rel), strings.concat(wwork, rel), + strings.concat(rel, " differs between stages")); + j += 1; + }; + i += 1; + }; + samefile(stems[0], stems[1], "C executables are not deterministic"); + samefile(stems[0], stems[2], "C/WW executables differ"); + + let cerr: testenv.commandout; + let werr: testenv.commandout; + let cav: []str = [testenv.driver("ww"), "build", "-I", early, + "-o", strings.concat(td, "/bad-c"), app]; + let wav: []str = [testenv.driver("ww_ww"), "build", "-I", early, + "-o", strings.concat(td, "/bad-w"), app]; + command(td, "file_only_c", cav, &cerr); + command(td, "file_only_w", wav, &werr); + if (cerr.termination != exec.termination.EXIT || cerr.code == 0 + || werr.termination != exec.termination.EXIT || werr.code == 0 + || !testenv.same(cerr.stderr, werr.stderr) + || !testenv.has(cerr.stderr, "cannot find package example.foo") + || !testenv.has(cerr.stderr, strings.concat(app, "/main.ww:"))) { + fail("file-only import rejection is not stable and package-attributed"); + }; + + testenv.writefile(strings.concat(checks, "/checks.ww"), + "package checks;\nexport fn ready() bool = { return true; };\n"); + testenv.writefile(strings.concat(checks, "/checks_test.ww"), strings.concat( + "package checks;\nimport example.foo;\n", + "@test fn directory_import() void = { assert(foo.value() == 42); };\n")); + let testbins: []str = [strings.concat(td, "/test-c"), + strings.concat(td, "/test-w")]; + i = 0; + for (i < 2) { + let tav: []str = [testenv.driver(drivers[i * 2]), "test", "-c", + "-I", early, "-I", late, "-o", testbins[i], checks]; + expectcode(td, strings.concat("test_build_", tags[i * 2]), tav, 0); + let trav: []str = [testbins[i]]; + expectcode(td, strings.concat("test_run_", tags[i * 2]), trav, 0); + i += 1; + }; + samefile(testbins[0], testbins[1], "directory-test binaries differ"); + samefile(strings.concat(testbins[0], ".sepwork/example.foo.a"), + strings.concat(testbins[1], ".sepwork/example.foo.a"), + "directory-test dependency archives differ"); + testenv.clean(td); +};