test: port the .wwi observers to ww; retire 989_m2wwi + 989_wwileaf
test/byteid/wwi_test.ww carries every assertion of both carriers: the M2 producer gate (ascii/strings/getopt driver-combined positives with byte-id + wwdump -a re-parse, the synth decl-kind/type-node sweep with the #47 @symbol round-trip, the #48 types export-def gate, and the negative private-nominal leak with both-stage identical reject diagnostics), plus the #11 wwileaf decl-less-leaf rows (package-line leaf, cross-stage .wwi byte-id, importer resolution + .s byte-id). Byteid carriers 10 -> 8; docs counts move; the byteid ww target gains the wwdump prereq its re-parse legs invoke.
This commit is contained in:
15
Makefile
15
Makefile
@@ -354,15 +354,13 @@ BYTEID_WRAPPER_SOURCES = test/wcc/753_convwrap_audit.c \
|
|||||||
test/wcc/930_free_noop_run.c \
|
test/wcc/930_free_noop_run.c \
|
||||||
test/wcc/946_structparam_run.c \
|
test/wcc/946_structparam_run.c \
|
||||||
test/wcc/946_structret_run.c \
|
test/wcc/946_structret_run.c \
|
||||||
test/wcc/989_m1mangle_sym.c \
|
test/wcc/989_m1mangle_sym.c
|
||||||
test/wcc/989_m2wwi_run.c \
|
|
||||||
test/wcc/989_wwileaf_run.c
|
|
||||||
|
|
||||||
# Ww-native byte/artifact observers: single-file ww tests under
|
# Ww-native byte/artifact observers: single-file ww tests under
|
||||||
# test/byteid/ on the test/testenv helper package. Each drives both
|
# test/byteid/ on the test/testenv helper package. Each drives the
|
||||||
# driver stages itself, so the build needs only cstage plus the two
|
# stage tools it observes itself, so the build needs only cstage plus
|
||||||
# stage frontends it launches.
|
# the wwstage binaries it launches.
|
||||||
BYTEID_WW_TESTS = test/byteid/libbyteid_test.ww
|
BYTEID_WW_TESTS = test/byteid/libbyteid_test.ww test/byteid/wwi_test.ww
|
||||||
BYTEID_WW_TARGETS = $(BYTEID_WW_TESTS:%=wwtest/%)
|
BYTEID_WW_TARGETS = $(BYTEID_WW_TESTS:%=wwtest/%)
|
||||||
BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \
|
BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \
|
||||||
test/wcc/991_w6a_ww.c \
|
test/wcc/991_w6a_ww.c \
|
||||||
@@ -463,7 +461,8 @@ $(LIBRARY_TEST_TARGETS): wwtest/%: $(BIN)/ww \
|
|||||||
-I $(CURDIR)/lib/ww $*
|
-I $(CURDIR)/lib/ww $*
|
||||||
|
|
||||||
$(BYTEID_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a \
|
$(BYTEID_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a \
|
||||||
$(BIN)/w6l $(LIB)/libwwrt.a $(BIN)/ww_ww $(BIN)/w6c_ww
|
$(BIN)/w6l $(BIN)/wwdump $(LIB)/libwwrt.a \
|
||||||
|
$(BIN)/ww_ww $(BIN)/w6c_ww
|
||||||
@echo "ww test $*"
|
@echo "ww test $*"
|
||||||
@mkdir -p $(WWBUILD)/wwtest/$(subst /,_,$*)
|
@mkdir -p $(WWBUILD)/wwtest/$(subst /,_,$*)
|
||||||
@WW_TEST_REPO=$(CURDIR) $(BIN)/ww test \
|
@WW_TEST_REPO=$(CURDIR) $(BIN)/ww test \
|
||||||
|
|||||||
@@ -36,10 +36,12 @@ The live declarative compiler corpus has 1,495 fixtures and 2,990 C/WW cells:
|
|||||||
338 expected rejections (314 shared and 24 stage-specific), 17 compile-only
|
338 expected rejections (314 shared and 24 stage-specific), 17 compile-only
|
||||||
successes, 198 exit-zero programs, and 942 explicit-exit programs.
|
successes, 198 exit-zero programs, and 942 explicit-exit programs.
|
||||||
|
|
||||||
133 native C carriers remain. They are partitioned exactly once as five
|
131 native C carriers remain. They are partitioned exactly once as five
|
||||||
in-process units, 10 byte/artifact gates, six bootstrap gates, one platform
|
in-process units, 8 byte/artifact gates, six bootstrap gates, one platform
|
||||||
gate, and 111 residual compiler, package-layout, ABI, diagnostic-observer,
|
gate, and 111 residual compiler, package-layout, ABI, diagnostic-observer,
|
||||||
driver, linker, or FFI gates. Rows migrated to fixtures or native `@test`
|
driver, linker, or FFI gates. The `.wwi` round-trip observers are
|
||||||
|
ww-native (`test/byteid/wwi_test.ww`, replacing `989_m2wwi_run` and
|
||||||
|
`989_wwileaf_run`). Rows migrated to fixtures or native `@test`
|
||||||
owners were removed from those carriers; there is no compatibility execution
|
owners were removed from those carriers; there is no compatibility execution
|
||||||
path for retired rows. 74 former byte/artifact carriers whose only assertion
|
path for retired rows. 74 former byte/artifact carriers whose only assertion
|
||||||
was a cstage-vs-wwstage `.s` byte-compare of sources now living in the
|
was a cstage-vs-wwstage `.s` byte-compare of sources now living in the
|
||||||
|
|||||||
304
test/byteid/wwi_test.ww
Normal file
304
test/byteid/wwi_test.ww
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
package wwi_test;
|
||||||
|
|
||||||
|
// .wwi export-data observers. Ports of the retired native carriers
|
||||||
|
// test/wcc/989_m2wwi_run.c and test/wcc/989_wwileaf_run.c; every
|
||||||
|
// assertion preserved.
|
||||||
|
//
|
||||||
|
// m2* — the M2 `.wwi` producer gate (task #22 arc). Both stages (rule
|
||||||
|
// 10): the `w6c -I <out.wwi>` producer IS the live import path, and
|
||||||
|
// `.wwi` is a cross-stage byte-id substrate pinned directly. POSITIVE:
|
||||||
|
// for ascii/strings/getopt (drew2-audited leak-free), drive the target
|
||||||
|
// as the PRIMARY module of a driver-combined unit, then (1) w6c -I and
|
||||||
|
// w6c_ww -I both succeed, (2) cs.wwi == ww.wwi byte-for-byte, (3) the
|
||||||
|
// emitted .wwi re-parses (wwdump -a exit 0). getopt is the recursion
|
||||||
|
// stressor. A synth fixture covers the decl-kinds + type-nodes no lib
|
||||||
|
// package reaches (def const-expr fold, let global, [N]T, fn-ptr, !T,
|
||||||
|
// tuple, storage-less enum, and the #47 @symbol round-trip); the types
|
||||||
|
// gate proves the exported limit defs reach the interface. NEGATIVE: an
|
||||||
|
// exported fn naming a private nominal must be LOUD-REJECTED by
|
||||||
|
// check_exported_type identically on both stages (nonzero exit +
|
||||||
|
// byte-identical diagnostic) — without it a vacuous no-op check would
|
||||||
|
// pass the positive gate silently.
|
||||||
|
//
|
||||||
|
// wwileaf — BUG-C (#11) regression pin: a decl-less / export-less
|
||||||
|
// primary module's `.wwi` `package` line must carry the module's real
|
||||||
|
// leaf, NOT the literal default "main". Table-driven over the three
|
||||||
|
// decl-less shapes routing the wwi_emit fallback (empty, comment-only,
|
||||||
|
// nested dotted path a.b.c -> leaf c). Each row drives the REAL
|
||||||
|
// producer->importer flow on BOTH stages: (a) the dep `.wwi` package
|
||||||
|
// line equals the real leaf, (b) the dep `.wwi` is byte-identical
|
||||||
|
// across stages, (c) a root importing the dep RESOLVES on both stages
|
||||||
|
// and the two importer `.s` are byte-identical. The composed units are
|
||||||
|
// fed straight to w6c / w6c_ww (not `ww build`): the producer-unit
|
||||||
|
// shape `//ww:module-reset <path>` + body is exactly what the driver's
|
||||||
|
// sep_emit_body emits.
|
||||||
|
|
||||||
|
import os;
|
||||||
|
import os.exec;
|
||||||
|
import strings;
|
||||||
|
import testenv;
|
||||||
|
import time;
|
||||||
|
|
||||||
|
fn fail(label: str, why: str) void = {
|
||||||
|
let m: str = strings.concat("wwi FAIL: ", label, " -- ", why, "\n");
|
||||||
|
os.write(2, m.ptr, m.len: u64);
|
||||||
|
assert(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn lifetime() time.duration = {
|
||||||
|
return (180i64 * (time.second: i64)): time.duration;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run argv in `dir`, true iff normal exit 0.
|
||||||
|
fn runok(dir: str, name: str, argv: []str) bool = {
|
||||||
|
let co: testenv.commandout;
|
||||||
|
testenv.runcommand(dir, dir, name, argv, lifetime(), &co);
|
||||||
|
return co.termination == exec.termination.EXIT && co.code == 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn firstline(path: str) str = {
|
||||||
|
let body: str = testenv.readfile(path);
|
||||||
|
let nl: i32 = testenv.pos(body, "\n");
|
||||||
|
if (nl < 0) { return body; };
|
||||||
|
return strings.sub(body, 0, nl);
|
||||||
|
};
|
||||||
|
|
||||||
|
// one positive package: build the target as primary, produce the `.wwi`
|
||||||
|
// on both stages, require byte-id + re-parse.
|
||||||
|
fn m2positive(pkg: str) void = {
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
let srcbase: str = strings.concat(pkg, ".ww");
|
||||||
|
testenv.writefile(strings.concat(td, "/", srcbase),
|
||||||
|
testenv.readfile(strings.concat(testenv.repo(), "/lib/", pkg,
|
||||||
|
"/", pkg, ".ww")));
|
||||||
|
|
||||||
|
// #94 sep layout: drive the package file as the build target so its
|
||||||
|
// decls are the PRIMARY module of the resolved ROOT unit. -S stops
|
||||||
|
// after the compiler outputs; the unit is the only artifact consumed
|
||||||
|
// here. The build's own exit code is deliberately not asserted — the
|
||||||
|
// resolved-unit check below is the gate (carrier-faithful).
|
||||||
|
let av: []str = [];
|
||||||
|
append(av, testenv.driver("ww"));
|
||||||
|
append(av, "build");
|
||||||
|
append(av, "-S");
|
||||||
|
append(av, "-I");
|
||||||
|
append(av, strings.concat(testenv.repo(), "/lib"));
|
||||||
|
append(av, "-o");
|
||||||
|
append(av, strings.concat(td, "/", pkg));
|
||||||
|
append(av, srcbase);
|
||||||
|
let co: testenv.commandout;
|
||||||
|
testenv.runcommand(td, td, "drv", av, lifetime(), &co);
|
||||||
|
|
||||||
|
let comb: str = strings.concat(td, "/", pkg,
|
||||||
|
".sepwork/__root.unit.ww");
|
||||||
|
if (!testenv.exists(comb)) { fail(pkg, "no resolved unit"); };
|
||||||
|
|
||||||
|
let cs: str = strings.concat(td, "/cs.wwi");
|
||||||
|
let ws: str = strings.concat(td, "/ww.wwi");
|
||||||
|
let cav: []str = [testenv.driver("w6c"), "-I", cs, comb];
|
||||||
|
if (!runok(td, "w6c", cav)) { fail(pkg, "w6c -I rejected"); };
|
||||||
|
let wav: []str = [testenv.driver("w6c_ww"), "-I", ws, comb];
|
||||||
|
if (!runok(td, "w6c_ww", wav)) { fail(pkg, "w6c_ww -I rejected"); };
|
||||||
|
|
||||||
|
if (!testenv.same(testenv.readfile(cs), testenv.readfile(ws))) {
|
||||||
|
fail(pkg, "cs.wwi != ww.wwi (byte-id broken on the .wwi substrate)");
|
||||||
|
};
|
||||||
|
|
||||||
|
// re-parse: the emitted `.wwi` must be valid ww prototype source.
|
||||||
|
let dav: []str = [testenv.driver("wwdump"), "-a", cs];
|
||||||
|
if (!runok(td, "wwdump", dav)) {
|
||||||
|
fail(pkg, "emitted .wwi does not re-parse (wwdump -a)");
|
||||||
|
};
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn m2ascii() void = { m2positive("ascii"); };
|
||||||
|
@test fn m2strings() void = { m2positive("strings"); };
|
||||||
|
@test fn m2getopt() void = { m2positive("getopt"); };
|
||||||
|
|
||||||
|
// synth: decl-kinds + type-AST nodes the lib packages do not cover —
|
||||||
|
// `export def` (const-expr unparser, folded 10+2*3, unary -7), `export
|
||||||
|
// let` global, `[N]T` array (decl + array-dim ident-ref), fn-ptr type,
|
||||||
|
// `!T`, tuple return, storage-less enum, and the #47 @symbol
|
||||||
|
// round-trip. `chan T` omitted: the wwstage parser does not yet accept
|
||||||
|
// it in a return position (pre-existing, unrelated to M2).
|
||||||
|
@test fn m2synth() void = {
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
let src: str = strings.concat(
|
||||||
|
"package synth;\n",
|
||||||
|
"export type color = enum { RED, GREEN = 5, BLUE };\n",
|
||||||
|
"export def LIMIT: i32 = 10 + 2 * 3;\n",
|
||||||
|
"export def NAME: str = \"hi\\n\";\n",
|
||||||
|
"export def FLAG: bool = true;\n",
|
||||||
|
"export def NEG: i32 = -7;\n",
|
||||||
|
"export let counter: i32;\n",
|
||||||
|
"export let grid: [4]i32;\n",
|
||||||
|
"export fn apply(f: fn(x: i32) i32, n: i32) i32;\n",
|
||||||
|
"export fn risky() !i32;\n",
|
||||||
|
"export fn matrix() [LIMIT]u8;\n",
|
||||||
|
"export fn pair() (i32, i32);\n",
|
||||||
|
"export fn opt(p: *color, b: []u8) (i32 | void);\n",
|
||||||
|
"@symbol(\"rt_ext\") export fn ext(n: i32) i32;\n");
|
||||||
|
let p: str = strings.concat(td, "/synth.ww");
|
||||||
|
testenv.writefile(p, src);
|
||||||
|
let cs: str = strings.concat(td, "/cs.wwi");
|
||||||
|
let ws: str = strings.concat(td, "/ww.wwi");
|
||||||
|
// self-contained package (no imports) -> its own primary unit, so
|
||||||
|
// w6c -I runs directly on the source (no driver-combined step).
|
||||||
|
let cav: []str = [testenv.driver("w6c"), "-I", cs, p];
|
||||||
|
if (!runok(td, "w6c", cav)) { fail("synth", "w6c -I rejected"); };
|
||||||
|
let wav: []str = [testenv.driver("w6c_ww"), "-I", ws, p];
|
||||||
|
if (!runok(td, "w6c_ww", wav)) { fail("synth", "w6c_ww -I rejected"); };
|
||||||
|
if (!testenv.same(testenv.readfile(cs), testenv.readfile(ws))) {
|
||||||
|
fail("synth",
|
||||||
|
"cs.wwi != ww.wwi (const-expr / decl-kind unparse diverges)");
|
||||||
|
};
|
||||||
|
// #47: the @symbol attribute must survive the round-trip verbatim.
|
||||||
|
if (!testenv.has(testenv.readfile(cs),
|
||||||
|
"@symbol(\"rt_ext\") export fn ext")) {
|
||||||
|
fail("synth", ".wwi dropped @symbol (wrong link symbol under sep)");
|
||||||
|
};
|
||||||
|
let dav: []str = [testenv.driver("wwdump"), "-a", cs];
|
||||||
|
if (!runok(td, "wwdump", dav)) {
|
||||||
|
fail("synth", "emitted .wwi does not re-parse (wwdump -a)");
|
||||||
|
};
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
|
|
||||||
|
// #48 gate: the lib/types limit constants are `export def`s. Produce
|
||||||
|
// types.wwi on both stages directly from the real lib source, assert
|
||||||
|
// byte-id + re-parse + that the exported limits actually appear (a
|
||||||
|
// non-exported def is omitted, so this proves the export reached the
|
||||||
|
// interface a cross-package sep-compile consumer reads).
|
||||||
|
@test fn m2types() void = {
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
let src: str = strings.concat(testenv.repo(), "/lib/types/types.ww");
|
||||||
|
let cs: str = strings.concat(td, "/cs.wwi");
|
||||||
|
let ws: str = strings.concat(td, "/ww.wwi");
|
||||||
|
let cav: []str = [testenv.driver("w6c"), "-I", cs, src];
|
||||||
|
if (!runok(td, "w6c", cav)) { fail("types", "w6c -I rejected"); };
|
||||||
|
let wav: []str = [testenv.driver("w6c_ww"), "-I", ws, src];
|
||||||
|
if (!runok(td, "w6c_ww", wav)) { fail("types", "w6c_ww -I rejected"); };
|
||||||
|
if (!testenv.same(testenv.readfile(cs), testenv.readfile(ws))) {
|
||||||
|
fail("types", "cs.wwi != ww.wwi");
|
||||||
|
};
|
||||||
|
let body: str = testenv.readfile(cs);
|
||||||
|
if (!testenv.has(body, "export def I32_MAX")
|
||||||
|
|| !testenv.has(body, "export def RUNE_MAX")) {
|
||||||
|
fail("types", "limit consts not exported into the .wwi");
|
||||||
|
};
|
||||||
|
let dav: []str = [testenv.driver("wwdump"), "-a", cs];
|
||||||
|
if (!runok(td, "wwdump", dav)) {
|
||||||
|
fail("types", "emitted .wwi does not re-parse (wwdump -a)");
|
||||||
|
};
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
|
|
||||||
|
// negative: an exported fn naming a private nominal must be rejected
|
||||||
|
// identically (exit + diagnostic) by BOTH stages.
|
||||||
|
@test fn m2negative() void = {
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
let src: str = strings.concat(
|
||||||
|
"package leaktest;\n",
|
||||||
|
"type secret = struct { x: i32 };\n",
|
||||||
|
"export fn leaks(s: secret) i32 = { return s.x; };\n",
|
||||||
|
"export fn clean(a: i32) i32 = { return a; };\n");
|
||||||
|
testenv.writefile(strings.concat(td, "/leak.ww"), src);
|
||||||
|
|
||||||
|
// relative leak.ww under td so both stages report the bare
|
||||||
|
// `leak.ww:L:C:` prefix.
|
||||||
|
let cav: []str = [testenv.driver("w6c"), "-I", "out.wwi", "leak.ww"];
|
||||||
|
let cco: testenv.commandout;
|
||||||
|
testenv.runcommand(td, td, "cs", cav, lifetime(), &cco);
|
||||||
|
let wav: []str = [testenv.driver("w6c_ww"), "-I", "out.wwi", "leak.ww"];
|
||||||
|
let wco: testenv.commandout;
|
||||||
|
testenv.runcommand(td, td, "ws", wav, lifetime(), &wco);
|
||||||
|
|
||||||
|
let csok: bool = cco.termination == exec.termination.EXIT
|
||||||
|
&& cco.code == 0;
|
||||||
|
let wsok: bool = wco.termination == exec.termination.EXIT
|
||||||
|
&& wco.code == 0;
|
||||||
|
if (csok || wsok) {
|
||||||
|
fail("negative", "check_exported_type is vacuous (a stage accepted the private-type leak)");
|
||||||
|
};
|
||||||
|
if (!testenv.same(cco.stderr, wco.stderr)) {
|
||||||
|
fail("negative", "reject diagnostics differ across stages (rule-10)");
|
||||||
|
};
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- wwileaf (#11) -----------------------------------------------------
|
||||||
|
|
||||||
|
fn leafrow(tag: str, path: str, leaf: str, body: str, want: str) void = {
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
let prod: str = strings.concat(td, "/", tag, ".prod.ww");
|
||||||
|
let cswwi: str = strings.concat(td, "/", tag, ".cs.wwi");
|
||||||
|
let wwwwi: str = strings.concat(td, "/", tag, ".ww.wwi");
|
||||||
|
let css: str = strings.concat(td, "/", tag, ".cs.s");
|
||||||
|
let wws: str = strings.concat(td, "/", tag, ".ww.s");
|
||||||
|
let root: str = strings.concat(td, "/", tag, ".root.ww");
|
||||||
|
let rcss: str = strings.concat(td, "/", tag, ".rcs.s");
|
||||||
|
let rwws: str = strings.concat(td, "/", tag, ".rww.s");
|
||||||
|
|
||||||
|
// the producer unit — exactly the driver's sep_emit_body shape.
|
||||||
|
testenv.writefile(prod, strings.concat(
|
||||||
|
"//ww:module-reset ", path, "\n",
|
||||||
|
"package ", leaf, ";\n",
|
||||||
|
body));
|
||||||
|
|
||||||
|
// Leg a — both stages emit the dep `.wwi`; the `package` leaf is
|
||||||
|
// the module's real leaf, not the default "main".
|
||||||
|
let cav: []str = [testenv.driver("w6c"), "-c", "-I", cswwi,
|
||||||
|
"-o", css, prod];
|
||||||
|
if (!runok(td, "csprod", cav)) { fail(tag, "w6c producer errored"); };
|
||||||
|
let wav: []str = [testenv.driver("w6c_ww"), "-c", "-I", wwwwi,
|
||||||
|
"-o", wws, prod];
|
||||||
|
if (!runok(td, "wsprod", wav)) { fail(tag, "w6c_ww producer errored"); };
|
||||||
|
let wantline: str = strings.concat("package ", want, ";");
|
||||||
|
if (!testenv.same(firstline(cswwi), wantline)) {
|
||||||
|
fail(tag, ".wwi package line is not the real leaf (BUG-C)");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Leg b — the dep `.wwi` is byte-identical across stages (rule 10).
|
||||||
|
if (!testenv.same(testenv.readfile(cswwi), testenv.readfile(wwwwi))) {
|
||||||
|
fail(tag, "w6c vs w6c_ww .wwi differ (rule 10)");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Leg c — a root importing the dep RESOLVES on both stages
|
||||||
|
// (pre-fix: "package main does not match import path" REJECT). The
|
||||||
|
// dep `.wwi` is prepended under //ww:module <path>, exactly as the
|
||||||
|
// driver composes an importer unit.
|
||||||
|
testenv.writefile(root, strings.concat(
|
||||||
|
"//ww:module ", path, "\n",
|
||||||
|
testenv.readfile(cswwi), "\n",
|
||||||
|
"//ww:module-reset\n",
|
||||||
|
"package main;\n",
|
||||||
|
"import ", path, ";\n",
|
||||||
|
"export fn main() i32 = { return 42; };\n"));
|
||||||
|
let rcav: []str = [testenv.driver("w6c"), "-c", "-o", rcss, root];
|
||||||
|
if (!runok(td, "csroot", rcav)) {
|
||||||
|
fail(tag, "w6c rejected the import (BUG-C)");
|
||||||
|
};
|
||||||
|
let rwav: []str = [testenv.driver("w6c_ww"), "-c", "-o", rwws, root];
|
||||||
|
if (!runok(td, "wsroot", rwav)) {
|
||||||
|
fail(tag, "w6c_ww rejected the import (BUG-C)");
|
||||||
|
};
|
||||||
|
if (!testenv.same(testenv.readfile(rcss), testenv.readfile(rwws))) {
|
||||||
|
fail(tag, "importer .s differ cs vs ww (rule 10)");
|
||||||
|
};
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Each shape lacks any module-tagged decl, so wwi_emit's decl-scan
|
||||||
|
// misses and the leaf must come from the parse-stamped N_FILE identity.
|
||||||
|
@test fn wwileaf_empty() void = {
|
||||||
|
leafrow("empty", "emptymod", "emptymod", "", "emptymod");
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn wwileaf_comment() void = {
|
||||||
|
leafrow("comment", "cmod", "cmod", "// only a comment\n", "cmod");
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn wwileaf_nested() void = {
|
||||||
|
leafrow("nested", "a.b.c", "c", "", "c");
|
||||||
|
};
|
||||||
@@ -1,429 +0,0 @@
|
|||||||
/*
|
|
||||||
* 989_m2wwi_run — M2 `.wwi` export-data producer gate (task #22 arc).
|
|
||||||
* Both stages (rule-10): since the sep flip the `w6c -I <out.wwi>`
|
|
||||||
* producer IS the live import path, and `.wwi` is a cross-stage
|
|
||||||
* byte-id substrate this gate pins directly.
|
|
||||||
*
|
|
||||||
* POSITIVE gate — for each package (ascii/strings/getopt, drew2-audited
|
|
||||||
* leak-free), drive the target as the PRIMARY module of a driver-combined
|
|
||||||
* unit, then:
|
|
||||||
* 1. w6c -I cs.wwi and w6c_ww -I ww.wwi both succeed,
|
|
||||||
* 2. cs.wwi == ww.wwi byte-for-byte (the new substrate's determinism),
|
|
||||||
* 3. cs.wwi RE-PARSES under the existing parser (wwdump -a exits 0) —
|
|
||||||
* proves the producer emits genuinely re-parseable ww prototype
|
|
||||||
* source, de-risking the M3 consumer.
|
|
||||||
* getopt is the recursion stressor (enum→struct→struct, ptr-to-nominal,
|
|
||||||
* tagged-union return, qualified pkg.Name). A synth fixture then covers the
|
|
||||||
* decl-kinds + type-nodes no lib package reaches: `def` (const-expr unparser
|
|
||||||
* + fold parity), `let` global, `[N]T` array, `fn(..)R` fn-ptr, `!T`, tuple,
|
|
||||||
* storage-less enum.
|
|
||||||
*
|
|
||||||
* NEGATIVE gate — a fixture whose exported fn names a PRIVATE (non-
|
|
||||||
* exported) nominal must be LOUD-REJECTED by check_exported_type,
|
|
||||||
* identically on BOTH stages (same non-zero exit, byte-identical
|
|
||||||
* diagnostic). Without it a vacuous no-op check would pass the positive
|
|
||||||
* gate silently.
|
|
||||||
*
|
|
||||||
* Compile/produce/cmp only — no driver run, everything under a getpid-keyed
|
|
||||||
* /tmp directory with no source-tree writes. 9xx is full; shares the 989 prefix per the 989_lib_byteid
|
|
||||||
* precedent (the `short` name keys the binary).
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.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
|
|
||||||
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
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* one positive package: build the target as primary, produce the `.wwi`
|
|
||||||
* on both stages, require byte-id + re-parse. Returns 0 on pass. */
|
|
||||||
static int
|
|
||||||
positive(const char *bin, const char *cwd, const char *pkg, int idx)
|
|
||||||
{
|
|
||||||
char td[64], cmd[4096], src[1024];
|
|
||||||
int rc = -1;
|
|
||||||
|
|
||||||
snprintf(td, sizeof td, "/tmp/wwm2_%d_%d", getpid(), idx);
|
|
||||||
if (mkdir(td, 0755) != 0) {
|
|
||||||
perror(td);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(src, sizeof src, "%s/lib/%s/%s.ww", cwd, pkg, pkg);
|
|
||||||
snprintf(cmd, sizeof cmd, "cp %s %s/%s.ww", src, td, pkg);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: %s — cp\n", pkg);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #94 sep layout: drive the package file as the build target →
|
|
||||||
* its decls are the PRIMARY module of the resolved ROOT unit, which the
|
|
||||||
* producer's primary filter narrows to exactly this package's
|
|
||||||
* interface. The root unit's amalgamated source is composed at
|
|
||||||
* <stem>.sepwork/__root.unit.ww before codegen. `-S` stops after the
|
|
||||||
* compiler outputs; the unit is the only artifact consumed here. All
|
|
||||||
* generated files stay under td. */
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"cd %s && timeout 180 %s/ww build -S "
|
|
||||||
"-I %s/lib -o %s/%s %s.ww >/dev/null 2>&1",
|
|
||||||
td, bin, cwd, td, pkg, pkg);
|
|
||||||
runwait(cmd);
|
|
||||||
|
|
||||||
char comb[1100], cs[1100], ws[1100];
|
|
||||||
snprintf(comb, sizeof comb, "%s/%s.sepwork/__root.unit.ww", td, pkg);
|
|
||||||
snprintf(cs, sizeof cs, "%s/cs.wwi", td);
|
|
||||||
snprintf(ws, sizeof ws, "%s/ww.wwi", td);
|
|
||||||
if (access(comb, 0) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: %s — no resolved unit\n", pkg);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"timeout 180 %s/w6c -I %s %s >/dev/null 2>&1", bin, cs, comb);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: %s — w6c -I rejected\n", pkg);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"timeout 180 %s/w6c_ww -I %s %s >/dev/null 2>&1", bin, ws, comb);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: %s — w6c_ww -I rejected\n", pkg);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (files_eq(cs, ws) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: %s — cs.wwi != ww.wwi (byte-id "
|
|
||||||
"broken on the .wwi substrate)\n", pkg);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* re-parse: the emitted `.wwi` must be valid ww prototype source. */
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"timeout 180 %s/wwdump -a %s >/dev/null 2>&1", bin, cs);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: %s — emitted .wwi does not "
|
|
||||||
"re-parse (wwdump -a)\n", pkg);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
rc = 0;
|
|
||||||
out:
|
|
||||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi: cleanup rm -rf %s failed\n", td);
|
|
||||||
if (rc == 0) rc = -1;
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* negative gate: an exported fn naming a private nominal must be rejected
|
|
||||||
* identically (exit + diagnostic) by BOTH stages. Returns 0 on pass. */
|
|
||||||
static const char *leak_src =
|
|
||||||
"package leaktest;\n"
|
|
||||||
"type secret = struct { x: i32 };\n"
|
|
||||||
"export fn leaks(s: secret) i32 = { return s.x; };\n"
|
|
||||||
"export fn clean(a: i32) i32 = { return a; };\n";
|
|
||||||
|
|
||||||
static int
|
|
||||||
negative(const char *bin)
|
|
||||||
{
|
|
||||||
char td[64], cmd[4096], p[512];
|
|
||||||
int rc = -1;
|
|
||||||
|
|
||||||
snprintf(td, sizeof td, "/tmp/wwm2neg_%d", getpid());
|
|
||||||
if (mkdir(td, 0755) != 0) {
|
|
||||||
perror(td);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(p, sizeof p, "%s/leak.ww", td);
|
|
||||||
FILE *f = fopen(p, "wb");
|
|
||||||
if (!f) goto out;
|
|
||||||
fputs(leak_src, f);
|
|
||||||
fclose(f);
|
|
||||||
|
|
||||||
char cse[1100], wwe[1100];
|
|
||||||
snprintf(cse, sizeof cse, "%s/cs.err", td);
|
|
||||||
snprintf(wwe, sizeof wwe, "%s/ww.err", td);
|
|
||||||
|
|
||||||
/* cd into td so both stages report the bare `leak.ww:L:C:` prefix. */
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"cd %s && timeout 180 %s/w6c -I out.wwi leak.ww >/dev/null 2>cs.err",
|
|
||||||
td, bin);
|
|
||||||
int crc = runwait(cmd);
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"cd %s && timeout 180 %s/w6c_ww -I out.wwi leak.ww >/dev/null 2>ww.err",
|
|
||||||
td, bin);
|
|
||||||
int wrc = runwait(cmd);
|
|
||||||
|
|
||||||
if (crc == 0 || wrc == 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: negative — check_exported_type is "
|
|
||||||
"vacuous (cstage exit=%d wwstage exit=%d; both must reject "
|
|
||||||
"the private-type leak)\n", crc, wrc);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (files_eq(cse, wwe) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: negative — reject diagnostics "
|
|
||||||
"differ across stages (rule-10)\n");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
rc = 0;
|
|
||||||
out:
|
|
||||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi: cleanup rm -rf %s failed\n", td);
|
|
||||||
if (rc == 0) rc = -1;
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* synth gate: a single self-contained package exercising the decl-kinds +
|
|
||||||
* type-AST nodes the lib packages above do NOT cover — `export def` (the
|
|
||||||
* const-expr unparser, incl a folded `10+2*3` and a unary `-7`), `export
|
|
||||||
* let` (global), `[N]T` array (decl + array-dim ident-ref), an `fn(...) R`
|
|
||||||
* fn-pointer type, an `!T` error type, a `(a, b)` tuple return, and a
|
|
||||||
* storage-less `enum {...}`. Asserts both stages produce a byte-identical,
|
|
||||||
* re-parseable `.wwi` (the const-expr fold + tokname spelling must agree
|
|
||||||
* cross-stage — paths the lib gate never reaches). `chan T` is omitted: the
|
|
||||||
* wwstage parser does not yet accept it in a return position (pre-existing,
|
|
||||||
* unrelated to M2), so the producer's N_TCHAN arm is unreachable there.
|
|
||||||
* Returns 0 on pass. */
|
|
||||||
static const char *synth_src =
|
|
||||||
"package synth;\n"
|
|
||||||
"export type color = enum { RED, GREEN = 5, BLUE };\n"
|
|
||||||
"export def LIMIT: i32 = 10 + 2 * 3;\n"
|
|
||||||
"export def NAME: str = \"hi\\n\";\n"
|
|
||||||
"export def FLAG: bool = true;\n"
|
|
||||||
"export def NEG: i32 = -7;\n"
|
|
||||||
"export let counter: i32;\n"
|
|
||||||
"export let grid: [4]i32;\n"
|
|
||||||
"export fn apply(f: fn(x: i32) i32, n: i32) i32;\n"
|
|
||||||
"export fn risky() !i32;\n"
|
|
||||||
"export fn matrix() [LIMIT]u8;\n"
|
|
||||||
"export fn pair() (i32, i32);\n"
|
|
||||||
"export fn opt(p: *color, b: []u8) (i32 | void);\n"
|
|
||||||
/* #47: a codegen/link-relevant attribute (@symbol — the FFI link-
|
|
||||||
* symbol override) MUST round-trip into the `.wwi`; dropping it makes
|
|
||||||
* sep-compile emit `CALL ext` for a `@symbol("rt_ext")` fn. */
|
|
||||||
"@symbol(\"rt_ext\") export fn ext(n: i32) i32;\n";
|
|
||||||
|
|
||||||
static int
|
|
||||||
synth(const char *bin)
|
|
||||||
{
|
|
||||||
char td[64], cmd[4096], p[512];
|
|
||||||
int rc = -1;
|
|
||||||
|
|
||||||
snprintf(td, sizeof td, "/tmp/wwm2syn_%d", getpid());
|
|
||||||
if (mkdir(td, 0755) != 0) {
|
|
||||||
perror(td);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(p, sizeof p, "%s/synth.ww", td);
|
|
||||||
FILE *f = fopen(p, "wb");
|
|
||||||
if (!f) goto out;
|
|
||||||
fputs(synth_src, f);
|
|
||||||
fclose(f);
|
|
||||||
|
|
||||||
char cs[1100], ws[1100];
|
|
||||||
snprintf(cs, sizeof cs, "%s/cs.wwi", td);
|
|
||||||
snprintf(ws, sizeof ws, "%s/ww.wwi", td);
|
|
||||||
|
|
||||||
/* self-contained package (no imports) → it is its own primary unit,
|
|
||||||
* so w6c -I runs directly on the source (no driver-combined step). */
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"timeout 180 %s/w6c -I %s %s/synth.ww >/dev/null 2>&1", bin, cs, td);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: synth — w6c -I rejected\n");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"timeout 180 %s/w6c_ww -I %s %s/synth.ww >/dev/null 2>&1", bin, ws, td);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: synth — w6c_ww -I rejected\n");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (files_eq(cs, ws) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: synth — cs.wwi != ww.wwi (const-expr "
|
|
||||||
"/ decl-kind unparse diverges across stages)\n");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
/* #47: the @symbol attribute must survive the round-trip verbatim. */
|
|
||||||
if (file_contains(cs, "@symbol(\"rt_ext\") export fn ext") != 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: synth — .wwi dropped @symbol "
|
|
||||||
"(sep-compile would emit the wrong link symbol)\n");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"timeout 180 %s/wwdump -a %s >/dev/null 2>&1", bin, cs);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: synth — emitted .wwi does not "
|
|
||||||
"re-parse (wwdump -a)\n");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
rc = 0;
|
|
||||||
out:
|
|
||||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi: cleanup rm -rf %s failed\n", td);
|
|
||||||
if (rc == 0) rc = -1;
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #48 gate: the lib/types limit constants are `export def`s (Hare exports
|
|
||||||
* types::I32_MAX &c). Produce types.wwi on both stages directly from the
|
|
||||||
* real lib source (no imports → its own primary unit), assert byte-id +
|
|
||||||
* re-parse + that the exported limits actually appear in the .wwi (a non-
|
|
||||||
* exported def is omitted, so this proves the export reached the interface
|
|
||||||
* a cross-package sep-compile consumer reads). Returns 0 on pass. */
|
|
||||||
static int
|
|
||||||
typesexport(const char *bin, const char *cwd)
|
|
||||||
{
|
|
||||||
char td[64], cmd[4096], src[1024];
|
|
||||||
int rc = -1;
|
|
||||||
|
|
||||||
snprintf(td, sizeof td, "/tmp/wwm2typ_%d", getpid());
|
|
||||||
if (mkdir(td, 0755) != 0) {
|
|
||||||
perror(td);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(src, sizeof src, "%s/lib/types/types.ww", cwd);
|
|
||||||
char cs[1100], ws[1100];
|
|
||||||
snprintf(cs, sizeof cs, "%s/cs.wwi", td);
|
|
||||||
snprintf(ws, sizeof ws, "%s/ww.wwi", td);
|
|
||||||
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"timeout 180 %s/w6c -I %s %s >/dev/null 2>&1", bin, cs, src);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: types — w6c -I rejected\n");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"timeout 180 %s/w6c_ww -I %s %s >/dev/null 2>&1", bin, ws, src);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: types — w6c_ww -I rejected\n");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (files_eq(cs, ws) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: types — cs.wwi != ww.wwi\n");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (file_contains(cs, "export def I32_MAX") != 0
|
|
||||||
|| file_contains(cs, "export def RUNE_MAX") != 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: types — limit consts not exported "
|
|
||||||
"into the .wwi (cross-package sep-compile would not see "
|
|
||||||
"types.I32_MAX)\n");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"timeout 180 %s/wwdump -a %s >/dev/null 2>&1", bin, cs);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi FAIL: types — emitted .wwi does not "
|
|
||||||
"re-parse (wwdump -a)\n");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
rc = 0;
|
|
||||||
out:
|
|
||||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "m2wwi: cleanup rm -rf %s failed\n", td);
|
|
||||||
if (rc == 0) rc = -1;
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(void)
|
|
||||||
{
|
|
||||||
const char *bin = absbin();
|
|
||||||
if (!bin) return 1;
|
|
||||||
char cwd[1024];
|
|
||||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
|
||||||
|
|
||||||
const char *pkgs[] = { "ascii", "strings", "getopt", NULL };
|
|
||||||
int fail = 0, npos = 0;
|
|
||||||
for (int i = 0; pkgs[i]; i++) {
|
|
||||||
if (positive(bin, cwd, pkgs[i], i) != 0) fail++;
|
|
||||||
else npos++;
|
|
||||||
}
|
|
||||||
if (synth(bin) != 0) fail++;
|
|
||||||
else npos++;
|
|
||||||
if (typesexport(bin, cwd) != 0) fail++;
|
|
||||||
else npos++;
|
|
||||||
if (negative(bin) != 0) fail++;
|
|
||||||
|
|
||||||
if (fail) {
|
|
||||||
fprintf(stderr, "m2wwi: %d check(s) failed\n", fail);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("m2wwi: %d package(s) byte-identical + re-parse; negative "
|
|
||||||
"gate rejects (both stages)\n", npos);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
/*
|
|
||||||
* 989_wwileaf_run — BUG-C (#11) regression pin: a decl-less / export-less
|
|
||||||
* primary module's `.wwi` `package` line must carry the module's real leaf,
|
|
||||||
* NOT the literal default "main".
|
|
||||||
*
|
|
||||||
* THE BUG: wwi_emit derived the `package` leaf by scanning for the first
|
|
||||||
* PRIMARY decl bearing a module tag. A fully empty (or comment-only, or
|
|
||||||
* export-less) body has NO such decl, so the scan fell through and the leaf
|
|
||||||
* stayed the seed default "main". The dep's `.wwi` then read `package main;`
|
|
||||||
* → when its importer composed the dep under `//ww:module <path>`, the
|
|
||||||
* in-`.wwi` `package main` clause was validated against the import path and
|
|
||||||
* REJECTED: "package main does not match import path <leaf>"
|
|
||||||
* (cmd/wcc/parse.c, both stages). A decl-less module was simply unimportable.
|
|
||||||
*
|
|
||||||
* THE FIX (cmd/w6c/wwi.c + selfhost/cmd/wcc/wwi.ww + the two parse sites):
|
|
||||||
* parse stamps the primary module identity on the N_FILE node (module/nmod,
|
|
||||||
* "only if empty", never on the imported TK_MODPATH boundary nor the bare
|
|
||||||
* root reset); wwi_emit falls back to that identity's leaf when the decl-scan
|
|
||||||
* finds nothing (`!found` — NOT pkg=="main", so a body whose first tagged
|
|
||||||
* decl legitimately leafs to "main" is unaffected and both stages decide
|
|
||||||
* identically). A real root `package main` arrives via a bare reset, leaves
|
|
||||||
* the stamp empty, and stays "main".
|
|
||||||
*
|
|
||||||
* Table-driven over the three decl-less shapes that all route the fallback:
|
|
||||||
* an empty body, a comment-only body, and a NESTED dotted path (a.b.c → leaf
|
|
||||||
* c, exercising the leaf-slice). Each row drives the REAL producer→importer
|
|
||||||
* flow on BOTH stages:
|
|
||||||
* (a) the dep `.wwi` `package` line == the real leaf (pre-fix: "main");
|
|
||||||
* (b) the dep `.wwi` is byte-identical w6c vs w6c_ww (rule 10);
|
|
||||||
* (c) a root that `import`s the dep RESOLVES on both stages (pre-fix:
|
|
||||||
* hard REJECT) and the two importer `.s` are byte-identical (rule 10).
|
|
||||||
*
|
|
||||||
* The composed units are fed straight to w6c / w6c_ww (not `ww build`): the
|
|
||||||
* producer-unit shape `//ww:module-reset <path>` + body is exactly what the
|
|
||||||
* driver's sep_emit_body emits, so this pins the same path without the
|
|
||||||
* driver's filesystem layout. Non-vacuity: neutering the wwi_emit fallback
|
|
||||||
* reddens row (a) ("package main;") and row (c) (the import REJECT).
|
|
||||||
*
|
|
||||||
* Light wwstage-driver test (CLAUDE.md rule 14): every intermediate lands in
|
|
||||||
* a private mkdtemp dir, rm -rf'd at exit — no /tmp scratch leak (task #8).
|
|
||||||
* 989 prefix per the sep-gate precedent; twin of 989_modresetadj_run (#9).
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
write_file(const char *path, const char *content)
|
|
||||||
{
|
|
||||||
FILE *f = fopen(path, "wb");
|
|
||||||
if (!f) return -1;
|
|
||||||
fputs(content, f);
|
|
||||||
fclose(f);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* First physical line of `path` (newline stripped) into `out`. */
|
|
||||||
static int
|
|
||||||
first_line(const char *path, char *out, size_t outsz)
|
|
||||||
{
|
|
||||||
FILE *f = fopen(path, "rb");
|
|
||||||
if (!f) return -1;
|
|
||||||
if (fgets(out, (int)outsz, f) == NULL) { fclose(f); out[0] = '\0'; }
|
|
||||||
fclose(f);
|
|
||||||
size_t n = strlen(out);
|
|
||||||
if (n && out[n - 1] == '\n') out[n - 1] = '\0';
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct row {
|
|
||||||
const char *tag; /* scratch-file stem */
|
|
||||||
const char *path; /* dotted module path on the //ww:module-reset */
|
|
||||||
const char *leaf; /* the in-file `package <leaf>;` clause */
|
|
||||||
const char *body; /* body after the clause (zero module-tagged decls) */
|
|
||||||
const char *want; /* expected emitted `package <want>;` leaf */
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Each shape lacks any module-tagged decl, so wwi_emit's decl-scan misses
|
|
||||||
* and the leaf must come from the parse-stamped N_FILE identity. */
|
|
||||||
static const struct row rows[] = {
|
|
||||||
{ "empty", "emptymod", "emptymod", "", "emptymod" },
|
|
||||||
{ "comment", "cmod", "cmod", "// only a comment\n", "cmod" },
|
|
||||||
{ "nested", "a.b.c", "c", "", "c" },
|
|
||||||
};
|
|
||||||
#define NROW ((int)(sizeof rows / sizeof rows[0]))
|
|
||||||
#define RET 42
|
|
||||||
|
|
||||||
static int
|
|
||||||
run_row(const char *bin, const char *dir, const struct row *r)
|
|
||||||
{
|
|
||||||
char prod[1024], cswwi[1024], wwwwi[1024], css[1024], wws[1024];
|
|
||||||
char root[1024], rcss[1024], rwws[1024], unit[8192], cmd[16384];
|
|
||||||
|
|
||||||
snprintf(prod, sizeof prod, "%s/%s.prod.ww", dir, r->tag);
|
|
||||||
snprintf(cswwi, sizeof cswwi, "%s/%s.cs.wwi", dir, r->tag);
|
|
||||||
snprintf(wwwwi, sizeof wwwwi, "%s/%s.ww.wwi", dir, r->tag);
|
|
||||||
snprintf(css, sizeof css, "%s/%s.cs.s", dir, r->tag);
|
|
||||||
snprintf(wws, sizeof wws, "%s/%s.ww.s", dir, r->tag);
|
|
||||||
snprintf(root, sizeof root, "%s/%s.root.ww", dir, r->tag);
|
|
||||||
snprintf(rcss, sizeof rcss, "%s/%s.rcs.s", dir, r->tag);
|
|
||||||
snprintf(rwws, sizeof rwws, "%s/%s.rww.s", dir, r->tag);
|
|
||||||
|
|
||||||
/* the producer unit — exactly the driver's sep_emit_body shape. */
|
|
||||||
snprintf(unit, sizeof unit,
|
|
||||||
"//ww:module-reset %s\n"
|
|
||||||
"package %s;\n"
|
|
||||||
"%s",
|
|
||||||
r->path, r->leaf, r->body);
|
|
||||||
if (write_file(prod, unit) != 0) {
|
|
||||||
fprintf(stderr, "wwileaf[%s]: cannot write producer unit\n", r->tag);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Leg a — both stages emit the dep `.wwi`; the `package` leaf is the
|
|
||||||
* module's real leaf, not the default "main". */
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"%s/w6c -c -I %s -o %s %s 2>/dev/null", bin, cswwi, css, prod);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "wwileaf[%s] FAIL: w6c producer errored\n", r->tag);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"%s/w6c_ww -c -I %s -o %s %s 2>/dev/null", bin, wwwwi, wws, prod);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "wwileaf[%s] FAIL: w6c_ww producer errored\n", r->tag);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
char want[256], line[512];
|
|
||||||
snprintf(want, sizeof want, "package %s;", r->want);
|
|
||||||
if (first_line(cswwi, line, sizeof line) != 0 || strcmp(line, want) != 0) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"wwileaf[%s] FAIL: .wwi package line '%s' want '%s' (BUG-C)\n",
|
|
||||||
r->tag, line, want);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Leg b — the dep `.wwi` is byte-identical across stages (rule 10). */
|
|
||||||
if (files_eq(cswwi, wwwwi) != 0) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"wwileaf[%s] FAIL: w6c vs w6c_ww .wwi differ (rule 10)\n", r->tag);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Leg c — a root importing the dep RESOLVES on both stages (pre-fix:
|
|
||||||
* "package main does not match import path %s" REJECT). The dep `.wwi`
|
|
||||||
* is prepended under //ww:module <path>, exactly as the driver composes
|
|
||||||
* an importer unit. */
|
|
||||||
{
|
|
||||||
char *wwi = NULL;
|
|
||||||
size_t wlen = 0;
|
|
||||||
if (slurp(cswwi, &wwi, &wlen) < 0) {
|
|
||||||
fprintf(stderr, "wwileaf[%s]: cannot read dep .wwi\n", r->tag);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
snprintf(unit, sizeof unit,
|
|
||||||
"//ww:module %s\n"
|
|
||||||
"%s\n"
|
|
||||||
"//ww:module-reset\n"
|
|
||||||
"package main;\n"
|
|
||||||
"import %s;\n"
|
|
||||||
"export fn main() i32 = { return %d; };\n",
|
|
||||||
r->path, wwi, r->path, RET);
|
|
||||||
free(wwi);
|
|
||||||
}
|
|
||||||
if (write_file(root, unit) != 0) {
|
|
||||||
fprintf(stderr, "wwileaf[%s]: cannot write importer unit\n", r->tag);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"%s/w6c -c -o %s %s 2>/dev/null", bin, rcss, root);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"wwileaf[%s] FAIL: w6c rejected the import (BUG-C)\n", r->tag);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"%s/w6c_ww -c -o %s %s 2>/dev/null", bin, rwws, root);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"wwileaf[%s] FAIL: w6c_ww rejected the import (BUG-C)\n", r->tag);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (files_eq(rcss, rwws) != 0) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"wwileaf[%s] FAIL: importer .s differ cs vs ww (rule 10)\n",
|
|
||||||
r->tag);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(void)
|
|
||||||
{
|
|
||||||
const char *bin = absbin();
|
|
||||||
if (!bin) return 1;
|
|
||||||
|
|
||||||
char dir[] = "/tmp/wwileaf_XXXXXX";
|
|
||||||
if (mkdtemp(dir) == NULL) {
|
|
||||||
fprintf(stderr, "wwileaf: mkdtemp failed\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int fail = 0;
|
|
||||||
for (int i = 0; i < NROW; i++)
|
|
||||||
fail |= run_row(bin, dir, &rows[i]);
|
|
||||||
|
|
||||||
char rm[2048];
|
|
||||||
snprintf(rm, sizeof rm, "rm -rf %s", dir);
|
|
||||||
if (runwait(rm) != 0) {
|
|
||||||
fprintf(stderr, "wwileaf: cleanup rm -rf %s failed\n", dir);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fail) return 1;
|
|
||||||
printf("wwileaf: %d decl-less shapes — .wwi leaf derived, byte-id, "
|
|
||||||
"import resolves cs==ww (#11)\n", NROW);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user