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 ` 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 owner-only driver unit with separate direct exports, then // (1) w6c -c -I and // w6c_ww -c -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 owner units and // separate exports are fed straight to w6c / w6c_ww (not `ww build`): // the producer-unit shape `//ww:module-reset ` + 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; }; 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 work: str = strings.concat(td, "/", pkg, ".sepwork/"); let comb: str = strings.concat(work, "__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 deps: []str = []; if (testenv.same(pkg, "ascii")) { append(deps, "strings"); }; if (testenv.same(pkg, "strings")) { append(deps, "bytes"); append(deps, "encoding.utf8"); append(deps, "os"); append(deps, "types"); }; if (testenv.same(pkg, "getopt")) { append(deps, "encoding.utf8"); append(deps, "fmt"); append(deps, "io"); append(deps, "os"); append(deps, "strings"); }; if (deps.len == 0) { abort("unknown m2 package"); }; let cav: []str = [testenv.driver("w6c"), "-c"]; let di: i32 = 0; for (di < deps.len) { append(cav, "--import"); append(cav, deps[di]); append(cav, strings.concat(work, deps[di], ".wwi")); di += 1; }; append(cav, "-I"); append(cav, cs); append(cav, comb); if (!runok(td, "w6c", cav)) { fail(pkg, "w6c -c -I rejected"); }; let wav: []str = [testenv.driver("w6c_ww"), "-c"]; di = 0; for (di < deps.len) { append(wav, "--import"); append(wav, deps[di]); append(wav, strings.concat(work, deps[di], ".wwi")); di += 1; }; append(wav, "-I"); append(wav, ws); append(wav, comb); if (!runok(td, "w6c_ww", wav)) { fail(pkg, "w6c_ww -c -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", "def GREEN: i32 = 99;\n", "export type color = enum { RED, GREEN = 5, BLUE = GREEN + 1 };\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 let grouped: [(1 + 2) * 3]u8;\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)"); }; if (!testenv.has(testenv.readfile(cs), "export let grouped: [9]u8;") || !testenv.has(testenv.readfile(cs), "export fn matrix() [16]u8;")) { fail("synth", ".wwi did not canonicalize checked array dimensions"); }; if (!testenv.has(testenv.readfile(cs), "export type color = enum { RED, GREEN = 5, BLUE = (GREEN + 1) };") || testenv.has(testenv.readfile(cs), "def GREEN: i32 = 99")) { fail("synth", ".wwi confused an enum sibling with a package def"); }; // #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); }; @test fn m2qualified() void = { let td: str = testenv.fresh(); let src: str = strings.concat( "//ww:module a.dep\n", "package dep;\n", "export type Clash = struct { x: i32 };\n", "//ww:module-reset\n", "package root;\n", "import a.dep;\n", "type Clash = struct { y: i32 };\n", "export fn use(x: dep.Clash) i32;\n"); let p: str = strings.concat(td, "/qualified.ww"); testenv.writefile(p, src); let cs: str = strings.concat(td, "/cs.wwi"); let ws: str = strings.concat(td, "/ww.wwi"); let cav: []str = [testenv.driver("w6c"), "-I", cs, p]; if (!runok(td, "w6c", cav)) { fail("qualified", "w6c confused dep.Clash with private Clash"); }; let wav: []str = [testenv.driver("w6c_ww"), "-I", ws, p]; if (!runok(td, "w6c_ww", wav)) { fail("qualified", "w6c_ww confused dep.Clash with private Clash"); }; if (!testenv.same(testenv.readfile(cs), testenv.readfile(ws))) { fail("qualified", "cs.wwi != ww.wwi"); }; let body: str = testenv.readfile(cs); if (!testenv.has(body, "export fn use(x: dep.Clash) i32;")) { fail("qualified", ".wwi dropped the qualified signature"); }; if (!testenv.has(body, "//ww:module a.dep\n") || !testenv.has(body, "export type Clash = struct { x: i32 };")) { fail("qualified", ".wwi omitted the signature's origin-owned type fact"); }; let dav: []str = [testenv.driver("wwdump"), "-a", cs]; if (!runok(td, "wwdump", dav)) { fail("qualified", "emitted .wwi does not re-parse"); }; 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); }; 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 from a separate export on // both stages (pre-fix: "package main does not match import path" // REJECT). testenv.writefile(root, strings.concat( "//ww:module-reset\n", "package main;\n", "import ", path, ";\n", "export fn main() i32 = { return 42; };\n")); let rcav: []str = [testenv.driver("w6c"), "-c", "--import", path, cswwi, "-o", rcss, root]; if (!runok(td, "csroot", rcav)) { fail(tag, "w6c rejected the import (BUG-C)"); }; let rwav: []str = [testenv.driver("w6c_ww"), "-c", "--import", path, wwwwi, "-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"); };