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).
This commit is contained in:
361
test/sep/seplink_test.ww
Normal file
361
test/sep/seplink_test.ww
Normal file
@@ -0,0 +1,361 @@
|
||||
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);
|
||||
};
|
||||
@@ -1,416 +0,0 @@
|
||||
/*
|
||||
* 989_separchive_run — M3-tail commit-5a gate (#46, task #62): the
|
||||
* per-package `.a` substrate + its archive-path #31 dup-detect, both
|
||||
* stages, COLD. Commit 5a makes `ww build` wrap each DEP package's
|
||||
* `.o` in a deterministic single-member `.a` (cstage archive_o /
|
||||
* wwstage archiveo) and link the ROOT as a positional `.o` (force-loaded)
|
||||
* + dep `.a` reverse-topo + libwwrt.a. The #31 dup the selective pull
|
||||
* would mask is caught by a post-pull PASS 3 in w6l/w6l_ww load_archive.
|
||||
*
|
||||
* Legs (fresh output stems under an invocation-owned root; retained
|
||||
* `<stem>.sepwork` is inspected, then removed by final carrier cleanup):
|
||||
* 1. POSITIVE: root→helper builds + runs exit 7, BOTH stages, through
|
||||
* the `.a` link path (root `.o` + helper `.a`).
|
||||
* 2. ★ DETERMINISM (ken, load-bearing): re-archive the same package 3×
|
||||
* → byte-identical `.a` (proves zeroed mtime/uid/gid + fixed mode +
|
||||
* fixed member name; a floating md5 would poison the 5b cache key).
|
||||
* 3. ★ cs `.a` == ww `.a` (rule 10, the NEW byte-id substrate ken binds):
|
||||
* the dep `.a` from `ww` is byte-identical to the one from
|
||||
* `ww_ww`.
|
||||
* 4. ★ #31 dup THROUGH the `.a` path (ken #263 + D3, the leg that proves
|
||||
* PASS 3 closed the selective-pull hole): two DEP packages each export
|
||||
* the same link symbol via `@symbol("dup_sym")`, referenced by root.
|
||||
* One dep's member is pulled; the OTHER lands UNPULLED and defines an
|
||||
* already-`defined` name — exactly what selective-pull skips. BOTH
|
||||
* stages must (exit≠0) ∧ (stderr contains "duplicate symbol") ∧ (no
|
||||
* partial binary). NON-VACUITY flip: give the second dep a DISTINCT
|
||||
* symbol → both stages build + run (exit 3), proving the leg actually
|
||||
* discriminates (without PASS 3 the dup leg would WRONGLY pass green).
|
||||
* 5. ACHIEVABLE-parity posture (same as c4's 989_sepcycle_dup, because
|
||||
* #61 is a separate commit): cs vs ww is `both exit≠0 ∧ both stderrs
|
||||
* contain "duplicate symbol"` — NOT exact-stderr-equal (cstage names
|
||||
* `<path>: duplicate symbol <sym>`, wwstage the bare form).
|
||||
*
|
||||
* Light wwstage-driver test (CLAUDE.md rule 14): all fixtures + scratch
|
||||
* live under /tmp, COLD each run. Models 989_sepbuild_run.c conventions.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define EXPECT_EXIT 7
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const char *
|
||||
absbin(void)
|
||||
{
|
||||
const char *b = getenv("BIN");
|
||||
if (!b) b = "out/bin";
|
||||
if (b[0] == '/') return b;
|
||||
static char buf[2048];
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return NULL;
|
||||
snprintf(buf, sizeof buf, "%s/%s", cwd, b);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int
|
||||
slurp(const char *path, char **outbuf, size_t *outlen)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return -1;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long n = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if (n < 0) { fclose(f); return -1; }
|
||||
char *b = malloc((size_t)n + 1);
|
||||
if (!b) { fclose(f); return -1; }
|
||||
if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; }
|
||||
b[n] = '\0';
|
||||
fclose(f);
|
||||
*outbuf = b;
|
||||
*outlen = (size_t)n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
files_eq(const char *a, const char *b)
|
||||
{
|
||||
char *ba = NULL, *bb = NULL;
|
||||
size_t na = 0, nb = 0;
|
||||
if (slurp(a, &ba, &na) < 0 || slurp(b, &bb, &nb) < 0) {
|
||||
free(ba); free(bb);
|
||||
return -1;
|
||||
}
|
||||
int eq = (na == nb && memcmp(ba, bb, na) == 0);
|
||||
free(ba); free(bb);
|
||||
return eq ? 0 : 1;
|
||||
}
|
||||
|
||||
static int
|
||||
file_has(const char *path, const char *needle)
|
||||
{
|
||||
char *b = NULL;
|
||||
size_t n = 0;
|
||||
if (slurp(path, &b, &n) < 0) return 0;
|
||||
int found = (strstr(b, needle) != NULL);
|
||||
free(b);
|
||||
return found;
|
||||
}
|
||||
|
||||
static int
|
||||
write_file(const char *path, const char *body)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(body, f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = absbin();
|
||||
if (!bin) return 1;
|
||||
char td[64], cmd[8192];
|
||||
int fail = 0, have_helpdir = 0, have_pkgad = 0, have_pkgbd = 0;
|
||||
|
||||
snprintf(td, sizeof td, "/tmp/wwar_%d", getpid());
|
||||
if (mkdir(td, 0755) != 0) {
|
||||
fprintf(stderr, "separchive FAIL: cannot acquire %s\n", td);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ---- root → helper fixture (one dep `.a`) ------------------------ */
|
||||
char helpdir[1024], helpww[1100], rootww[1100];
|
||||
snprintf(helpdir, sizeof helpdir, "%s/helper", td);
|
||||
if (mkdir(helpdir, 0755) != 0) {
|
||||
fprintf(stderr, "separchive FAIL: cannot create %s\n", helpdir);
|
||||
fail++; goto out;
|
||||
}
|
||||
have_helpdir = 1;
|
||||
snprintf(helpww, sizeof helpww, "%s/h.ww", helpdir);
|
||||
snprintf(rootww, sizeof rootww, "%s/root.ww", td);
|
||||
if (write_file(helpww,
|
||||
"package helper;\n"
|
||||
"export fn val() i32 = { return 7; };\n") ||
|
||||
write_file(rootww,
|
||||
"package main;\n"
|
||||
"import helper;\n"
|
||||
"fn main() i32 = { return helper.val(); };\n")) {
|
||||
fail++; goto out;
|
||||
}
|
||||
|
||||
struct { const char *drv, *tag; char prog[1024]; }
|
||||
stg[] = { { "ww", "cs", {0} }, { "ww_ww", "ww", {0} } };
|
||||
|
||||
/* Leg 1: POSITIVE build + run through the `.a` path, both stages. */
|
||||
for (int s = 0; s < 2; s++) {
|
||||
snprintf(stg[s].prog, sizeof stg[s].prog, "%s/prog.%s", td, stg[s].tag);
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"timeout 240 %s/%s build -I %s -o %s %s >/dev/null 2>&1",
|
||||
bin, stg[s].drv, td, stg[s].prog, rootww);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "separchive FAIL: %s build\n", stg[s].drv);
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
int rc = runwait(stg[s].prog);
|
||||
if (rc != EXPECT_EXIT) {
|
||||
fprintf(stderr, "separchive FAIL: %s prog exit=%d expected %d\n",
|
||||
stg[s].drv, rc, EXPECT_EXIT);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Root is a positional `.o` (force-loaded), so NO __root.a exists;
|
||||
* the dep is the only `.a`. Assert the layout the driver produced. */
|
||||
{
|
||||
char roota[1100], rooto[1100], helpa[1100];
|
||||
snprintf(roota, sizeof roota, "%s/prog.cs.sepwork/__root.a", td);
|
||||
snprintf(rooto, sizeof rooto, "%s/prog.cs.sepwork/__root.o", td);
|
||||
snprintf(helpa, sizeof helpa, "%s/prog.cs.sepwork/helper.a", td);
|
||||
if (access(roota, 0) == 0) {
|
||||
fprintf(stderr, "separchive FAIL: root wrapped in .a "
|
||||
"(should stay positional .o)\n");
|
||||
fail++;
|
||||
}
|
||||
if (access(rooto, 0) != 0) {
|
||||
fprintf(stderr, "separchive FAIL: missing root .o\n");
|
||||
fail++;
|
||||
}
|
||||
if (access(helpa, 0) != 0) {
|
||||
fprintf(stderr, "separchive FAIL: missing dep helper.a\n");
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Leg 3: cs `.a` == ww `.a` (rule 10, the new byte-id substrate). */
|
||||
{
|
||||
char a[1100], b[1100];
|
||||
snprintf(a, sizeof a, "%s/prog.cs.sepwork/helper.a", td);
|
||||
snprintf(b, sizeof b, "%s/prog.ww.sepwork/helper.a", td);
|
||||
if (files_eq(a, b) != 0) {
|
||||
fprintf(stderr, "separchive FAIL: cs helper.a != ww helper.a "
|
||||
"(rule 10 .a byte-id)\n");
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Leg 2: DETERMINISM — re-archive the same package 3× (cold each),
|
||||
* the `.a` must be byte-identical (zeroed mtime/uid/gid, fixed mode,
|
||||
* fixed member name). Copy each build's helper.a aside, compare. */
|
||||
{
|
||||
char det[3][1100];
|
||||
int ok = 1;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
char prog[1100];
|
||||
snprintf(prog, sizeof prog, "%s/det%d", td, i);
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"timeout 240 %s/ww build -I %s -o %s %s >/dev/null 2>&1",
|
||||
bin, td, prog, rootww);
|
||||
if (runwait(cmd) != 0) { ok = 0; break; }
|
||||
snprintf(det[i], sizeof det[i], "%s/det%d.sepwork/helper.a", td, i);
|
||||
}
|
||||
if (!ok) {
|
||||
fprintf(stderr, "separchive FAIL: determinism build\n");
|
||||
fail++;
|
||||
} else if (files_eq(det[0], det[1]) != 0 ||
|
||||
files_eq(det[1], det[2]) != 0) {
|
||||
fprintf(stderr, "separchive FAIL: .a not deterministic across "
|
||||
"3 builds (floating md5 → poisons 5b cache)\n");
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Leg 4: #31 dup THROUGH the `.a` path (PASS 3) --------------- */
|
||||
/* Two DEP packages each export the SAME link symbol via @symbol;
|
||||
* root references both. One member is pulled, the other lands
|
||||
* UNPULLED defining an already-`defined` name → the masked dup PASS 3
|
||||
* catches (#53 path-qualifies normal exports, so @symbol is the
|
||||
* cleanest forced cross-package clash). */
|
||||
char pkgad[1024], pkgbd[1024], paww[1100], pbww[1100], droot[1100];
|
||||
snprintf(pkgad, sizeof pkgad, "%s/pkga", td);
|
||||
snprintf(pkgbd, sizeof pkgbd, "%s/pkgb", td);
|
||||
if (mkdir(pkgad, 0755) != 0) {
|
||||
fprintf(stderr, "separchive FAIL: cannot create %s\n", pkgad);
|
||||
fail++; goto out;
|
||||
}
|
||||
have_pkgad = 1;
|
||||
if (mkdir(pkgbd, 0755) != 0) {
|
||||
fprintf(stderr, "separchive FAIL: cannot create %s\n", pkgbd);
|
||||
fail++; goto out;
|
||||
}
|
||||
have_pkgbd = 1;
|
||||
snprintf(paww, sizeof paww, "%s/a.ww", pkgad);
|
||||
snprintf(pbww, sizeof pbww, "%s/b.ww", pkgbd);
|
||||
snprintf(droot, sizeof droot, "%s/droot.ww", td);
|
||||
if (write_file(paww,
|
||||
"package pkga;\n"
|
||||
"@symbol(\"dup_sym\") export fn afn() i32 = { return 1; };\n") ||
|
||||
write_file(pbww,
|
||||
"package pkgb;\n"
|
||||
"@symbol(\"dup_sym\") export fn bfn() i32 = { return 2; };\n") ||
|
||||
write_file(droot,
|
||||
"package main;\n"
|
||||
"import pkga;\n"
|
||||
"import pkgb;\n"
|
||||
"fn main() i32 = { return pkga.afn() + pkgb.bfn(); };\n")) {
|
||||
fail++; goto out;
|
||||
}
|
||||
|
||||
for (int s = 0; s < 2; s++) {
|
||||
char prog[1100], errf[1100];
|
||||
snprintf(prog, sizeof prog, "%s/dup.%s", td, stg[s].tag);
|
||||
snprintf(errf, sizeof errf, "%s/dup.%s.err", td, stg[s].tag);
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"timeout 240 %s/%s build -I %s -o %s %s >/dev/null 2>%s",
|
||||
bin, stg[s].drv, td, prog, droot, errf);
|
||||
int rc = runwait(cmd);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "separchive FAIL: %s accepted a masked "
|
||||
"cross-pkg dup through .a (exit 0 — PASS 3 missing?)\n",
|
||||
stg[s].drv);
|
||||
fail++;
|
||||
}
|
||||
if (!file_has(errf, "duplicate symbol")) {
|
||||
fprintf(stderr, "separchive FAIL: %s missing 'duplicate "
|
||||
"symbol' on the .a dup path\n", stg[s].drv);
|
||||
fail++;
|
||||
}
|
||||
if (access(prog, 0) == 0) {
|
||||
fprintf(stderr, "separchive FAIL: %s produced a partial "
|
||||
"binary on the dup reject\n", stg[s].drv);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Leg 4 NON-VACUITY: give pkgb a DISTINCT symbol → no collision →
|
||||
* both stages build + run (exit 3 = 1 + 2). Proves the dup leg
|
||||
* actually discriminates (not a vacuous always-fail). */
|
||||
if (write_file(pbww,
|
||||
"package pkgb;\n"
|
||||
"@symbol(\"uniq_sym\") export fn bfn() i32 = { return 2; };\n")) {
|
||||
fail++; goto out;
|
||||
}
|
||||
for (int s = 0; s < 2; s++) {
|
||||
char prog[1100];
|
||||
snprintf(prog, sizeof prog, "%s/ok.%s", td, stg[s].tag);
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"timeout 240 %s/%s build -I %s -o %s %s >/dev/null 2>&1",
|
||||
bin, stg[s].drv, td, prog, droot);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "separchive FAIL(non-vacuity): %s could not "
|
||||
"build the distinct-symbol graph\n", stg[s].drv);
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
int rc = runwait(prog);
|
||||
if (rc != 3) {
|
||||
fprintf(stderr, "separchive FAIL(non-vacuity): %s prog "
|
||||
"exit=%d expected 3\n", stg[s].drv, rc);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"rm -rf -- '%s/prog.cs.sepwork' '%s/prog.ww.sepwork' "
|
||||
"'%s/det0.sepwork' '%s/det1.sepwork' '%s/det2.sepwork' "
|
||||
"'%s/dup.cs.sepwork' '%s/dup.ww.sepwork' "
|
||||
"'%s/ok.cs.sepwork' '%s/ok.ww.sepwork'",
|
||||
td, td, td, td, td, td, td, td, td);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "separchive FAIL: cannot clean .sepwork trees\n");
|
||||
fail++;
|
||||
}
|
||||
{
|
||||
char path[1200];
|
||||
snprintf(path, sizeof path, "%s/root.ww", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/droot.ww", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/prog.cs", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/prog.ww", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/det0", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/det1", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/det2", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/dup.cs", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/dup.ww", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/dup.cs.err", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/dup.ww.err", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/ok.cs", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/ok.ww", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
|
||||
}
|
||||
if (have_helpdir) {
|
||||
if (unlink(helpww) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "separchive FAIL: cannot remove %s\n", helpww);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (have_pkgad) {
|
||||
char path[1200];
|
||||
snprintf(path, sizeof path, "%s/a.ww", pkgad);
|
||||
if (unlink(path) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "separchive FAIL: cannot remove %s\n", path);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (have_pkgbd) {
|
||||
char path[1200];
|
||||
snprintf(path, sizeof path, "%s/b.ww", pkgbd);
|
||||
if (unlink(path) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "separchive FAIL: cannot remove %s\n", path);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (have_helpdir && rmdir(helpdir) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "separchive FAIL: cannot remove %s\n", helpdir);
|
||||
fail++;
|
||||
}
|
||||
if (have_pkgad && rmdir(pkgad) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "separchive FAIL: cannot remove %s\n", pkgad);
|
||||
fail++;
|
||||
}
|
||||
if (have_pkgbd && rmdir(pkgbd) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "separchive FAIL: cannot remove %s\n", pkgbd);
|
||||
fail++;
|
||||
}
|
||||
if (rmdir(td) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "separchive FAIL: cannot remove %s\n", td);
|
||||
fail++;
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr, "separchive: %d check(s) failed\n", fail);
|
||||
return 1;
|
||||
}
|
||||
printf("separchive: per-pkg .a (root=.o force-load, deps=.a) build+run "
|
||||
"(exit %d) + .a determinism (3x identical) + cs.a==ww.a (rule 10) + "
|
||||
"#31 masked-dup reject THROUGH .a (PASS 3, both stages loud+non-zero, "
|
||||
"non-vacuity flip exit 3)\n", EXPECT_EXIT);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,409 +0,0 @@
|
||||
/*
|
||||
* 989_sepcycle_dup — M3-tail commit-4 negative gates (#46, #31).
|
||||
*
|
||||
* Two error PATHS the commit-4 link-hardening relies on, each asserted to
|
||||
* ACTUALLY FIRE (ken #263: a happy-path-green run does not prove a reject
|
||||
* works — a no-op reject ships green). Every negative leg asserts
|
||||
* (exit != 0) AND (the expected stderr message) AND a non-vacuity flip
|
||||
* (remove the defect → green), so the gate cannot pass for the wrong
|
||||
* reason.
|
||||
*
|
||||
* A. LOUD dep-cycle reject (spec §2, Hare deps.ha:243). A 3-package
|
||||
* import cycle root->A->B->C->A fed to `ww build` must exit
|
||||
* non-zero, print the `dependency cycle: A -> B -> C -> A` chain, and
|
||||
* produce NO output binary. cs==ww (rule 10): the cycle message is
|
||||
* pure driver code, so cstage `ww` and wwstage `ww_ww` emit
|
||||
* BYTE-IDENTICAL stderr — asserted here. Non-vacuity: break the cycle
|
||||
* (C stops importing A) → both stages build green and the program
|
||||
* runs (exit 7).
|
||||
*
|
||||
* B. #31 duplicate-symbol reject (spec §3; w6l ALREADY detects it, this
|
||||
* gate only proves it fires). Two trivial root units each define a
|
||||
* bare `main` (the #31 bare-collision class — #53 path-qualifies
|
||||
* normal exports, so `main` is the cleanest bare clash); both `.o`
|
||||
* fed directly to `w6l` AND `w6l_ww` must exit non-zero with a
|
||||
* `duplicate symbol` message. Non-vacuity: link a single `.o`
|
||||
* (+ libwwrt.a) → clean link, runs.
|
||||
* NOTE on parity: w6l and w6l_ww BOTH reject loud + non-zero and both
|
||||
* name "duplicate symbol", but the EXACT text differs — cstage adds
|
||||
* the path+symbol (`w6l: u2.o: duplicate symbol main`), wwstage prints
|
||||
* the bare `w6l: duplicate symbol`. That is a PRE-EXISTING w6l_ww vs
|
||||
* w6l message divergence on the reject path (byte-id-blind); commit 4
|
||||
* adds NO linker code and must hold w6l_ww byte-identical, so it is
|
||||
* NOT reconciled here. The gate asserts the achievable reject parity:
|
||||
* both exit != 0 AND both stderr contain "duplicate symbol". The
|
||||
* text-divergence is filed (see report) for a sibling linker task.
|
||||
*
|
||||
* Light wwstage-driver test (CLAUDE.md rule 14): all fixtures + scratch
|
||||
* live under /tmp, COLD each run. Models 989_sepbuild_run.c conventions.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const char *
|
||||
absbin(void)
|
||||
{
|
||||
const char *b = getenv("BIN");
|
||||
if (!b) b = "out/bin";
|
||||
if (b[0] == '/') return b;
|
||||
static char buf[2048];
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return NULL;
|
||||
snprintf(buf, sizeof buf, "%s/%s", cwd, b);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int
|
||||
slurp(const char *path, char **outbuf, size_t *outlen)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return -1;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long n = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if (n < 0) { fclose(f); return -1; }
|
||||
char *b = malloc((size_t)n + 1);
|
||||
if (!b) { fclose(f); return -1; }
|
||||
if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; }
|
||||
b[n] = '\0';
|
||||
fclose(f);
|
||||
*outbuf = b;
|
||||
*outlen = (size_t)n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
files_eq(const char *a, const char *b)
|
||||
{
|
||||
char *ba = NULL, *bb = NULL;
|
||||
size_t na = 0, nb = 0;
|
||||
if (slurp(a, &ba, &na) < 0 || slurp(b, &bb, &nb) < 0) {
|
||||
free(ba); free(bb);
|
||||
return -1;
|
||||
}
|
||||
int eq = (na == nb && memcmp(ba, bb, na) == 0);
|
||||
free(ba); free(bb);
|
||||
return eq ? 0 : 1;
|
||||
}
|
||||
|
||||
/* True iff file `path` contains the literal substring `needle`. */
|
||||
static int
|
||||
file_has(const char *path, const char *needle)
|
||||
{
|
||||
char *b = NULL;
|
||||
size_t n = 0;
|
||||
if (slurp(path, &b, &n) < 0) return 0;
|
||||
int found = (strstr(b, needle) != NULL);
|
||||
free(b);
|
||||
return found;
|
||||
}
|
||||
|
||||
static int
|
||||
write_file(const char *path, const char *body)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(body, f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = absbin();
|
||||
if (!bin) return 1;
|
||||
char td[64], cmd[8192];
|
||||
int fail = 0, have_dira = 0, have_dirb = 0, have_dirc = 0;
|
||||
|
||||
snprintf(td, sizeof td, "/tmp/wwcd_%d", getpid());
|
||||
if (mkdir(td, 0755) != 0) {
|
||||
fprintf(stderr, "sepcycle_dup FAIL: cannot acquire %s\n", td);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ---- A. dep-cycle reject ----------------------------------------- */
|
||||
char rootww[1024], dira[1024], dirb[1024], dirc[1024], fa[1100],
|
||||
fb[1100], fc[1100];
|
||||
snprintf(rootww, sizeof rootww, "%s/root.ww", td);
|
||||
snprintf(dira, sizeof dira, "%s/pkga", td);
|
||||
snprintf(dirb, sizeof dirb, "%s/pkgb", td);
|
||||
snprintf(dirc, sizeof dirc, "%s/pkgc", td);
|
||||
if (mkdir(dira, 0755) != 0) {
|
||||
fprintf(stderr, "sepcycle_dup FAIL: cannot create %s\n", dira);
|
||||
fail++; goto out;
|
||||
}
|
||||
have_dira = 1;
|
||||
if (mkdir(dirb, 0755) != 0) {
|
||||
fprintf(stderr, "sepcycle_dup FAIL: cannot create %s\n", dirb);
|
||||
fail++; goto out;
|
||||
}
|
||||
have_dirb = 1;
|
||||
if (mkdir(dirc, 0755) != 0) {
|
||||
fprintf(stderr, "sepcycle_dup FAIL: cannot create %s\n", dirc);
|
||||
fail++; goto out;
|
||||
}
|
||||
have_dirc = 1;
|
||||
snprintf(fa, sizeof fa, "%s/a.ww", dira);
|
||||
snprintf(fb, sizeof fb, "%s/b.ww", dirb);
|
||||
snprintf(fc, sizeof fc, "%s/c.ww", dirc);
|
||||
if (write_file(rootww,
|
||||
"package main;\n"
|
||||
"import pkga;\n"
|
||||
"fn main() i32 = { return pkga.av(); };\n") ||
|
||||
write_file(fa,
|
||||
"package pkga;\n"
|
||||
"import pkgb;\n"
|
||||
"export fn av() i32 = { return pkgb.bv(); };\n") ||
|
||||
write_file(fb,
|
||||
"package pkgb;\n"
|
||||
"import pkgc;\n"
|
||||
"export fn bv() i32 = { return pkgc.cv(); };\n") ||
|
||||
write_file(fc,
|
||||
"package pkgc;\n"
|
||||
"import pkga;\n"
|
||||
"export fn cv() i32 = { return pkga.av(); };\n")) {
|
||||
fail++; goto out;
|
||||
}
|
||||
|
||||
struct { const char *drv, *tag; } stg[] = {
|
||||
{ "ww", "cs" }, { "ww_ww", "ww" }
|
||||
};
|
||||
char cerr_path[2][1100];
|
||||
for (int s = 0; s < 2; s++) {
|
||||
char prog[1100], errf[1100];
|
||||
snprintf(prog, sizeof prog, "%s/prog.%s", td, stg[s].tag);
|
||||
snprintf(errf, sizeof errf, "%s/cyc.%s.err", td, stg[s].tag);
|
||||
snprintf(cerr_path[s], sizeof cerr_path[s], "%s", errf);
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"%s/%s build -o %s %s >/dev/null 2>%s",
|
||||
bin, stg[s].drv, prog, rootww, errf);
|
||||
int rc = runwait(cmd);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "cycle FAIL: %s build returned 0 "
|
||||
"(no-op reject)\n", stg[s].drv);
|
||||
fail++;
|
||||
}
|
||||
if (!file_has(errf, "dependency cycle: pkga -> pkgb -> pkgc -> pkga")) {
|
||||
fprintf(stderr, "cycle FAIL: %s missing cycle-chain "
|
||||
"message\n", stg[s].drv);
|
||||
fail++;
|
||||
}
|
||||
if (access(prog, 0) == 0) {
|
||||
fprintf(stderr, "cycle FAIL: %s produced a partial "
|
||||
"binary %s\n", stg[s].drv, prog);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
/* cs==ww (rule 10): the cycle message is pure driver code → stderr
|
||||
* byte-identical between the two stages. */
|
||||
if (files_eq(cerr_path[0], cerr_path[1]) != 0) {
|
||||
fprintf(stderr, "cycle FAIL: cs stderr != ww stderr (rule 10)\n");
|
||||
fail++;
|
||||
}
|
||||
|
||||
/* Non-vacuity: break the cycle → both stages build + run (exit 7). */
|
||||
if (write_file(fc,
|
||||
"package pkgc;\n"
|
||||
"export fn cv() i32 = { return 7; };\n")) {
|
||||
fail++; goto out;
|
||||
}
|
||||
for (int s = 0; s < 2; s++) {
|
||||
char prog[1100];
|
||||
snprintf(prog, sizeof prog, "%s/ok.%s", td, stg[s].tag);
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"%s/%s build -o %s %s >/dev/null 2>&1",
|
||||
bin, stg[s].drv, prog, rootww);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "cycle FAIL(non-vacuity): %s could not "
|
||||
"build the acyclic graph\n", stg[s].drv);
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
int rc = runwait(prog);
|
||||
if (rc != 7) {
|
||||
fprintf(stderr, "cycle FAIL(non-vacuity): %s prog exit=%d "
|
||||
"expected 7\n", stg[s].drv, rc);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- B. #31 duplicate-symbol reject ------------------------------ */
|
||||
char u1[1100], u2[1100], s1[1100], s2[1100], o1[1100], o2[1100];
|
||||
snprintf(u1, sizeof u1, "%s/u1.ww", td);
|
||||
snprintf(u2, sizeof u2, "%s/u2.ww", td);
|
||||
snprintf(s1, sizeof s1, "%s/u1.s", td);
|
||||
snprintf(s2, sizeof s2, "%s/u2.s", td);
|
||||
snprintf(o1, sizeof o1, "%s/u1.o", td);
|
||||
snprintf(o2, sizeof o2, "%s/u2.o", td);
|
||||
if (write_file(u1, "package main;\nfn main() i32 = { return 0; };\n") ||
|
||||
write_file(u2, "package main;\nfn main() i32 = { return 1; };\n")) {
|
||||
fail++; goto out;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -c -o %s %s && %s/w6a -o %s %s",
|
||||
bin, s1, u1, bin, o1, s1);
|
||||
if (runwait(cmd) != 0) { fprintf(stderr, "dup FAIL: build u1.o\n"); fail++; goto out; }
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -c -o %s %s && %s/w6a -o %s %s",
|
||||
bin, s2, u2, bin, o2, s2);
|
||||
if (runwait(cmd) != 0) { fprintf(stderr, "dup FAIL: build u2.o\n"); fail++; goto out; }
|
||||
|
||||
/* Both linkers must reject the dup loud + non-zero. */
|
||||
const char *lnk[] = { "w6l", "w6l_ww" };
|
||||
for (int i = 0; i < 2; i++) {
|
||||
char errf[1100], prog[1100];
|
||||
snprintf(errf, sizeof errf, "%s/dup.%s.err", td, lnk[i]);
|
||||
snprintf(prog, sizeof prog, "%s/dupprog.%s", td, lnk[i]);
|
||||
snprintf(cmd, sizeof cmd, "%s/%s -o %s %s %s >/dev/null 2>%s",
|
||||
bin, lnk[i], prog, o1, o2, errf);
|
||||
int rc = runwait(cmd);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "dup FAIL: %s accepted a duplicate "
|
||||
"symbol (exit 0)\n", lnk[i]);
|
||||
fail++;
|
||||
}
|
||||
if (!file_has(errf, "duplicate symbol")) {
|
||||
fprintf(stderr, "dup FAIL: %s missing 'duplicate symbol' "
|
||||
"message\n", lnk[i]);
|
||||
fail++;
|
||||
}
|
||||
if (access(prog, 0) == 0) {
|
||||
fprintf(stderr, "dup FAIL: %s produced a partial binary "
|
||||
"%s on the reject path\n", lnk[i], prog);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Non-vacuity: a single .o + libwwrt.a links clean and runs. */
|
||||
{
|
||||
char rt[1100], prog[1100];
|
||||
snprintf(rt, sizeof rt, "%s/../lib/libwwrt.a", bin);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
snprintf(prog, sizeof prog, "%s/single.%s", td, lnk[i]);
|
||||
snprintf(cmd, sizeof cmd, "%s/%s -o %s %s %s >/dev/null 2>&1",
|
||||
bin, lnk[i], prog, o1, rt);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "dup FAIL(non-vacuity): %s could "
|
||||
"not link a single .o\n", lnk[i]);
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
if (runwait(prog) != 0) {
|
||||
fprintf(stderr, "dup FAIL(non-vacuity): %s single "
|
||||
"prog did not run clean\n", lnk[i]);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"rm -rf -- '%s/prog.cs.sepwork' '%s/prog.ww.sepwork' "
|
||||
"'%s/ok.cs.sepwork' '%s/ok.ww.sepwork'", td, td, td, td);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "sepcycle_dup FAIL: cannot clean .sepwork trees\n");
|
||||
fail++;
|
||||
}
|
||||
{
|
||||
char path[1200];
|
||||
snprintf(path, sizeof path, "%s/root.ww", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/prog.cs", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/prog.ww", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/cyc.cs.err", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/cyc.ww.err", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/ok.cs", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/ok.ww", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/u1.ww", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/u2.ww", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/u1.s", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/u2.s", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/u1.o", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/u2.o", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/dup.w6l.err", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/dup.w6l_ww.err", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/dupprog.w6l", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/dupprog.w6l_ww", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/single.w6l", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/single.w6l_ww", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path); fail++; }
|
||||
}
|
||||
if (have_dira) {
|
||||
char path[1200];
|
||||
snprintf(path, sizeof path, "%s/a.ww", dira);
|
||||
if (unlink(path) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (have_dirb) {
|
||||
char path[1200];
|
||||
snprintf(path, sizeof path, "%s/b.ww", dirb);
|
||||
if (unlink(path) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (have_dirc) {
|
||||
char path[1200];
|
||||
snprintf(path, sizeof path, "%s/c.ww", dirc);
|
||||
if (unlink(path) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", path);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (have_dira && rmdir(dira) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", dira);
|
||||
fail++;
|
||||
}
|
||||
if (have_dirb && rmdir(dirb) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", dirb);
|
||||
fail++;
|
||||
}
|
||||
if (have_dirc && rmdir(dirc) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", dirc);
|
||||
fail++;
|
||||
}
|
||||
if (rmdir(td) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "sepcycle_dup FAIL: cannot remove %s\n", td);
|
||||
fail++;
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr, "sepcycle_dup: %d check(s) failed\n", fail);
|
||||
return 1;
|
||||
}
|
||||
printf("sepcycle_dup: dep-cycle reject (chain named, cs==ww stderr, no "
|
||||
"partial binary, non-vacuity flip) + #31 dup-symbol reject (w6l & "
|
||||
"w6l_ww loud+non-zero, non-vacuity single-.o link)\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user