431 lines
18 KiB
Plaintext
431 lines
18 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 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. PRIVATE CLOSURE:
|
|
// an exported fn naming a private nominal carries that nominal without
|
|
// `export`, remains byte-identical and re-parseable, and still rejects source
|
|
// qualification of the compiler-private name on both stages.
|
|
//
|
|
// wwileaf — BUG-C (#11) regression pin: a decl-less / export-less
|
|
// primary module's `.wwi` must preserve its independently parsed declaration,
|
|
// rather than substituting 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
|
|
// owner marker is the complete path and its 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 both importers diagnose the same file-local unused binding after
|
|
// obtaining that declared name from export data. The owner units and
|
|
// separate exports 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;
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
// 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);
|
|
};
|
|
|
|
// A public declaration may reach a private nominal as compiler export data,
|
|
// but that private spelling must not become source-importable.
|
|
@test fn m2privateclosure() 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);
|
|
|
|
let cav: []str = [testenv.driver("w6c"), "-I", "cs.wwi", "leak.ww"];
|
|
let wav: []str = [testenv.driver("w6c_ww"), "-I", "ww.wwi", "leak.ww"];
|
|
if (!runok(td, "cs", cav) || !runok(td, "ws", wav)) {
|
|
fail("private-closure", "a stage rejected reachable private type data");
|
|
};
|
|
let csbody: str = testenv.readfile(strings.concat(td, "/cs.wwi"));
|
|
let wsbody: str = testenv.readfile(strings.concat(td, "/ww.wwi"));
|
|
if (!testenv.same(csbody, wsbody)
|
|
|| !testenv.has(csbody, "type secret = struct { x: i32 };")
|
|
|| testenv.has(csbody, "export type secret")
|
|
|| !testenv.has(csbody, "export fn leaks(s: secret) i32;")) {
|
|
fail("private-closure", "private compiler fact bytes are wrong");
|
|
};
|
|
let dav: []str = [testenv.driver("wwdump"), "-a", "cs.wwi"];
|
|
if (!runok(td, "private-reparse", dav)) {
|
|
fail("private-closure", "self-contained export does not re-parse");
|
|
};
|
|
testenv.writefile(strings.concat(td, "/consumer.ww"), strings.concat(
|
|
"package consumer;\nimport leaktest;\n",
|
|
"fn forbidden(s: secret) void = {};\n"));
|
|
let ccav: []str = [testenv.driver("w6c"), "-c", "--import",
|
|
"leaktest", "cs.wwi", "-o", "cs.s", "consumer.ww"];
|
|
let wcav: []str = [testenv.driver("w6c_ww"), "-c", "--import",
|
|
"leaktest", "ww.wwi", "-o", "ww.s", "consumer.ww"];
|
|
let cco: testenv.commandout;
|
|
let wco: testenv.commandout;
|
|
testenv.runcommand(td, td, "private-cs", ccav, lifetime(), &cco);
|
|
testenv.runcommand(td, td, "private-ws", wcav, lifetime(), &wco);
|
|
if (cco.termination != exec.termination.EXIT || cco.code == 0
|
|
|| wco.termination != exec.termination.EXIT || wco.code == 0
|
|
|| !testenv.same(cco.stderr, wco.stderr)
|
|
|| !testenv.has(cco.stderr, "unknown type 'secret'")) {
|
|
fail("private-closure", "private nominal became source-visible");
|
|
};
|
|
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` declaration is
|
|
// the source declaration supplied independently of its canonical owner.
|
|
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 wanthead: str = strings.concat(strings.concat(strings.concat(
|
|
"//ww:module ", path), "\npackage "), strings.concat(want, ";\n"));
|
|
if (!strings.hasprefix(testenv.readfile(cswwi), wanthead)) {
|
|
fail(tag, ".wwi owner or declared package name is incomplete (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 empty dep obtains its default qualifier from
|
|
// the separate export on both stages. With no exported declaration to use,
|
|
// Go-style file scope requires the import itself to be rejected as unused;
|
|
// the diagnostic proves that the reader recovered the declared name rather
|
|
// than substituting the historical default `main` declaration.
|
|
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];
|
|
let rwav: []str = [testenv.driver("w6c_ww"), "-c", "--import", path,
|
|
wwwwi, "-o", rwws, root];
|
|
let rco: testenv.commandout;
|
|
let rwo: testenv.commandout;
|
|
testenv.runcommand(td, td, "csroot", rcav, lifetime(), &rco);
|
|
testenv.runcommand(td, td, "wsroot", rwav, lifetime(), &rwo);
|
|
let (prefix, suffix) = strings.rcut(path, ".");
|
|
let leaf: str = suffix;
|
|
if (leaf.len == 0) { leaf = path; };
|
|
let unused: str;
|
|
if (testenv.same(want, leaf)) {
|
|
unused = strings.concat(strings.concat("\"", path),
|
|
"\" imported and not used");
|
|
} else {
|
|
unused = strings.concat(strings.concat(strings.concat(
|
|
"\"", path), "\" imported as "), strings.concat(want,
|
|
" and not used"));
|
|
};
|
|
if (rco.termination != exec.termination.EXIT || rco.code == 0
|
|
|| rwo.termination != exec.termination.EXIT || rwo.code == 0
|
|
|| !testenv.same(rco.stderr, rwo.stderr)
|
|
|| !testenv.has(rco.stderr, unused)) {
|
|
fail(tag, "imported-name or unused-import behavior differs by stage");
|
|
};
|
|
testenv.clean(td);
|
|
};
|
|
|
|
fn resetfallbackrow() void = {
|
|
let td: str = testenv.fresh();
|
|
let src: str = strings.concat(td, "/reset.ww");
|
|
testenv.writefile(src, strings.concat(
|
|
"//ww:module-reset raw.owner\n",
|
|
"export fn value() i32 = { return 7; };\n"));
|
|
let cs: str = strings.concat(td, "/c.wwi");
|
|
let ws: str = strings.concat(td, "/w.wwi");
|
|
let cav: []str = [testenv.driver("w6c"), "-c", "-I", cs,
|
|
"-o", strings.concat(td, "/c.s"), src];
|
|
let wav: []str = [testenv.driver("w6c_ww"), "-c", "-I", ws,
|
|
"-o", strings.concat(td, "/w.s"), src];
|
|
if (!runok(td, "reset-c", cav) || !runok(td, "reset-w", wav)) {
|
|
fail("nested", "a package-less reset owner failed to export");
|
|
};
|
|
let body: str = testenv.readfile(cs);
|
|
if (!testenv.same(body, testenv.readfile(ws))
|
|
|| !testenv.has(body, "//ww:module raw.owner\npackage main;\n")
|
|
|| !testenv.has(body, "export fn value() i32;")) {
|
|
fail("nested", "reset-owner fallback lost its source section");
|
|
};
|
|
testenv.clean(td);
|
|
};
|
|
|
|
// Each shape lacks any module-tagged decl, so wwi_emit's decl-scan
|
|
// misses and the declaration must come from the parse-stamped N_FILE fact.
|
|
@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");
|
|
resetfallbackrow();
|
|
};
|