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 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, "\n//ww:module-reset example.foo\n", zfoosrc, "\n"); let foovoucher: str = "//ww:direct-export example.bar "; if (!strings.hasprefix(foounit, wantfoo) || testenv.occurrences(foounit, "//ww:direct-export ") != 1 || testenv.occurrences(foounit, foovoucher) != 1 || testenv.pos(foounit, foovoucher) != wantfoo.len || testenv.has(foounit, "DECOY_FILE") || testenv.has(foounit, "//ww:module ")) { fail("foo owner prefix/direct-export voucher mismatch"); }; let wantroot: str = strings.concat("//ww:module-reset ", appidentity, "\n", appsrc, "\n"); let rootunit: str = testenv.readfile(strings.concat(cwork, appidentity, ".unit.ww")); let rootvoucher: str = "//ww:direct-export example.foo "; if (!strings.hasprefix(rootunit, wantroot) || testenv.occurrences(rootunit, "//ww:direct-export ") != 1 || testenv.occurrences(rootunit, rootvoucher) != 1 || testenv.pos(rootunit, rootvoucher) != wantroot.len) { fail("root owner prefix/direct-export voucher mismatch"); }; let keys: []str = ["example.base", "example.bar", "example.foo", appidentity]; let suffixes: []str = [".unit.ww", ".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"); // 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", "-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, "-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); };