test: port the M1 mangle/usehint run observers to ww

test/xmod/m1_test.ww replaces 989_m1mangle_run.c and
989_m1usehint_run.c with every assertion preserved (5 tree rows +
2 usehint rows, both stages, plus the leaf_collision byte-id leg;
concat order strengthened from shell glob to byte-sorted listdir).
This commit is contained in:
2026-08-08 14:29:56 +09:00
parent 57675431e2
commit 3f0b7647f6
4 changed files with 255 additions and 659 deletions

254
test/xmod/m1_test.ww Normal file
View File

@@ -0,0 +1,254 @@
package m1_test;
// M1 path-mangle RUN-level observers over runtime-written dir-package
// trees, both driver stages. Ports of the retired native carriers
// test/wcc/989_m1mangle_run.c and 989_m1usehint_run.c; every assertion
// preserved. The symbol-level cousin claims live in
// test/byteid/mangle_test.ww; these rows own the build/link/run legs.
//
// m1mangle (#22 + #32) rows, each a tree beside a root main.ww built
// with `build -o main main.ww` (cwd = tree, Hare CWD==module-dir
// mirror) and run:
// nested_call | dir import path-mangles; qualified-ref hint
// | resolves the PATH aa.bb.val -> exit 42
// imported_main | imported pkg's private `fn main` mangles aa.bb.main
// | instead of colliding with the root entry -> 7
// priv_global | module-PRIVATE value global mangles aa.bb.pv -> 9
// single_level | control: single-level cc.v unchanged -> 5
// deep_nest | 2-level nest aa.bb.cc.val path-mangle -> 3
//
// m1usehint (#40) — the cgen mangle-hint must be MODULE-scoped, not
// unit-global: two dir-packages both `package math` export the same
// leaf `pick`, and each referencing module's `math.pick()` must bind
// its OWN import (a.math=111 / b.math=222; mis-route exits 1/2):
// leaf_collision | same-leaf exported fns, distinct bodies -> exit 0
// collision_diff | same, mismatched signatures (checker curmod-
// | preference agrees with the hint) -> exit 0
// plus the rule-10 byte-id leg: cstage vs wwstage sepwork *.s concat
// on the leaf_collision unit, byte-identical (concat order
// strengthened from the carrier's shell glob to explicit
// byte-lexicographic listdir).
//
// Dropped C machinery, not assertions: the ww_ww-absent skip gate
// (the Make target declares both drivers) and the per-path owned-flag
// cleanup ledger (testenv.clean asserts the removal).
import os;
import os.exec;
import strings;
import testenv;
import time;
fn fail(label: str, why: str) void = {
let m: str = strings.concat("m1 FAIL: ", label, " -- ", why, "\n");
os.write(2, m.ptr, m.len: u64);
assert(false);
};
fn tmo() time.duration = {
return (240i64 * (time.second: i64)): time.duration;
};
// -1 encodes an abnormal (non-EXIT) termination, never a valid code.
fn runcode(dir: str, name: str, argv: []str) i32 = {
let co: testenv.commandout;
testenv.runcommand(dir, dir, name, argv, tmo(), &co);
if (co.termination != exec.termination.EXIT) { return -1; };
return co.code;
};
// Every per-package .s under `sepdir`, concatenated in byte-sorted
// order; "" when the directory is missing or holds no .s.
fn catasm(sepdir: str) str = {
if (!testenv.isdir(sepdir)) { return ""; };
let names: []str = testenv.listdir(sepdir);
let out: str = "";
let i: i32 = 0;
for (i < names.len) {
if (strings.hassuffix(names[i], ".s")) {
out = strings.concat(out,
testenv.readfile(strings.concat(sepdir, "/", names[i])));
};
i += 1;
};
return out;
};
fn mkdirs(td: str, dirs: []str) void = {
let d: i32 = 0;
for (d < dirs.len) {
assert(os.mkdir(strings.concat(td, "/", dirs[d]), 493) == 0);
d += 1;
};
};
// ---- m1mangle (#22 + #32) ----------------------------------------------
// dirs are '/'-joined mkdir steps, parents before children.
fn m1row(label: str, dirs: []str, rel: str, content: str, root: str,
want: i32) void = {
let drvs: []str = ["ww", "ww_ww"];
let s: i32 = 0;
for (s < 2) {
let td: str = testenv.fresh();
mkdirs(td, dirs);
testenv.writefile(strings.concat(td, "/", rel), content);
testenv.writefile(strings.concat(td, "/main.ww"), root);
let av: []str = [testenv.driver(drvs[s]), "build", "-o", "main",
"main.ww"];
if (runcode(td, strings.concat("build_", drvs[s]), av) != 0) {
fail(label, strings.concat(drvs[s], " build failed (broken ",
"mangle hint or skip rule -> link failure)"));
};
let rav: []str = [strings.concat(td, "/main")];
if (runcode(td, strings.concat("run_", drvs[s]), rav) != want) {
fail(label, strings.concat(drvs[s],
" run-exit differs from the row's want"));
};
testenv.clean(td);
s += 1;
};
};
@test fn nested_call() void = {
let dirs: []str = ["aa", "aa/bb"];
m1row("nested_call", dirs, "aa/bb/bb.ww", strings.concat(
"package bb;\n",
"export fn val() int = { return 42; };\n"), strings.concat(
"package main;\n",
"import aa.bb;\n",
"export fn main() int = { return bb.val(); };\n"), 42);
};
@test fn imported_main() void = {
let dirs: []str = ["aa", "aa/bb"];
m1row("imported_main", dirs, "aa/bb/bb.ww", strings.concat(
"package bb;\n",
"fn main() int = { return 7; };\n",
"export fn run() int = { return main(); };\n"), strings.concat(
"package main;\n",
"import aa.bb;\n",
"export fn main() int = { return bb.run(); };\n"), 7);
};
@test fn priv_global() void = {
let dirs: []str = ["aa", "aa/bb"];
m1row("priv_global", dirs, "aa/bb/bb.ww", strings.concat(
"package bb;\n",
"let pv: int = 9;\n",
"export fn getpv() int = { return pv; };\n"), strings.concat(
"package main;\n",
"import aa.bb;\n",
"export fn main() int = { return bb.getpv(); };\n"), 9);
};
@test fn single_level() void = {
let dirs: []str = ["cc"];
m1row("single_level", dirs, "cc/cc.ww", strings.concat(
"package cc;\n",
"export fn v() int = { return 5; };\n"), strings.concat(
"package main;\n",
"import cc;\n",
"export fn main() int = { return cc.v(); };\n"), 5);
};
@test fn deep_nest() void = {
let dirs: []str = ["aa", "aa/bb", "aa/bb/cc"];
m1row("deep_nest", dirs, "aa/bb/cc/cc.ww",
strings.concat(
"package cc;\n",
"export fn val() int = { return 3; };\n"), strings.concat(
"package main;\n",
"import aa.bb.cc;\n",
"export fn main() int = { return cc.val(); };\n"), 3);
};
// ---- m1usehint (#40) ---------------------------------------------------
fn uselayout(td: str, apick: str, onecall: str) void = {
let dirs: []str = ["a", "a/math", "b", "b/math", "one", "two"];
mkdirs(td, dirs);
testenv.writefile(strings.concat(td, "/a/math/math.ww"),
strings.concat("package math;\n", apick));
testenv.writefile(strings.concat(td, "/b/math/math.ww"),
strings.concat(
"package math;\n",
"export fn pick() int = { return 222; };\n"));
testenv.writefile(strings.concat(td, "/one/one.ww"), strings.concat(
"package one;\n",
"import a.math;\n",
"export fn getone() int = { return ", onecall, "; };\n"));
testenv.writefile(strings.concat(td, "/two/two.ww"), strings.concat(
"package two;\n",
"import b.math;\n",
"export fn gettwo() int = { return math.pick(); };\n"));
testenv.writefile(strings.concat(td, "/main.ww"), strings.concat(
"package main;\n",
"import one;\n",
"import two;\n",
"export fn main() int = {\n",
" if (one.getone() != 111) { return 1; };\n",
" if (two.gettwo() != 222) { return 2; };\n",
" return 0;\n",
"};\n"));
};
fn userow(label: str, apick: str, onecall: str) void = {
let drvs: []str = ["ww", "ww_ww"];
let s: i32 = 0;
for (s < 2) {
let td: str = testenv.fresh();
uselayout(td, apick, onecall);
let av: []str = [testenv.driver(drvs[s]), "build", "-o", "main",
"main.ww"];
if (runcode(td, strings.concat("build_", drvs[s]), av) != 0) {
fail(label, strings.concat(drvs[s], " build failed"));
};
let rav: []str = [strings.concat(td, "/main")];
if (runcode(td, strings.concat("run_", drvs[s]), rav) != 0) {
fail(label, strings.concat(drvs[s], " run-exit != 0 (a ",
"file-global first-match hint routes one math.pick() to ",
"the wrong package -- #40)"));
};
testenv.clean(td);
s += 1;
};
};
@test fn leaf_collision() void = {
userow("leaf_collision",
"export fn pick() int = { return 111; };\n", "math.pick()");
};
@test fn collision_diff() void = {
userow("collision_diff",
"export fn pick(x: int) int = { return x + 100; };\n",
"math.pick(11)");
};
@test fn usehint_byteid() void = {
let td: str = testenv.fresh();
uselayout(td, "export fn pick() int = { return 111; };\n",
"math.pick()");
let drvs: []str = ["ww", "ww_ww"];
let tags: []str = ["cstage", "wwstage"];
let asms: []str = ["", ""];
let s: i32 = 0;
for (s < 2) {
let av: []str = [testenv.driver(drvs[s]), "build", "-o", tags[s],
"main.ww"];
if (runcode(td, strings.concat("bid_", tags[s]), av) != 0) {
fail("usehint_byteid", strings.concat(tags[s],
" build failed"));
};
asms[s] = catasm(strings.concat(td, "/", tags[s], ".sepwork"));
s += 1;
};
if (asms[0].len == 0 || asms[1].len == 0) {
fail("usehint_byteid", "empty .s concat");
};
if (!testenv.same(asms[0], asms[1])) {
fail("usehint_byteid", "cstage.s != wwstage.s (byte-id break)");
};
testenv.clean(td);
};