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.
This commit is contained in:
28
Makefile
28
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
|
||||
|
||||
464
test/sep/sepbuild_test.ww
Normal file
464
test/sep/sepbuild_test.ww
Normal file
@@ -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 <pkg>.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
|
||||
// <path>` dep section (its .wwi content) is replaced by <path>'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");
|
||||
};
|
||||
@@ -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
|
||||
* `<stem>.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 `<P>.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 `<P>.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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.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
|
||||
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 <path>` dep section (its `.wwi` content)
|
||||
* is replaced by <path>'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. <libdir> 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
|
||||
* <P>.unit.ww (deps as .wwi) into a bodies-unit (deps as full dir
|
||||
* bodies), `w6c -c` it, cmp vs the driver's <P>.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;
|
||||
}
|
||||
@@ -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 <wwi> -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 <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 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;
|
||||
}
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#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 `= <init>` 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;
|
||||
}
|
||||
@@ -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 <pkg>.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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user