Files
ww/test/sep/seplink_test.ww
Hojun-Cho ad189c2ee2 test: port the sep archive/link observers to ww; retire 989_{separchive,sepcycle_dup}
seplink_test.ww carries the .a substrate legs (root-as-.o layout, .a
byte-id + 3x determinism, the #31 masked-dup PASS-3 reject with its
non-vacuity flip), the loud dependency-cycle reject with byte-equal
cs/ww stderr, and the w6l/w6l_ww bare-main duplicate-symbol reject
(achievable substring parity per the filed message divergence).
2026-08-08 14:13:50 +09:00

362 lines
13 KiB
Plaintext

package seplink_test;
// Archive-substrate + link-reject observers, both stages. Ports of the
// retired native carriers test/wcc/989_separchive_run.c and
// 989_sepcycle_dup.c; every assertion preserved.
//
// archive (#46 commit-5a) — `ww build` wraps each DEP package's .o in
// a deterministic single-member .a and links the ROOT as a positional
// .o (force-loaded): build+run exit 7 both stages; __root.a absent,
// __root.o + helper.a present; cs helper.a == ww helper.a (rule 10,
// the .a byte-id substrate); 3 cold cstage rebuilds emit
// byte-identical helper.a (zeroed mtime/uid/gid, fixed mode/member —
// a floating byte would poison the content cache key).
//
// archivedup (#31 PASS 3) — two dep packages force the same link
// symbol via @symbol("dup_sym"); one member is pulled, the other
// lands UNPULLED defining an already-defined name — exactly what
// selective pull skips and the post-pull PASS 3 catches: both stages
// exit non-zero, name "duplicate symbol", and leave no partial
// binary. Non-vacuity flip: a distinct symbol builds + runs (exit 3).
//
// cycle (#46 commit-4) — a 3-package import cycle
// root->pkga->pkgb->pkgc->pkga is LOUD-rejected: non-zero exit, the
// `dependency cycle: pkga -> pkgb -> pkgc -> pkga` chain named, no
// partial binary, and cs stderr == ww stderr BYTE-IDENTICAL (the
// message is pure driver code, rule 10). Non-vacuity flip: the
// acyclic tree builds + runs (exit 7).
//
// linkdup (#31) — two hand-assembled .o each defining bare `main` fed
// to w6l AND w6l_ww: non-zero, "duplicate symbol", no partial binary.
// ACHIEVABLE parity only — substring, NOT byte-equal stderr: cstage
// prints `<path>: duplicate symbol <sym>`, wwstage the bare form (a
// pre-existing divergence filed with the carrier, not reconciled
// here). Non-vacuity: a single .o + libwwrt.a links and runs clean.
//
// Dropped C machinery, not assertions: per-path unlink/rmdir
// accounting (testenv.clean asserts the removal).
import os;
import os.exec;
import strings;
import testenv;
import time;
fn fail(label: str, why: str) void = {
let m: str = strings.concat("seplink FAIL: ", label, " -- ", why,
"\n");
os.write(2, m.ptr, m.len: u64);
assert(false);
};
fn tmo() time.duration = {
return (240i64 * (time.second: i64)): time.duration;
};
fn run(dir: str, name: str, argv: []str, out: *testenv.commandout) void = {
testenv.runcommand(dir, dir, name, argv, tmo(), out);
};
// -1 encodes an abnormal (non-EXIT) termination, never a valid code.
fn runcode(dir: str, name: str, argv: []str) i32 = {
let co: testenv.commandout;
run(dir, name, argv, &co);
if (co.termination != exec.termination.EXIT) { return -1; };
return co.code;
};
// ---- archive (#46 commit-5a) -------------------------------------------
@test fn archive() void = {
let td: str = testenv.fresh();
assert(os.mkdir(strings.concat(td, "/helper"), 493) == 0);
testenv.writefile(strings.concat(td, "/helper/h.ww"), strings.concat(
"package helper;\n",
"export fn val() i32 = { return 7; };\n"));
let rootww: str = strings.concat(td, "/root.ww");
testenv.writefile(rootww, strings.concat(
"package main;\n",
"import helper;\n",
"fn main() i32 = { return helper.val(); };\n"));
let drvs: []str = ["ww", "ww_ww"];
let tags: []str = ["cs", "ww"];
let s: i32 = 0;
for (s < 2) {
let prog: str = strings.concat(td, "/prog.", tags[s]);
let av: []str = [testenv.driver(drvs[s]), "build", "-I", td,
"-o", prog, rootww];
if (runcode(td, strings.concat("build_", tags[s]), av) != 0) {
fail("archive", strings.concat(drvs[s], " build failed"));
};
let rav: []str = [prog];
if (runcode(td, strings.concat("runbin_", tags[s]), rav) != 7) {
fail("archive", strings.concat(drvs[s], " prog exit != 7"));
};
s += 1;
};
// layout: root stays a positional force-loaded .o, deps become .a
if (testenv.exists(strings.concat(td, "/prog.cs.sepwork/__root.a"))) {
fail("archive", "root wrapped in .a (should stay positional .o)");
};
if (!testenv.exists(strings.concat(td, "/prog.cs.sepwork/__root.o"))) {
fail("archive", "missing root .o");
};
if (!testenv.exists(strings.concat(td, "/prog.cs.sepwork/helper.a"))) {
fail("archive", "missing dep helper.a");
};
// rule 10: the .a byte-id substrate
if (!testenv.same(
testenv.readfile(strings.concat(td, "/prog.cs.sepwork/helper.a")),
testenv.readfile(strings.concat(td, "/prog.ww.sepwork/helper.a")))) {
fail("archive", "cs helper.a != ww helper.a (rule 10 .a byte-id)");
};
// determinism: 3 cold cstage rebuilds -> byte-identical .a
let det0: str = strings.concat(td, "/det0");
let det1: str = strings.concat(td, "/det1");
let det2: str = strings.concat(td, "/det2");
let av0: []str = [testenv.driver("ww"), "build", "-I", td, "-o", det0,
rootww];
if (runcode(td, "det0", av0) != 0) { fail("archive", "det0 build"); };
let av1: []str = [testenv.driver("ww"), "build", "-I", td, "-o", det1,
rootww];
if (runcode(td, "det1", av1) != 0) { fail("archive", "det1 build"); };
let av2: []str = [testenv.driver("ww"), "build", "-I", td, "-o", det2,
rootww];
if (runcode(td, "det2", av2) != 0) { fail("archive", "det2 build"); };
let a0: str = testenv.readfile(strings.concat(det0,
".sepwork/helper.a"));
let a1: str = testenv.readfile(strings.concat(det1,
".sepwork/helper.a"));
let a2: str = testenv.readfile(strings.concat(det2,
".sepwork/helper.a"));
if (!testenv.same(a0, a1) || !testenv.same(a1, a2)) {
fail("archive", strings.concat(".a not deterministic across 3 ",
"builds (floating bytes poison the cache key)"));
};
testenv.clean(td);
};
// ---- archivedup (#31 through .a, PASS 3) -------------------------------
@test fn archivedup() void = {
let td: str = testenv.fresh();
assert(os.mkdir(strings.concat(td, "/pkga"), 493) == 0);
assert(os.mkdir(strings.concat(td, "/pkgb"), 493) == 0);
testenv.writefile(strings.concat(td, "/pkga/a.ww"), strings.concat(
"package pkga;\n",
"@symbol(\"dup_sym\") export fn afn() i32 = { return 1; };\n"));
let pbww: str = strings.concat(td, "/pkgb/b.ww");
testenv.writefile(pbww, strings.concat(
"package pkgb;\n",
"@symbol(\"dup_sym\") export fn bfn() i32 = { return 2; };\n"));
let droot: str = strings.concat(td, "/droot.ww");
testenv.writefile(droot, strings.concat(
"package main;\n",
"import pkga;\n",
"import pkgb;\n",
"fn main() i32 = { return pkga.afn() + pkgb.bfn(); };\n"));
let drvs: []str = ["ww", "ww_ww"];
let tags: []str = ["cs", "ww"];
let s: i32 = 0;
for (s < 2) {
let prog: str = strings.concat(td, "/dup.", tags[s]);
let av: []str = [testenv.driver(drvs[s]), "build", "-I", td,
"-o", prog, droot];
let co: testenv.commandout;
run(td, strings.concat("dup_", tags[s]), av, &co);
if (co.termination == exec.termination.EXIT && co.code == 0) {
fail("archivedup", strings.concat(drvs[s], " accepted a masked ",
"cross-pkg dup through .a (PASS 3 missing?)"));
};
if (!testenv.has(co.stderr, "duplicate symbol")) {
fail("archivedup", strings.concat(drvs[s],
" missing 'duplicate symbol' on the .a dup path"));
};
if (testenv.exists(prog)) {
fail("archivedup", strings.concat(drvs[s],
" produced a partial binary on the dup reject"));
};
s += 1;
};
// non-vacuity flip: a distinct symbol builds + runs (1 + 2)
assert(os.remove(pbww) == 0);
testenv.writefile(pbww, strings.concat(
"package pkgb;\n",
"@symbol(\"uniq_sym\") export fn bfn() i32 = { return 2; };\n"));
let s2: i32 = 0;
for (s2 < 2) {
let prog: str = strings.concat(td, "/ok.", tags[s2]);
let av: []str = [testenv.driver(drvs[s2]), "build", "-I", td,
"-o", prog, droot];
if (runcode(td, strings.concat("ok_", tags[s2]), av) != 0) {
fail("archivedup", strings.concat(drvs[s2], " could not build ",
"the distinct-symbol graph (non-vacuity)"));
};
let rav: []str = [prog];
if (runcode(td, strings.concat("okrun_", tags[s2]), rav) != 3) {
fail("archivedup", strings.concat(drvs[s2],
" flip prog exit != 3 (non-vacuity)"));
};
s2 += 1;
};
testenv.clean(td);
};
// ---- cycle (#46 commit-4 A) --------------------------------------------
@test fn cycle() void = {
let td: str = testenv.fresh();
assert(os.mkdir(strings.concat(td, "/pkga"), 493) == 0);
assert(os.mkdir(strings.concat(td, "/pkgb"), 493) == 0);
assert(os.mkdir(strings.concat(td, "/pkgc"), 493) == 0);
let rootww: str = strings.concat(td, "/root.ww");
testenv.writefile(rootww, strings.concat(
"package main;\n",
"import pkga;\n",
"fn main() i32 = { return pkga.av(); };\n"));
testenv.writefile(strings.concat(td, "/pkga/a.ww"), strings.concat(
"package pkga;\n",
"import pkgb;\n",
"export fn av() i32 = { return pkgb.bv(); };\n"));
testenv.writefile(strings.concat(td, "/pkgb/b.ww"), strings.concat(
"package pkgb;\n",
"import pkgc;\n",
"export fn bv() i32 = { return pkgc.cv(); };\n"));
let fc: str = strings.concat(td, "/pkgc/c.ww");
testenv.writefile(fc, strings.concat(
"package pkgc;\n",
"import pkga;\n",
"export fn cv() i32 = { return pkga.av(); };\n"));
let drvs: []str = ["ww", "ww_ww"];
let tags: []str = ["cs", "ww"];
let errs: []str = ["", ""];
let s: i32 = 0;
for (s < 2) {
let prog: str = strings.concat(td, "/prog.", tags[s]);
let av: []str = [testenv.driver(drvs[s]), "build", "-o", prog,
rootww];
let co: testenv.commandout;
run(td, strings.concat("cyc_", tags[s]), av, &co);
if (co.termination == exec.termination.EXIT && co.code == 0) {
fail("cycle", strings.concat(drvs[s],
" build returned 0 (no-op reject)"));
};
if (!testenv.has(co.stderr,
"dependency cycle: pkga -> pkgb -> pkgc -> pkga")) {
fail("cycle", strings.concat(drvs[s],
" missing the cycle-chain message"));
};
if (testenv.exists(prog)) {
fail("cycle", strings.concat(drvs[s],
" produced a partial binary"));
};
errs[s] = co.stderr;
s += 1;
};
// the cycle message is pure driver code -> byte-identical stderr
if (!testenv.same(errs[0], errs[1])) {
fail("cycle", "cs stderr != ww stderr (rule 10)");
};
// non-vacuity flip: break the cycle -> both stages build + run
assert(os.remove(fc) == 0);
testenv.writefile(fc, strings.concat(
"package pkgc;\n",
"export fn cv() i32 = { return 7; };\n"));
let s2: i32 = 0;
for (s2 < 2) {
let prog: str = strings.concat(td, "/ok.", tags[s2]);
let av: []str = [testenv.driver(drvs[s2]), "build", "-o", prog,
rootww];
if (runcode(td, strings.concat("cycok_", tags[s2]), av) != 0) {
fail("cycle", strings.concat(drvs[s2], " could not build the ",
"acyclic graph (non-vacuity)"));
};
let rav: []str = [prog];
if (runcode(td, strings.concat("cycokrun_", tags[s2]), rav) != 7) {
fail("cycle", strings.concat(drvs[s2],
" flip prog exit != 7 (non-vacuity)"));
};
s2 += 1;
};
testenv.clean(td);
};
// ---- linkdup (#31 at the linker) ---------------------------------------
@test fn linkdup() void = {
let td: str = testenv.fresh();
let u1: str = strings.concat(td, "/u1.ww");
let u2: str = strings.concat(td, "/u2.ww");
testenv.writefile(u1, strings.concat("package main;\n",
"fn main() i32 = { return 0; };\n"));
testenv.writefile(u2, strings.concat("package main;\n",
"fn main() i32 = { return 1; };\n"));
let o1: str = strings.concat(td, "/u1.o");
let o2: str = strings.concat(td, "/u2.o");
let names: []str = ["u1", "u2"];
let n: i32 = 0;
for (n < 2) {
let sf: str = strings.concat(td, "/", names[n], ".s");
let cav: []str = [testenv.driver("w6c"), "-c", "-o", sf,
strings.concat(td, "/", names[n], ".ww")];
if (runcode(td, strings.concat("c_", names[n]), cav) != 0) {
fail("linkdup", strings.concat("w6c -c ", names[n], " failed"));
};
let aav: []str = [testenv.driver("w6a"), "-o",
strings.concat(td, "/", names[n], ".o"), sf];
if (runcode(td, strings.concat("a_", names[n]), aav) != 0) {
fail("linkdup", strings.concat("w6a ", names[n], " failed"));
};
n += 1;
};
let lnks: []str = ["w6l", "w6l_ww"];
let l: i32 = 0;
for (l < 2) {
let prog: str = strings.concat(td, "/dupprog.", lnks[l]);
let av: []str = [testenv.driver(lnks[l]), "-o", prog, o1, o2];
let co: testenv.commandout;
run(td, strings.concat("dup_", lnks[l]), av, &co);
if (co.termination == exec.termination.EXIT && co.code == 0) {
fail("linkdup", strings.concat(lnks[l],
" accepted a duplicate symbol (exit 0)"));
};
if (!testenv.has(co.stderr, "duplicate symbol")) {
fail("linkdup", strings.concat(lnks[l],
" missing 'duplicate symbol' message"));
};
if (testenv.exists(prog)) {
fail("linkdup", strings.concat(lnks[l],
" produced a partial binary on the reject path"));
};
l += 1;
};
// non-vacuity: a single .o + libwwrt.a links clean and runs
let rt: str = strings.concat(testenv.repo(), "/out/lib/libwwrt.a");
let l2: i32 = 0;
for (l2 < 2) {
let prog: str = strings.concat(td, "/single.", lnks[l2]);
let av: []str = [testenv.driver(lnks[l2]), "-o", prog, o1, rt];
if (runcode(td, strings.concat("single_", lnks[l2]), av) != 0) {
fail("linkdup", strings.concat(lnks[l2],
" could not link a single .o (non-vacuity)"));
};
let rav: []str = [prog];
if (runcode(td, strings.concat("singlerun_", lnks[l2]), rav) != 0) {
fail("linkdup", strings.concat(lnks[l2],
" single prog did not run clean (non-vacuity)"));
};
l2 += 1;
};
testenv.clean(td);
};