From cd49966539487b9270fc12680e55db32a2dd89c8 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 14:07:19 +0900 Subject: [PATCH] test: port the sep-layout observers to ww; retire 989_{sepbuild,seproot_export,sepstructdef,wwispread} Birth test/sep/ (single-file ww observers on test/testenv, run by the new SEP_WW_TARGETS pattern rule under test-compiler) with sepbuild_test.ww: the build_one_sep real-chain gate incl. the bodies==.wwi keystone recompile, the #69 root-without--I contract, the #70 aggregate-def .wwi prototypes, and the #95 spread-marker round-trip. Every carrier assertion has a successor row; the C cleanup-accounting ledgers collapse into testenv.clean. --- Makefile | 28 +- test/sep/sepbuild_test.ww | 464 ++++++++++++++++++++++++++++++ test/wcc/989_sepbuild_run.c | 400 -------------------------- test/wcc/989_seproot_export_run.c | 323 --------------------- test/wcc/989_sepstructdef_run.c | 275 ------------------ test/wcc/989_wwispread_sep.c | 196 ------------- 6 files changed, 489 insertions(+), 1197 deletions(-) create mode 100644 test/sep/sepbuild_test.ww delete mode 100644 test/wcc/989_sepbuild_run.c delete mode 100644 test/wcc/989_seproot_export_run.c delete mode 100644 test/wcc/989_sepstructdef_run.c delete mode 100644 test/wcc/989_wwispread_sep.c diff --git a/Makefile b/Makefile index 38d8d9ed..c5c8fc21 100644 --- a/Makefile +++ b/Makefile @@ -356,6 +356,14 @@ BYTEID_WW_TESTS = test/byteid/libbyteid_test.ww test/byteid/wwi_test.ww \ test/byteid/asmwindow_test.ww test/byteid/freenoop_test.ww \ test/byteid/structabi_test.ww test/byteid/mangle_test.ww BYTEID_WW_TARGETS = $(BYTEID_WW_TESTS:%=wwtest/%) +# Ww-native sep-driver/tool observers: single-file ww tests under +# test/sep/ on the test/testenv helper package, porting the residual +# 989_sep* driver-observer C carriers. These are compiler/driver +# gates (module trees, sepwork artifact layout, composed //ww:module +# units, link rejects), NOT byteid suites: they run under +# test-compiler beside the surviving residual carriers. +SEP_WW_TESTS = test/sep/sepbuild_test.ww +SEP_WW_TARGETS = $(SEP_WW_TESTS:%=wwtest/%) BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \ test/wcc/991_w6a_ww.c \ test/wcc/992_w6l_ww.c test/wcc/993_ww_ww.c \ @@ -438,8 +446,10 @@ test-compiler-smoke: $(WWFIXTURE_BIN) $(BIN)/w6c_ww $(BIN)/w6a_ww \ -run compiler.r78_strict_package_main_accept # Complete declarative corpus plus the residual native artifact/integration -# gates that cannot be represented by a single compiler fixture. -test-compiler: $(WWFIXTURE_BIN) $(WRAPPER_TOOLS) $(COMPILER_WRAPPER_BINS) +# gates that cannot be represented by a single compiler fixture, plus the +# ww-native sep observers that replaced the 989_sep* carriers. +test-compiler: $(WWFIXTURE_BIN) $(WRAPPER_TOOLS) $(COMPILER_WRAPPER_BINS) \ + $(SEP_WW_TARGETS) @$(CURDIR)/$(WWFIXTURE_BIN) -j $(JOBS) @set -e; for t in $(COMPILER_WRAPPER_BINS); do \ echo "compiler artifact $$t"; BIN=$(CURDIR)/$(BIN) $(CURDIR)/$$t; \ @@ -475,6 +485,18 @@ $(BYTEID_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a \ -w $(WWBUILD)/wwtest/$(subst /,_,$*) \ -I $(CURDIR)/test $* +# The sep observers drive both driver stages end-to-end (compile, +# assemble, link, run), so both full per-stage toolchains are +# prerequisites. +$(SEP_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(LIB)/libwwrt.a $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww \ + $(BIN)/w6l_ww + @echo "ww test $*" + @mkdir -p $(WWBUILD)/wwtest/$(subst /,_,$*) + @WW_TEST_REPO=$(CURDIR) $(BIN)/ww test \ + -w $(WWBUILD)/wwtest/$(subst /,_,$*) \ + -I $(CURDIR)/test $* + test-lang: $(LANG_TEST_TARGETS) test-library: $(LIBRARY_TEST_TARGETS) $(BIN)/w6c $(BIN)/w6c_ww \ @@ -842,6 +864,6 @@ nocc: test test-unit test-compiler-smoke test-compiler test-package \ test-lang test-library test-commit test-lang-byteid \ $(LANG_TEST_TARGETS) $(LIBRARY_TEST_TARGETS) $(BYTEID_WW_TARGETS) \ - test-data-byteid test-byteid \ + $(SEP_WW_TARGETS) test-data-byteid test-byteid \ test-bootstrap-native test-bootstrap test-incremental \ test-platform test-all install clean bootstrap nocc bootstrap-snapshot diff --git a/test/sep/sepbuild_test.ww b/test/sep/sepbuild_test.ww new file mode 100644 index 00000000..411c3741 --- /dev/null +++ b/test/sep/sepbuild_test.ww @@ -0,0 +1,464 @@ +package sepbuild_test; + +// `ww build` sep-driver layout observers, both stages. Ports of the +// retired native carriers test/wcc/989_sepbuild_run.c, +// 989_seproot_export_run.c, 989_sepstructdef_run.c and +// 989_wwispread_sep.c; every assertion preserved. +// +// sepbuild (#46 commit-3) — build_one_sep END-TO-END on the real lib +// chain root -> os -> {rt,time} (transitive-closure discovery + topo): +// build+run exit 7 both stages; {time,rt,os}.wwi + __root.s +// materialize (#69: the root is compiled without -I, so no +// __root.wwi); per-package .s/.wwi/.unit.ww and the final binary +// byte-id cs vs ww; the KEYSTONE (spec 3.1) recompiles the driver's +// own non-leaf .unit.ww with each dep's .wwi section replaced by that +// dep's full directory bodies and requires byte-identical .s — the +// proof the .wwi conveys exactly the dep facts P's codegen needs; +// `ww run` routes through the same sole sep path (exit 7 both +// stages). +// +// seproot (#69 BUG-1) — a ROOT whose `export fn use(a: *t)` names an +// unexported local `type t` builds (exit 37 both stages) because the +// root is compiled WITHOUT the -I .wwi-producer flag: __root.wwi must +// NOT exist; replaying the driver's own __root.unit.ww through +// w6c/w6c_ww with -I must REJECT (the isolated bug) while -c -o alone +// must accept; __root.s carries the exported fn. +// +// sepstructdef (#70 BUG-2) — a dep exporting aggregate-init defs +// (struct-lit + array-lit) sep-builds and links (exit 20 both +// stages); d.wwi carries the value-LESS prototypes `export def info: +// s;` + `export def arr: [3]i32;` and NOT the value-serialized +// `export def info: s = ` form (the producer took the aggregate arm). +// +// wwispread (#95) — the `...` union-spread marker survives N_TTAGGED +// .wwi serialization: consumer build+run exit 0 over the checked-in +// 2-arm and 3-arm trees, the produced .wwi contains the literal +// marker, and cs.wwi == ww.wwi. +// +// 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("sepbuild 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; +}; + +// -1 encodes an abnormal (non-EXIT) termination, never a valid code. +fn runcode(dir: str, name: str, argv: []str) i32 = { + let co: testenv.commandout; + testenv.runcommand(dir, dir, name, argv, tmo(), &co); + if (co.termination != exec.termination.EXIT) { return -1; }; + return co.code; +}; + +fn samefile(label: str, what: str, a: str, b: str) void = { + if (!testenv.same(testenv.readfile(a), testenv.readfile(b))) { + fail(label, strings.concat("cs!=ww for ", what, " (rule 10)")); + }; +}; + +// ---- sepbuild (#46): real chain root->os->{rt,time} -------------------- + +// A package directory's *.ww bodies (less *test.ww), byte-sorted, each +// newline-terminated — mirrors the driver's body enumeration so the +// substituted bodies-unit matches the sep-unit structurally. +fn dirbodies(dir: str) str = { + let out: str = ""; + let names: []str = testenv.listdir(dir); + let i: i32 = 0; + for (i < names.len) { + if (strings.hassuffix(names[i], ".ww") + && !strings.hassuffix(names[i], "test.ww")) { + out = strings.concat(out, + testenv.readfile(strings.concat(dir, "/", names[i])), + "\n"); + }; + i += 1; + }; + return out; +}; + +// Byte-offset borrow: strings.sub is rune-indexed, and the driver +// units embed lib comment bytes outside ASCII, so rune indices would +// mis-address the byte cursor below (readfile's borrow idiom). +fn bslice(s: str, start: i32, end: i32) str = { + assert(start <= end && end <= s.len); + let r: str; + r.ptr = s.ptr + (start: u64); + r.len = end - start; + return r; +}; + +// Transform a driver sep-unit into a bodies-unit: every `//ww:module +// ` dep section (its .wwi content) is replaced by 's full +// directory bodies (dots -> slashes under libdir); the trailing +// `//ww:module-reset` primary body is copied verbatim. The keystone's +// other arm. +fn composebodies(unit: str, libdir: str) str = { + let out: str = ""; + let n: i32 = unit.len; + let i: i32 = 0; + for (i < n) { + let j: i32 = i; + for (j < n && unit[j] != '\n') { j += 1; }; + let line: str = bslice(unit, i, j); + if (strings.hasprefix(line, "//ww:module-reset")) { + out = strings.concat(out, bslice(unit, i, n)); + return out; + }; + if (strings.hasprefix(line, "//ww:module ")) { + out = strings.concat(out, line, "\n"); + let path: str = bslice(line, 12, line.len); + let d: []u8 = alloc([], (path.len + 1): u64)!; + let k: i32 = 0; + for (k < path.len) { + if (path[k] == '.') { append(d, '/'); } + else { append(d, path[k]); }; + k += 1; + }; + out = strings.concat(out, dirbodies(strings.concat(libdir, + "/", strings.frombytes(d)))); + i = j + 1; + for (i < n) { + let e: i32 = i; + for (e < n && unit[e] != '\n') { e += 1; }; + if (strings.hasprefix(bslice(unit, i, e), + "//ww:module")) { break; }; + i = e + 1; + }; + continue; + }; + i = j + 1; + }; + return out; +}; + +@test fn sepbuild() void = { + let td: str = testenv.fresh(); + let rootww: str = strings.concat(td, "/root.ww"); + testenv.writefile(rootww, strings.concat( + "package main;\n", + "import os;\n", + "fn main() i32 = { return os.getpid() - os.getpid() + 7; };\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", "-o", prog, + rootww]; + if (runcode(td, strings.concat("build_", tags[s]), av) != 0) { + fail("sepbuild", strings.concat(drvs[s], " build failed")); + }; + let rav: []str = [prog]; + if (runcode(td, strings.concat("runbin_", tags[s]), rav) != 7) { + fail("sepbuild", strings.concat(drvs[s], " prog exit != 7")); + }; + s += 1; + }; + + // discovery/topo: the transitive package set materialized. #69: the + // root emits no .wwi (compiled without -I); its .s stands in. + let pkgs: []str = ["time", "rt", "os", "__root"]; + let i: i32 = 0; + for (i < pkgs.len) { + let suffix: str = ".wwi"; + if (testenv.same(pkgs[i], "__root")) { suffix = ".s"; }; + if (!testenv.exists(strings.concat(td, "/prog.cs.sepwork/", + pkgs[i], suffix))) { + fail("sepbuild", strings.concat(pkgs[i], suffix, + " missing (discovery/topo)")); + }; + i += 1; + }; + + // cs==ww (rule 10): per-package artifacts + the final binary + let sufs: []str = [".s", ".wwi", ".unit.ww"]; + let p: i32 = 0; + for (p < pkgs.len) { + let k: i32 = 0; + for (k < sufs.len) { + if (testenv.same(pkgs[p], "__root") + && testenv.same(sufs[k], ".wwi")) { k += 1; continue; }; + samefile("sepbuild", strings.concat(pkgs[p], sufs[k]), + strings.concat(td, "/prog.cs.sepwork/", pkgs[p], sufs[k]), + strings.concat(td, "/prog.ww.sepwork/", pkgs[p], sufs[k])); + k += 1; + }; + p += 1; + }; + samefile("sepbuild", "the final binary", + strings.concat(td, "/prog.cs"), strings.concat(td, "/prog.ww")); + + // KEYSTONE through the driver, non-leaf packages only (leaves have + // no dep sections, so their keystone is vacuous) + let libdir: str = strings.concat(testenv.repo(), "/lib"); + let keypkgs: []str = ["os", "__root"]; + let kp: i32 = 0; + for (kp < keypkgs.len) { + let unit: str = testenv.readfile(strings.concat(td, + "/prog.cs.sepwork/", keypkgs[kp], ".unit.ww")); + let bodiesf: str = strings.concat(td, "/", keypkgs[kp], + ".bodies.ww"); + testenv.writefile(bodiesf, composebodies(unit, libdir)); + let bodiess: str = strings.concat(td, "/", keypkgs[kp], + ".bodies.s"); + let av: []str = [testenv.driver("w6c"), "-c", "-o", bodiess, + bodiesf]; + if (runcode(td, strings.concat("key_", keypkgs[kp]), av) != 0) { + fail("sepbuild", strings.concat(keypkgs[kp], " bodies -c failed")); + }; + if (!testenv.same(testenv.readfile(bodiess), + testenv.readfile(strings.concat(td, "/prog.cs.sepwork/", + keypkgs[kp], ".s")))) { + fail("sepbuild", strings.concat(keypkgs[kp], + " bodies.s != driver sep.s (the .wwi does not convey ", + "the dep facts P needs)")); + }; + kp += 1; + }; + + // `ww run` routes through the sole sep path on both stages + let cav: []str = [testenv.driver("ww"), "run", rootww]; + let rccs: i32 = runcode(td, "run_cs", cav); + let wav: []str = [testenv.driver("ww_ww"), "run", rootww]; + let rcww: i32 = runcode(td, "run_ww", wav); + if (rccs != rcww || rccs != 7) { + fail("sepbuild", "`ww run` exits differ or != 7 across stages"); + }; + testenv.clean(td); +}; + +// ---- seproot (#69 BUG-1): export-over-unexported-type root ------------- + +@test fn seproot() void = { + let td: str = testenv.fresh(); + assert(os.mkdir(strings.concat(td, "/lib"), 493) == 0); + assert(os.mkdir(strings.concat(td, "/lib/c"), 493) == 0); + testenv.writefile(strings.concat(td, "/lib/c/mod.ww"), strings.concat( + "package c;\n", + "export fn cval() i32 = { return 5; };\n")); + let rootww: str = strings.concat(td, "/root.ww"); + testenv.writefile(rootww, strings.concat( + "package main;\n", + "import c;\n", + "type t = struct { v: i32 };\n", + "export fn use(a: *t) i32 = { return a.v; };\n", + "fn main() i32 = {\n", + "\tlet x: t = t { v = 42 };\n", + "\treturn use(&x) - c.cval();\n", + "};\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", strings.concat(td, "/lib"), "-o", prog, rootww]; + if (runcode(td, strings.concat("build_", tags[s]), av) != 0) { + fail("seproot", strings.concat(drvs[s], " build failed ", + "(pre-fix: -I on the root rejects ", + "export-fn-over-unexported-type)")); + }; + let rav: []str = [prog]; + if (runcode(td, strings.concat("runbin_", tags[s]), rav) != 37) { + fail("seproot", strings.concat(drvs[s], " prog exit != 37")); + }; + s += 1; + }; + + // cs==ww; the root's .wwi is intentionally absent (asserted below) + let pkgs: []str = ["c", "__root"]; + let sufs: []str = [".s", ".wwi", ".unit.ww"]; + let p: i32 = 0; + for (p < pkgs.len) { + let k: i32 = 0; + for (k < sufs.len) { + if (testenv.same(pkgs[p], "__root") + && testenv.same(sufs[k], ".wwi")) { k += 1; continue; }; + samefile("seproot", strings.concat(pkgs[p], sufs[k]), + strings.concat(td, "/prog.cs.sepwork/", pkgs[p], sufs[k]), + strings.concat(td, "/prog.ww.sepwork/", pkgs[p], sufs[k])); + k += 1; + }; + p += 1; + }; + samefile("seproot", "the final binary", + strings.concat(td, "/prog.cs"), strings.concat(td, "/prog.ww")); + + // the fix: the root is compiled WITHOUT -I, so no __root.wwi + let s2: i32 = 0; + for (s2 < 2) { + if (testenv.exists(strings.concat(td, "/prog.", tags[s2], + ".sepwork/__root.wwi"))) { + fail("seproot", strings.concat(drvs[s2], + " produced __root.wwi (-I still passed for the root)")); + }; + s2 += 1; + }; + + // replay the pre-fix invocation on the driver's own __root.unit.ww: + // with -I the export-check FIRES (the isolated bug); without it the + // post-fix invocation accepts. Both compilers. + let unit: str = strings.concat(td, "/prog.cs.sepwork/__root.unit.ww"); + let comps: []str = ["w6c", "w6c_ww"]; + let c: i32 = 0; + for (c < 2) { + let wwi: str = strings.concat(td, "/nv.", comps[c], ".wwi"); + let asmf: str = strings.concat(td, "/nv.", comps[c], ".s"); + let rav: []str = [testenv.driver(comps[c]), "-c", "-I", wwi, + "-o", asmf, unit]; + if (runcode(td, strings.concat("nvI_", comps[c]), rav) == 0) { + fail("seproot", strings.concat(comps[c], " -c -I accepted the ", + "root export-over-unexported-type (vacuous gate)")); + }; + let aav: []str = [testenv.driver(comps[c]), "-c", "-o", asmf, + unit]; + if (runcode(td, strings.concat("nvO_", comps[c]), aav) != 0) { + fail("seproot", strings.concat(comps[c], " -c -o (no -I) ", + "rejected the root unit (post-fix invocation)")); + }; + c += 1; + }; + + if (!testenv.has(testenv.readfile(strings.concat(td, + "/prog.cs.sepwork/__root.s")), "use")) { + fail("seproot", "__root.s lacks the exported `use` symbol"); + }; + testenv.clean(td); +}; + +// ---- sepstructdef (#70 BUG-2): aggregate-init def prototypes ----------- + +@test fn sepstructdef() void = { + let td: str = testenv.fresh(); + assert(os.mkdir(strings.concat(td, "/lib"), 493) == 0); + assert(os.mkdir(strings.concat(td, "/lib/d"), 493) == 0); + // the struct type must be EXPORTED: an exported def over an + // unexported type is a separate loud reject (check_exported_type) + testenv.writefile(strings.concat(td, "/lib/d/mod.ww"), strings.concat( + "package d;\n", + "export type s = struct { a: i32, b: i32 };\n", + "export def info: s = s { a = 7, b = 11 };\n", + "export def arr: [3]i32 = [1, 2, 3];\n", + "export fn geta() i32 = { return info.a; };\n", + "export fn getb() i32 = { return info.b; };\n", + "export fn getelem(i: i32) i32 = { return arr[i]; };\n")); + let rootww: str = strings.concat(td, "/root.ww"); + testenv.writefile(rootww, strings.concat( + "package main;\n", + "import d;\n", + "fn main() i32 = {\n", + "\treturn d.geta() + d.getelem(1) + d.getb();\n", + "};\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", strings.concat(td, "/lib"), "-o", prog, rootww]; + if (runcode(td, strings.concat("build_", tags[s]), av) != 0) { + fail("sepstructdef", strings.concat(drvs[s], " build failed ", + "(pre-fix: the aggregate-init def aborts the producer ", + "with \"node kind 15\")")); + }; + let rav: []str = [prog]; + if (runcode(td, strings.concat("runbin_", tags[s]), rav) != 20) { + fail("sepstructdef", strings.concat(drvs[s], " prog exit != 20 ", + "(external aggregate-DATA ref did not resolve)")); + }; + s += 1; + }; + + let pkgs: []str = ["d", "__root"]; + let sufs: []str = [".s", ".wwi", ".unit.ww"]; + let p: i32 = 0; + for (p < pkgs.len) { + let k: i32 = 0; + for (k < sufs.len) { + if (testenv.same(pkgs[p], "__root") + && testenv.same(sufs[k], ".wwi")) { k += 1; continue; }; + samefile("sepstructdef", strings.concat(pkgs[p], sufs[k]), + strings.concat(td, "/prog.cs.sepwork/", pkgs[p], sufs[k]), + strings.concat(td, "/prog.ww.sepwork/", pkgs[p], sufs[k])); + k += 1; + }; + p += 1; + }; + samefile("sepstructdef", "the final binary", + strings.concat(td, "/prog.cs"), strings.concat(td, "/prog.ww")); + + // value-LESS prototypes present, value-serialized form absent (the + // producer took the new aggregate arm, not the scalar arm) + let wwi: str = testenv.readfile(strings.concat(td, + "/prog.cs.sepwork/d.wwi")); + if (!testenv.has(wwi, "export def info: s;\n")) { + fail("sepstructdef", + "d.wwi lacks value-less `export def info: s;`"); + }; + if (!testenv.has(wwi, "export def arr: [3]i32;\n")) { + fail("sepstructdef", + "d.wwi lacks value-less `export def arr: [3]i32;`"); + }; + if (testenv.has(wwi, "export def info: s = ")) { + fail("sepstructdef", + "d.wwi serialized the aggregate value (vacuous gate)"); + }; + testenv.clean(td); +}; + +// ---- wwispread (#95): `...` spread marker round-trip ------------------- + +fn spreadshape(name: str, entry: str, wwi: str, marker: str) void = { + let td: str = testenv.fresh(); + let src: str = strings.concat(testenv.repo(), "/", entry); + let drvs: []str = ["ww", "ww_ww"]; + let tags: []str = ["cs", "ww"]; + let s: i32 = 0; + for (s < 2) { + let prog: str = strings.concat(td, "/", name, ".", tags[s]); + let av: []str = [testenv.driver(drvs[s]), "build", "-o", prog, + src]; + if (runcode(td, strings.concat("build_", tags[s]), av) != 0) { + fail(name, strings.concat(drvs[s], " build failed (spread ", + "member not assignable -> marker dropped?)")); + }; + let rav: []str = [prog]; + if (runcode(td, strings.concat("runbin_", tags[s]), rav) != 0) { + fail(name, strings.concat(drvs[s], " run exit != 0")); + }; + if (!testenv.has(testenv.readfile(strings.concat(prog, + ".sepwork/", wwi)), marker)) { + fail(name, strings.concat(wwi, " missing `", marker, + "` (spread marker dropped in the serializer)")); + }; + s += 1; + }; + samefile(name, wwi, + strings.concat(td, "/", name, ".cs.sepwork/", wwi), + strings.concat(td, "/", name, ".ww.sepwork/", wwi)); + testenv.clean(td); +}; + +@test fn wwispread_twoarm() void = { + spreadshape("twoarm", "test/wcc/data/wwispread/main.ww", + "liba.wwi", "...inner"); +}; + +@test fn wwispread_threearm() void = { + spreadshape("threearm", "test/wcc/data/wwispread3/main.ww", + "libb.wwi", "...three"); +}; diff --git a/test/wcc/989_sepbuild_run.c b/test/wcc/989_sepbuild_run.c deleted file mode 100644 index d61cc53e..00000000 --- a/test/wcc/989_sepbuild_run.c +++ /dev/null @@ -1,400 +0,0 @@ -/* - * 989_sepbuild_run — M3-tail commit-3 `ww build` driver gate (#46). - * - * Certifies build_one_sep (cmd/ww/main.c + selfhost/cmd/ww/main.ww twin) - * END-TO-END through the REAL `ww` / `ww_ww` drivers on a REAL lib chain - * (NOT the 989_m3sep synth fixture, which exercises the M3-CORE codegen - * via a hand harness). The driver adds ONLY orchestration around that - * proven core: discover_deps, reverse-topo, the transitive `w6c -c -I` - * producer loop, and `w6l` over the root `.o` plus reverse-topo dependency - * `.a` archives. - * - * Chosen chain (smallest real multi-package graph with a genuine - * multi-level dep edge): root → os → { rt, time }. rt and time are - * true leaves; os imports both AND its public interface exposes a - * transitive type (os returns/takes time.instant in sibling protos), so - * the root's sep-unit needs the TRANSITIVE `.wwi` closure for name - * resolution — direct-deps-only does not type-check (the lead-ratified - * superseding of rob-c3-spec §1.3). The graph thus exercises: - * - discovery of the transitive package set, - * - reverse-topo dep-first ordering (time/rt before os before root), - * - the transitive-closure `.wwi` prepend tagged by dotted path, - * - one-pass `w6c -c -I` (consumer + producer) per package, - * - `w6l` of the root `.o`, dependency `.a` set, and libwwrt.a. - * - * Asserts (fresh output stems under an invocation-owned root; retained - * `.sepwork` is inspected, then removed by final carrier cleanup): - * 1. Build + run, BOTH stages → exit EXPECT_EXIT (cross-boundary - * os.getpid resolves across the root `.o` and dependency `.a` boundary; - * the program runs). - * 2. cs==ww (rule 10): per-package `.s`/`.wwi`/`.unit.ww` AND the final - * binary are byte-identical between `ww` and `ww_ww`. - * 3. KEYSTONE (load-bearing, §3.1) THROUGH the driver: for each non-leaf - * package P, transform the driver's own `

.unit.ww` by substituting - * each dep's `.wwi` section with that dep's full directory BODIES, - * `w6c -c` it, and cmp vs the driver's `

.s`. Byte-identical proves - * the `.wwi` conveys exactly the dep facts P's codegen needs. (Leaves - * rt/time have no deps → keystone is vacuous; checked on os + root.) - * - * All intermediates are `-o`-redirected to /tmp for isolation. Models - * 989_m3sep_run.c conventions; 989 prefix per the m3sep/m2wwi precedent. - */ -#include -#include -#include -#include -#include -#include -#include -#include - -#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 -write_file(const char *path, const char *body) -{ - FILE *f = fopen(path, "wb"); - if (!f) return -1; - fputs(body, f); - fclose(f); - return 0; -} - -static int -strs_cmp(const void *a, const void *b) -{ - return strcmp(*(const char *const *)a, *(const char *const *)b); -} - -/* Concatenate a package directory's *.ww bodies (less *test.ww, - * byte-sorted) into `out`. Mirrors the driver's body enumeration so - * the substituted bodies-unit matches the sep-unit structurally. */ -static int -append_dir_bodies(FILE *out, const char *dir) -{ - DIR *d = opendir(dir); - if (!d) return -1; - char *names[256]; - int n = 0; - struct dirent *ent; - while ((ent = readdir(d)) != NULL && n < 256) { - const char *nm = ent->d_name; - size_t nl = strlen(nm); - if (nl <= 3 || strcmp(nm + nl - 3, ".ww") != 0) continue; - if (nl >= 7 && strcmp(nm + nl - 7, "test.ww") == 0) continue; - names[n++] = strdup(nm); - } - closedir(d); - if (n > 1) qsort(names, (size_t)n, sizeof names[0], strs_cmp); - int rc = 0; - for (int i = 0; i < n; i++) { - char fp[1024]; - snprintf(fp, sizeof fp, "%s/%s", dir, names[i]); - char *b = NULL; - size_t bn = 0; - if (slurp(fp, &b, &bn) == 0) { - fwrite(b, 1, bn, out); - fputc('\n', out); - free(b); - } else rc = -1; - free(names[i]); - } - return rc; -} - -/* Transform the driver's sep-unit at `unitf` into a bodies-unit at - * `bodiesf`: every `//ww:module ` dep section (its `.wwi` content) - * is replaced by 's full directory bodies; the trailing - * `//ww:module-reset` primary body is copied verbatim. The result is the - * SAME unit P would compile with deps-as-bodies — the keystone's other - * arm. roots the dotted-path → dir mapping (dots → slashes). */ -static int -compose_bodies(const char *unitf, const char *libdir, const char *bodiesf) -{ - char *buf = NULL; - size_t n = 0; - if (slurp(unitf, &buf, &n) < 0) return -1; - FILE *out = fopen(bodiesf, "wb"); - if (!out) { free(buf); return -1; } - int rc = 0; - size_t i = 0; - while (i < n) { - size_t j = i; - while (j < n && buf[j] != '\n') j++; - size_t linelen = j - i; - if (strncmp(buf + i, "//ww:module-reset", 17) == 0) { - /* primary body: copy this line + everything after. */ - fwrite(buf + i, 1, n - i, out); - break; - } - if (strncmp(buf + i, "//ww:module ", 12) == 0) { - /* dep section: emit the directive, then dir bodies; - * skip the original `.wwi` content to the next - * directive. */ - fwrite(buf + i, 1, linelen, out); - fputc('\n', out); - char path[256]; - size_t pl = linelen - 12; - if (pl >= sizeof path) pl = sizeof path - 1; - memcpy(path, buf + i + 12, pl); - path[pl] = '\0'; - char dir[1024]; - char form[256]; - size_t k; - for (k = 0; path[k]; k++) - form[k] = (path[k] == '.') ? '/' : path[k]; - form[k] = '\0'; - snprintf(dir, sizeof dir, "%s/%s", libdir, form); - if (append_dir_bodies(out, dir) < 0) rc = -1; - /* advance past the wwi content to the next directive. */ - i = j + 1; - while (i < n) { - if (strncmp(buf + i, "//ww:module", 11) == 0) break; - size_t e = i; - while (e < n && buf[e] != '\n') e++; - i = (e < n) ? e + 1 : n; - } - continue; - } - i = j + 1; - } - fclose(out); - free(buf); - return rc; -} - -static const char *root_src = - "package main;\n" - "import os;\n" - "fn main() i32 = { return os.getpid() - os.getpid() + 7; };\n"; - -int -main(void) -{ - const char *bin = absbin(); - if (!bin) return 1; - char td[64], cmd[8192], libdir[2048]; - int fail = 0; - - snprintf(libdir, sizeof libdir, "%s/../../lib", bin); - snprintf(td, sizeof td, "/tmp/wwsep_%d", getpid()); - if (mkdir(td, 0755) != 0) { - fprintf(stderr, "sepbuild FAIL: cannot acquire %s\n", td); - return 1; - } - - char rootww[1024]; - snprintf(rootww, sizeof rootww, "%s/root.ww", td); - if (write_file(rootww, root_src)) { fail++; goto out; } - - /* The two driver stages and their scratch dirs. All per-package paths - * are rebuilt from `td` (a small fixed buffer) + the stage tag rather - * than chained through a large path buffer, so the snprintfs are - * provably non-truncating (warning-clean, like 989_m3sep_run). */ - struct { const char *drv, *tag; char prog[1024]; } - stg[] = { { "ww", "cs", {0} }, { "ww_ww", "ww", {0} } }; - - 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 -o %s %s >/dev/null 2>&1", - bin, stg[s].drv, stg[s].prog, rootww); - if (runwait(cmd) != 0) { - fprintf(stderr, "sepbuild FAIL: %s build\n", stg[s].drv); - fail++; - continue; - } - int rc = runwait(stg[s].prog); - if (rc != EXPECT_EXIT) { - fprintf(stderr, "sepbuild FAIL: %s prog exit=%d expected %d\n", - stg[s].drv, rc, EXPECT_EXIT); - fail++; - } - } - - /* reverse-topo correctness is implicit: every package compiled (a - * dep's `.wwi` existed before its importer) → build succeeded above. - * Assert the discovered package set materialized in the scratch. */ - const char *pkgs[] = { "time", "rt", "os", "__root" }; - for (int i = 0; i < 4; i++) { - char p[1024]; - /* #69: the root is compiled WITHOUT `-I`, so it produces no `.wwi` - * (its interface is never consumed); assert its `.s` materialized - * instead. Dep packages still emit a `.wwi`. */ - int is_root = (strcmp(pkgs[i], "__root") == 0); - snprintf(p, sizeof p, "%s/prog.cs.sepwork/%s%s", td, pkgs[i], - is_root ? ".s" : ".wwi"); - if (access(p, 0) != 0) { - fprintf(stderr, "sepbuild FAIL: missing %s%s (discovery/topo)\n", - pkgs[i], is_root ? ".s" : ".wwi"); - fail++; - } - } - - /* cs==ww (rule 10): per-package .s/.wwi/.unit.ww + final binary. The - * root has no `.wwi` post-#69, so it is excluded from the .wwi compare. */ - for (int i = 0; i < 4; i++) { - const char *suf[] = { ".s", ".wwi", ".unit.ww" }; - for (int k = 0; k < 3; k++) { - if (strcmp(pkgs[i], "__root") == 0 && strcmp(suf[k], ".wwi") == 0) - continue; - char a[1024], b[1024]; - snprintf(a, sizeof a, "%s/prog.%s.sepwork/%s%s", - td, stg[0].tag, pkgs[i], suf[k]); - snprintf(b, sizeof b, "%s/prog.%s.sepwork/%s%s", - td, stg[1].tag, pkgs[i], suf[k]); - if (files_eq(a, b) != 0) { - fprintf(stderr, "sepbuild FAIL: cs!=ww for %s%s (rule 10)\n", - pkgs[i], suf[k]); - fail++; - } - } - } - if (files_eq(stg[0].prog, stg[1].prog) != 0) { - fprintf(stderr, "sepbuild FAIL: cs exe != ww exe (rule 10)\n"); - fail++; - } - - /* KEYSTONE through the driver: bodies-unit == sep-unit codegen, for - * each non-leaf package (os, root). Transform the driver's own - *

.unit.ww (deps as .wwi) into a bodies-unit (deps as full dir - * bodies), `w6c -c` it, cmp vs the driver's

.s. */ - const char *keypkgs[] = { "os", "__root" }; - for (int i = 0; i < 2; i++) { - char unitf[1024], bodiesf[1024], bodies_s[1024], sep_s[1024]; - snprintf(unitf, sizeof unitf, "%s/prog.cs.sepwork/%s.unit.ww", td, keypkgs[i]); - snprintf(bodiesf, sizeof bodiesf, "%s/%s.bodies.ww", td, keypkgs[i]); - snprintf(bodies_s, sizeof bodies_s, "%s/%s.bodies.s", td, keypkgs[i]); - snprintf(sep_s, sizeof sep_s, "%s/prog.cs.sepwork/%s.s", td, keypkgs[i]); - if (compose_bodies(unitf, libdir, bodiesf) < 0) { - fprintf(stderr, "sepbuild FAIL: compose bodies for %s\n", keypkgs[i]); - fail++; - continue; - } - snprintf(cmd, sizeof cmd, - "timeout 240 %s/w6c -c -o %s %s >/dev/null 2>&1", - bin, bodies_s, bodiesf); - if (runwait(cmd) != 0) { - fprintf(stderr, "sepbuild FAIL: %s bodies -c\n", keypkgs[i]); - fail++; - continue; - } - if (files_eq(bodies_s, sep_s) != 0) { - fprintf(stderr, "sepbuild FAIL: %s — bodies.s != driver sep.s " - "(the .wwi does not convey the dep facts P needs)\n", keypkgs[i]); - fail++; - } - } - - /* `run` must compile and execute this cross-package graph through the - * sole separate-compilation path. Both stages must agree and return - * EXPECT_EXIT. */ - { - int rc_cs, rc_ww; - snprintf(cmd, sizeof cmd, - "%s/ww run %s >/dev/null 2>&1", bin, rootww); - rc_cs = runwait(cmd); - snprintf(cmd, sizeof cmd, - "%s/ww_ww run %s >/dev/null 2>&1", bin, rootww); - rc_ww = runwait(cmd); - if (rc_cs != rc_ww || rc_cs != EXPECT_EXIT) { - fprintf(stderr, "sepbuild FAIL: run cs=%d ww=%d " - "(both must run via sep, exit %d)\n", rc_cs, rc_ww, - EXPECT_EXIT); - fail++; - } - } - -out: - snprintf(cmd, sizeof cmd, - "rm -rf -- '%s/prog.cs.sepwork' '%s/prog.ww.sepwork'", td, td); - if (runwait(cmd) != 0) { - fprintf(stderr, "sepbuild 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, "sepbuild FAIL: cannot remove %s\n", path); fail++; } - snprintf(path, sizeof path, "%s/prog.cs", td); - if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepbuild FAIL: cannot remove %s\n", path); fail++; } - snprintf(path, sizeof path, "%s/prog.ww", td); - if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepbuild FAIL: cannot remove %s\n", path); fail++; } - snprintf(path, sizeof path, "%s/os.bodies.ww", td); - if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepbuild FAIL: cannot remove %s\n", path); fail++; } - snprintf(path, sizeof path, "%s/os.bodies.s", td); - if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepbuild FAIL: cannot remove %s\n", path); fail++; } - snprintf(path, sizeof path, "%s/__root.bodies.ww", td); - if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepbuild FAIL: cannot remove %s\n", path); fail++; } - snprintf(path, sizeof path, "%s/__root.bodies.s", td); - if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepbuild FAIL: cannot remove %s\n", path); fail++; } - } - if (rmdir(td) != 0 && errno != ENOENT) { - fprintf(stderr, "sepbuild FAIL: cannot remove %s\n", td); - fail++; - } - if (fail) { - fprintf(stderr, "sepbuild: %d check(s) failed\n", fail); - return 1; - } - printf("sepbuild: real chain root->os->{rt,time} via build_one_sep — " - "build+run (exit %d) + cs==ww per-pkg .s/.wwi/.unit + final binary " - "+ transitive-topo discovery + keystone bodies==.wwi (os,root)\n", - EXPECT_EXIT); - return 0; -} diff --git a/test/wcc/989_seproot_export_run.c b/test/wcc/989_seproot_export_run.c deleted file mode 100644 index c4b34403..00000000 --- a/test/wcc/989_seproot_export_run.c +++ /dev/null @@ -1,323 +0,0 @@ -/* - * 989_seproot_export_run — sep-build of a ROOT unit whose `export fn` - * references an unexported LOCAL type (#69, BUG-1). - * - * Root cause it guards: the separate-compilation producer compiled EVERY package, - * INCLUDING the root build-target, with the `.wwi`-producer `-I` flag. - * `-I` triggers wwi_emit -> check_exported_type, which rejects an exported - * declaration that references an unexported type. A terminal binary's root - * legitimately has such a decl (`export fn use(a: *t)` over an unexported - * local `type t`) — fine, because the root is never imported, so its `.wwi` - * is never consumed. Historically, the separately compiled root was also - * passed `-I`, so it rejected and blocked every real tool build. - * Fix: for pi==root the producer invokes `w6c -c -o` WITHOUT `-I`. - * - * Graph (smallest reproducing shape): root -> { c (one real dep package) }. - * The root exports `use(a: *t)` over the unexported local `type t`. - * - * Asserts (direct builds compile every package): - * 1. Build + run, BOTH stages -> exit EXPECT_EXIT. Pre-fix the root w6c - * pass exited 1 ("exported declaration references unexported type"), - * so the build never produced a binary. - * 2. cs==ww (rule 10): per-package `.s`/`.wwi`/`.unit.ww` for {c,__root} - * AND the final binary are byte-identical between the two drivers. - * 3. NON-VACUITY (forced -I reject): re-run the EXACT pre-fix root - * invocation on the produced `__root.unit.ww` — `w6c -c -I -o` - * MUST exit non-zero (the export-check fires on this pattern), while - * `w6c -c -o` (the post-fix root invocation) MUST exit 0. Demonstrated - * for BOTH compilers (w6c, w6c_ww). This is the bug, isolated. - * 4. The root `.s` is produced and defines the exported fn symbol. - * - * All intermediates are `-o`-redirected to /tmp for isolation. Models - * 989_sepdotpath_run.c conventions; 989 prefix per the sep-gate precedent. - */ -#include -#include -#include -#include -#include -#include -#include - -#define EXPECT_EXIT 37 - -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; -} - -/* 0 if `needle` occurs in the file at `path`, 1 if absent, -1 on read err. */ -static int -file_contains(const char *path, const char *needle) -{ - char *b = NULL; - size_t n = 0; - if (slurp(path, &b, &n) < 0) return -1; - int found = (strstr(b, needle) != NULL); - free(b); - return found ? 0 : 1; -} - -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], p[1024]; - int fail = 0, have_lib = 0, have_c = 0; - - snprintf(td, sizeof td, "/tmp/wwseproot_%d", getpid()); - if (mkdir(td, 0755) != 0) { - fprintf(stderr, "seproot FAIL: cannot acquire %s\n", td); - return 1; - } - - /* lib/c — one real dep package the root imports. */ - snprintf(p, sizeof p, "%s/lib", td); - if (mkdir(p, 0755) != 0) { - fprintf(stderr, "seproot FAIL: cannot create %s\n", p); - fail++; goto out; - } - have_lib = 1; - snprintf(p, sizeof p, "%s/lib/c", td); - if (mkdir(p, 0755) != 0) { - fprintf(stderr, "seproot FAIL: cannot create %s\n", p); - fail++; goto out; - } - have_c = 1; - snprintf(p, sizeof p, "%s/lib/c/mod.ww", td); - if (write_file(p, "package c;\nexport fn cval() i32 = { return 5; };\n")) - { fail++; goto out; } - - /* Root: `export fn use(a: *t)` over the UNEXPORTED local `type t` — - * exactly the pattern `-I`'s check_exported_type rejected. */ - char rootww[1024]; - snprintf(rootww, sizeof rootww, "%s/root.ww", td); - if (write_file(rootww, - "package main;\n" - "import c;\n" - "type t = struct { v: i32 };\n" - "export fn use(a: *t) i32 = { return a.v; };\n" - "fn main() i32 = {\n" - "\tlet x: t = t { v = 42 };\n" - "\treturn use(&x) - c.cval();\n" - "};\n")) - { fail++; goto out; } - - struct { const char *drv, *tag; char prog[1024]; } - stg[] = { { "ww", "cs", {0} }, { "ww_ww", "ww", {0} } }; - - for (int s = 0; s < 2; s++) { - snprintf(stg[s].prog, sizeof stg[s].prog, "%s/prog.%s", td, stg[s].tag); - /* Each direct build compiles every package, so the - * `.s`/`.unit.ww` this gate inspects are always produced. */ - snprintf(cmd, sizeof cmd, - "timeout 240 %s/%s build " - "-I %s/lib -o %s %s >/dev/null 2>&1", - bin, stg[s].drv, td, stg[s].prog, rootww); - if (runwait(cmd) != 0) { - fprintf(stderr, "seproot FAIL: %s build (pre-fix: -I on root " - "rejects export-fn-over-unexported-type)\n", stg[s].drv); - fail++; - continue; - } - int rc = runwait(stg[s].prog); - if (rc != EXPECT_EXIT) { - fprintf(stderr, "seproot FAIL: %s prog exit=%d expected %d\n", - stg[s].drv, rc, EXPECT_EXIT); - fail++; - } - } - - /* cs==ww (rule 10): per-package .s/.unit.ww + final binary. The root's - * `.wwi` is intentionally NOT produced (the fix), so it is excluded - * here and asserted ABSENT below; the dep `c` still emits a `.wwi`. */ - const char *pkgs[] = { "c", "__root" }; - for (int i = 0; i < 2; i++) { - const char *suf[] = { ".s", ".wwi", ".unit.ww" }; - for (int k = 0; k < 3; k++) { - if (strcmp(pkgs[i], "__root") == 0 && strcmp(suf[k], ".wwi") == 0) - continue; - char a[1024], b[1024]; - snprintf(a, sizeof a, "%s/prog.%s.sepwork/%s%s", - td, stg[0].tag, pkgs[i], suf[k]); - snprintf(b, sizeof b, "%s/prog.%s.sepwork/%s%s", - td, stg[1].tag, pkgs[i], suf[k]); - if (files_eq(a, b) != 0) { - fprintf(stderr, "seproot FAIL: cs!=ww for %s%s (rule 10)\n", - pkgs[i], suf[k]); - fail++; - } - } - } - if (files_eq(stg[0].prog, stg[1].prog) != 0) { - fprintf(stderr, "seproot FAIL: cs exe != ww exe (rule 10)\n"); - fail++; - } - - /* NON-VACUITY: the fix omits `-I` for the root, so the root's `.wwi` - * (the producer flag's only effect) must NOT exist — BOTH stages. */ - for (int s = 0; s < 2; s++) { - char rw[1024]; - snprintf(rw, sizeof rw, "%s/prog.%s.sepwork/__root.wwi", td, stg[s].tag); - if (access(rw, 0) == 0) { - fprintf(stderr, "seproot FAIL: %s produced __root.wwi (-I still " - "passed for the root)\n", stg[s].drv); - fail++; - } - } - - /* NON-VACUITY: replay the EXACT pre-fix root invocation on the produced - * `__root.unit.ww`. With `-I` (the .wwi producer) the export-check fires - * and w6c exits non-zero; without `-I` (the post-fix invocation) it - * exits 0. Proven for BOTH compilers. */ - { - char unit[1024], wwi[1024], asmf[1024]; - snprintf(unit, sizeof unit, "%s/prog.cs.sepwork/__root.unit.ww", td); - struct { const char *comp; } cc[] = { { "w6c" }, { "w6c_ww" } }; - for (int j = 0; j < 2; j++) { - snprintf(wwi, sizeof wwi, "%s/nv.%s.wwi", td, cc[j].comp); - snprintf(asmf, sizeof asmf, "%s/nv.%s.s", td, cc[j].comp); - snprintf(cmd, sizeof cmd, - "%s/%s -c -I %s -o %s %s >/dev/null 2>&1", - bin, cc[j].comp, wwi, asmf, unit); - if (runwait(cmd) == 0) { - fprintf(stderr, "seproot FAIL: %s -c -I accepted the root " - "export-over-unexported-type (bug not reproduced -> " - "vacuous gate)\n", cc[j].comp); - fail++; - } - snprintf(cmd, sizeof cmd, - "%s/%s -c -o %s %s >/dev/null 2>&1", - bin, cc[j].comp, asmf, unit); - if (runwait(cmd) != 0) { - fprintf(stderr, "seproot FAIL: %s -c -o (no -I) rejected the " - "root unit (post-fix invocation must succeed)\n", - cc[j].comp); - fail++; - } - } - } - - /* The root `.s` is produced and defines the exported fn. */ - { - char rs[1024]; - snprintf(rs, sizeof rs, "%s/prog.cs.sepwork/__root.s", td); - if (file_contains(rs, "use") != 0) { - fprintf(stderr, "seproot FAIL: __root.s lacks the exported `use` " - "symbol (root compile produced no code)\n"); - fail++; - } - } - -out: - snprintf(cmd, sizeof cmd, - "rm -rf -- '%s/prog.cs.sepwork' '%s/prog.ww.sepwork'", td, td); - if (runwait(cmd) != 0) { - fprintf(stderr, "seproot 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, "seproot FAIL: cannot remove %s\n", path); fail++; } - snprintf(path, sizeof path, "%s/prog.cs", td); - if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", path); fail++; } - snprintf(path, sizeof path, "%s/prog.ww", td); - if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", path); fail++; } - snprintf(path, sizeof path, "%s/nv.w6c.wwi", td); - if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", path); fail++; } - snprintf(path, sizeof path, "%s/nv.w6c.s", td); - if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", path); fail++; } - snprintf(path, sizeof path, "%s/nv.w6c_ww.wwi", td); - if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", path); fail++; } - snprintf(path, sizeof path, "%s/nv.w6c_ww.s", td); - if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", path); fail++; } - } - if (have_c) { - snprintf(p, sizeof p, "%s/lib/c", td); - { - char path[1200]; - snprintf(path, sizeof path, "%s/mod.ww", p); - if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", path); fail++; } - } - if (rmdir(p) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", p); fail++; } - } - if (have_lib) { - snprintf(p, sizeof p, "%s/lib", td); - if (rmdir(p) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", p); fail++; } - } - if (rmdir(td) != 0 && errno != ENOENT) { - fprintf(stderr, "seproot FAIL: cannot remove %s\n", td); - fail++; - } - if (fail) { - fprintf(stderr, "seproot: %d check(s) failed\n", fail); - return 1; - } - printf("seproot: root `export fn use(a:*t)` over unexported `type t` " - "via build_one_sep — build+run (exit %d) + cs==ww per-pkg + final " - "binary + forced -I rejects (bug) while -o accepts (both compilers)\n", - EXPECT_EXIT); - return 0; -} diff --git a/test/wcc/989_sepstructdef_run.c b/test/wcc/989_sepstructdef_run.c deleted file mode 100644 index 3e7d25ee..00000000 --- a/test/wcc/989_sepstructdef_run.c +++ /dev/null @@ -1,275 +0,0 @@ -/* - * 989_sepstructdef_run — sep-build of a dep package that EXPORTS aggregate- - * initializer defs: `export def info: s = s{a=7,b=11};` (N_STRUCTLIT) and - * `export def arr: [3]i32 = [1,2,3];` (N_ARRLIT) — BUG-2 (#70). - * - * Root cause it guards: the `.wwi` producer's const-expr emitter handled - * scalar/N_BIN/N_UN/N_DOT but NOT N_STRUCTLIT(15)/N_ARRLIT(16). An exported - * aggregate-init def aborted the producer with "unhandled const-expr node - * kind 15", so the dep never produced a `.wwi` and the sep-build failed. - * - * Fix (producer, both stages): an aggregate-init def is a DATA-global (#52), - * so the producer emits a value-LESS prototype `export def X: T;` — the - * defining package's own `.o` emits the struct/array DATA; the importer - * registers the def by TYPE only (cgen's emit_defs/emit_lets already skip a - * NULL-rhs def, no dup DATA) and reads resolve to external refs the linker - * fills. Consuming the prototype also required the def parser (both stages) - * to accept the bodyless form, mirroring the existing fn-prototype arm. - * - * Graph (smallest reproducing shape): root -> { d (one real dep package) }. - * d exports an aggregate STRUCT def (info), an aggregate ARRAY def (arr), - * and three reader fns; root sums them at RUNTIME. - * - * Asserts (direct builds compile every package): - * 1. Build + LINK + run, BOTH stages -> exit EXPECT_EXIT. The link+run is - * the external-DATA-ref proof: the importer's CALL d.geta / d.getelem / - * d.getb and d's own LEAQ d.info/d.arr(SB) refs resolve against the dep - * `.o`. Pre-fix the dep producer aborted ("node kind 15") -> no binary. - * 2. cs==ww (rule 10): per-package `.s`/`.wwi`/`.unit.ww` for {d,__root} - * AND the final binary are byte-identical between the two drivers. - * 3. The dep `.wwi` carries the value-LESS prototypes `export def info: s;` - * and `export def arr: [3]i32;` (the fix's surface), and NOT the old - * value-serialized `export def info: s = ` form (NON-VACUITY: proves the - * producer took the new aggregate arm, not the scalar arm). - * - * All intermediates are `-o`-redirected to /tmp for isolation. Models - * 989_seproot_export_run.c conventions; 989 prefix per the sep-gate precedent. - */ -#include -#include -#include -#include -#include -#include -#include - -#define EXPECT_EXIT 20 /* info.a(7) + arr[1](2) + info.b(11) */ - -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; -} - -/* 0 if `needle` occurs in the file at `path`, 1 if absent, -1 on read err. */ -static int -file_contains(const char *path, const char *needle) -{ - char *b = NULL; - size_t n = 0; - if (slurp(path, &b, &n) < 0) return -1; - int found = (strstr(b, needle) != NULL); - free(b); - return found ? 0 : 1; -} - -static int -write_file(const char *path, const char *body) -{ - FILE *f = fopen(path, "wb"); - if (!f) return -1; - if (fputs(body, f) == EOF) { fclose(f); return -1; } - return fclose(f); -} - -int -main(void) -{ - const char *bin = absbin(); - if (!bin) return 1; - char td[] = "/tmp/wwsepstructdef_XXXXXX"; - char cmd[8192], p[1024]; - int fail = 0, cleanup_fail = 0, have_lib = 0, have_d = 0; - - if (mkdtemp(td) == NULL) return 1; - - /* lib/d — the dep package exporting aggregate-init defs + reader fns. - * The struct type must be EXPORTED so the prototype names a visible - * type (an exported def over an unexported type is a separate loud - * reject, check_exported_type). */ - snprintf(p, sizeof p, "%s/lib", td); - if (mkdir(p, 0755) != 0) { fail++; goto out; } - have_lib = 1; - snprintf(p, sizeof p, "%s/lib/d", td); - if (mkdir(p, 0755) != 0) { fail++; goto out; } - have_d = 1; - snprintf(p, sizeof p, "%s/lib/d/mod.ww", td); - if (write_file(p, - "package d;\n" - "export type s = struct { a: i32, b: i32 };\n" - "export def info: s = s { a = 7, b = 11 };\n" - "export def arr: [3]i32 = [1, 2, 3];\n" - "export fn geta() i32 = { return info.a; };\n" - "export fn getb() i32 = { return info.b; };\n" - "export fn getelem(i: i32) i32 = { return arr[i]; };\n")) - { fail++; goto out; } - - char rootww[1024]; - snprintf(rootww, sizeof rootww, "%s/root.ww", td); - if (write_file(rootww, - "package main;\n" - "import d;\n" - "fn main() i32 = {\n" - "\treturn d.geta() + d.getelem(1) + d.getb();\n" - "};\n")) - { fail++; goto out; } - - struct { const char *drv, *tag; char prog[1024]; } - stg[] = { { "ww", "cs", {0} }, { "ww_ww", "ww", {0} } }; - - for (int s = 0; s < 2; s++) { - snprintf(stg[s].prog, sizeof stg[s].prog, "%s/prog.%s", td, stg[s].tag); - /* Each direct build compiles every package, so the - * `.s`/`.wwi`/`.unit.ww` this gate inspects are always produced. */ - snprintf(cmd, sizeof cmd, - "timeout 240 %s/%s build " - "-I %s/lib -o %s %s >/dev/null 2>&1", - bin, stg[s].drv, td, stg[s].prog, rootww); - if (runwait(cmd) != 0) { - fprintf(stderr, "sepstructdef FAIL: %s build (pre-fix: the " - "aggregate-init def aborts the producer with \"node kind 15\")\n", - stg[s].drv); - fail++; - continue; - } - int rc = runwait(stg[s].prog); - if (rc != EXPECT_EXIT) { - fprintf(stderr, "sepstructdef FAIL: %s prog exit=%d expected %d " - "(external aggregate-DATA ref did not resolve)\n", - stg[s].drv, rc, EXPECT_EXIT); - fail++; - } - } - - /* cs==ww (rule 10): per-package .s/.wwi/.unit.ww + final binary. */ - const char *pkgs[] = { "d", "__root" }; - for (int i = 0; i < 2; i++) { - const char *suf[] = { ".s", ".wwi", ".unit.ww" }; - for (int k = 0; k < 3; k++) { - /* the root's `.wwi` is intentionally not produced (BUG-1 fix). */ - if (strcmp(pkgs[i], "__root") == 0 && strcmp(suf[k], ".wwi") == 0) - continue; - char a[1024], b[1024]; - snprintf(a, sizeof a, "%s/prog.%s.sepwork/%s%s", - td, stg[0].tag, pkgs[i], suf[k]); - snprintf(b, sizeof b, "%s/prog.%s.sepwork/%s%s", - td, stg[1].tag, pkgs[i], suf[k]); - if (files_eq(a, b) != 0) { - fprintf(stderr, "sepstructdef FAIL: cs!=ww for %s%s (rule 10)\n", - pkgs[i], suf[k]); - fail++; - } - } - } - if (files_eq(stg[0].prog, stg[1].prog) != 0) { - fprintf(stderr, "sepstructdef FAIL: cs exe != ww exe (rule 10)\n"); - fail++; - } - - /* The dep `.wwi` carries the value-LESS aggregate prototypes — and NOT - * the old value-serialized form (NON-VACUITY: the producer took the new - * aggregate arm, not the scalar `= ` arm). */ - { - char wwi[1024]; - snprintf(wwi, sizeof wwi, "%s/prog.cs.sepwork/d.wwi", td); - if (file_contains(wwi, "export def info: s;\n") != 0) { - fprintf(stderr, "sepstructdef FAIL: d.wwi lacks value-less " - "`export def info: s;` (struct-lit def prototype)\n"); - fail++; - } - if (file_contains(wwi, "export def arr: [3]i32;\n") != 0) { - fprintf(stderr, "sepstructdef FAIL: d.wwi lacks value-less " - "`export def arr: [3]i32;` (array-lit def prototype)\n"); - fail++; - } - if (file_contains(wwi, "export def info: s = ") == 0) { - fprintf(stderr, "sepstructdef FAIL: d.wwi serialized the aggregate " - "value (`export def info: s = ...`) -> vacuous gate\n"); - fail++; - } - } - -out: - for (int s = 0; s < 2; s++) { - const char *tag = s == 0 ? "cs" : "ww"; - snprintf(p, sizeof p, "%s/prog.%s.sepwork", td, tag); - snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", p); - if (runwait(cmd) != 0) cleanup_fail = 1; - snprintf(p, sizeof p, "%s/prog.%s", td, tag); - if (unlink(p) != 0 && errno != ENOENT) cleanup_fail = 1; - } - snprintf(p, sizeof p, "%s/root.ww", td); - if (unlink(p) != 0 && errno != ENOENT) cleanup_fail = 1; - if (have_d) { - snprintf(p, sizeof p, "%s/lib/d/mod.ww", td); - if (unlink(p) != 0 && errno != ENOENT) cleanup_fail = 1; - snprintf(p, sizeof p, "%s/lib/d", td); - if (rmdir(p) != 0 && errno != ENOENT) cleanup_fail = 1; - } - if (have_lib) { - snprintf(p, sizeof p, "%s/lib", td); - if (rmdir(p) != 0 && errno != ENOENT) cleanup_fail = 1; - } - if (rmdir(td) != 0) cleanup_fail = 1; - if (cleanup_fail) { - fprintf(stderr, "sepstructdef FAIL: cleanup incomplete under %s\n", td); - fail++; - } - if (fail) { - fprintf(stderr, "sepstructdef: %d check(s) failed\n", fail); - return 1; - } - printf("sepstructdef: exported struct-lit + array-lit defs via " - "build_one_sep — build+link+run (exit %d) + cs==ww per-pkg + binary " - "+ value-less `.wwi` prototypes (both stages)\n", EXPECT_EXIT); - return 0; -} diff --git a/test/wcc/989_wwispread_sep.c b/test/wcc/989_wwispread_sep.c deleted file mode 100644 index 79aeafff..00000000 --- a/test/wcc/989_wwispread_sep.c +++ /dev/null @@ -1,196 +0,0 @@ -/* - * 989_wwispread_sep — #95 .wwi producer must preserve the `...` union-spread - * marker when serializing a tagged-union type under `ww build`. - * - * A spread variant `(...inner | str)` carries Node.op == TK_ELLIPSIS on the - * variant node (parse.c:315-324). The N_TTAGGED serializer loop must re-emit - * the `...` prefix purely syntactically (cstage wwi.c, selfhost wwi.ww) — - * mirroring Hare's unparse (ref/hare/hare/unparse/type.ha:290-300), which - * leaves flattening to the consumer's type-store. Before the fix the marker - * was dropped: liba.wwi read back as `(inner | str)`, a consumer never - * flattened inner's arms, and a bare i32/i64 member was rejected — so fmt / - * log / getopt failed under the sep flip. (The old combined.ww path never - * round-trips through .wwi, so it accepted; the bug was latent until the flip.) - * - * TABLE-DRIVEN over two spread shapes (2-arm `inner` and 3-arm `three`). For - * each shape and each driver stage (cs `ww`, ww `ww_ww`) the test: - * (a) builds the consumer via `ww build` and runs it — clean compile - * + exit 0 proves the imported alias's arms flattened into the spread - * type (the consumer assigns BARE members the spread must expose); - * (b) greps the produced .wwi and asserts the spread marker survived; - * (c) cs==ww (rule 10): the .wwi from `ww` vs `ww_ww` must be byte-identical. - * - * NON-VACUITY: reverting the wwi.c/.ww edit reddens BOTH legs — the consumer - * build fails with `init i32 not assignable to declared outer` AND the marker - * grep finds nothing (verified by hand at fix time). - * - * Every build's intermediates are `-o`-redirected to a private /tmp directory. - * Models 989_declns_sep conventions; 989 prefix per the sep-gate precedent. - */ -#include -#include -#include -#include -#include -#include -#include - -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; -} - -/* 0 if `needle` occurs in the file at `path`, else 1 (or -1 on read err). */ -static int -file_contains(const char *path, const char *needle) -{ - char *b = NULL; - size_t n = 0; - if (slurp(path, &b, &n) < 0) return -1; - int found = (strstr(b, needle) != NULL); - free(b); - return found ? 0 : 1; -} - -struct shape { - const char *name; /* label */ - const char *main; /* consumer main.ww (imports the spread package) */ - const char *wwi; /* produced interface basename for the imported pkg */ - const char *marker; /* the spread marker that must survive serialization */ -}; -static const struct shape shapes[] = { - { "twoarm", "test/wcc/data/wwispread/main.ww", "liba.wwi", "...inner" }, - { "threearm", "test/wcc/data/wwispread3/main.ww", "libb.wwi", "...three" }, -}; - -static const struct { const char *drv; const char *tag; } drivers[] = { - { "ww", "cs" }, - { "ww_ww", "ww" }, -}; - -int -main(void) -{ - const char *bin = absbin(); - if (!bin) { fprintf(stderr, "wwispread FAIL: getcwd\n"); return 1; } - - char td[] = "/tmp/wwwwispread_XXXXXX"; - char cmd[4096]; - if (mkdtemp(td) == NULL) return 1; - - int fail = 0, cleanup_fail = 0; - for (size_t s = 0; s < sizeof shapes / sizeof shapes[0]; s++) { - char wwi[2][256] = {{0}}; - for (size_t d = 0; d < sizeof drivers / sizeof drivers[0]; d++) { - char prog[160]; - snprintf(prog, sizeof prog, "%s/%s.%s", td, shapes[s].name, - drivers[d].tag); - snprintf(cmd, sizeof cmd, "%s/%s build -o %s %s 2>/dev/null", - bin, drivers[d].drv, prog, shapes[s].main); - if (runwait(cmd) != 0) { - fprintf(stderr, "wwispread FAIL: %s build %s " - "(spread member not assignable → marker dropped?)\n", - drivers[d].drv, shapes[s].name); - fail++; - continue; - } - int got = runwait(prog); - if (got != 0) { - fprintf(stderr, "wwispread FAIL: %s %s run exit=%d want=0\n", - drivers[d].drv, shapes[s].name, got); - fail++; - } - snprintf(wwi[d], sizeof wwi[d], "%s/%s.%s.sepwork/%s", td, - shapes[s].name, drivers[d].tag, shapes[s].wwi); - if (file_contains(wwi[d], shapes[s].marker) != 0) { - fprintf(stderr, "wwispread FAIL: %s %s — %s missing `%s` " - "(spread marker dropped in serializer)\n", drivers[d].drv, - shapes[s].name, shapes[s].wwi, shapes[s].marker); - fail++; - } - } - /* cs==ww (rule 10): both stages emit the same interface text. */ - if (files_eq(wwi[0], wwi[1]) != 0) { - fprintf(stderr, "wwispread FAIL: %s cs!=ww for %s (rule 10)\n", - shapes[s].name, shapes[s].wwi); - fail++; - } - } - - for (size_t s = 0; s < sizeof shapes / sizeof shapes[0]; s++) { - for (size_t d = 0; d < sizeof drivers / sizeof drivers[0]; d++) { - char path[1200]; - snprintf(path, sizeof path, "%s/%s.%s.sepwork", td, - shapes[s].name, drivers[d].tag); - snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", path); - if (runwait(cmd) != 0) cleanup_fail = 1; - snprintf(path, sizeof path, "%s/%s.%s", td, - shapes[s].name, drivers[d].tag); - if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1; - } - } - if (rmdir(td) != 0) cleanup_fail = 1; - if (cleanup_fail) { - fprintf(stderr, "wwispread FAIL: cleanup incomplete under %s\n", td); - fail++; - } - - if (fail) { - fprintf(stderr, "wwispread: %d check(s) failed\n", fail); - return 1; - } - printf("wwispread: `...` union-spread marker survives .wwi serialization " - "(2-arm + 3-arm, both stages cs==ww), consumer flattens bare members " - "→ exit 0 (#95)\n"); - return 0; -}