Files
ww/test/byteid/wwi_test.ww
Hojun-Cho 634f7a2b3f 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.
2026-08-08 04:25:34 +09:00

305 lines
12 KiB
Plaintext

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");
};