diff --git a/Makefile b/Makefile index b5b38cb6..d5509d38 100644 --- a/Makefile +++ b/Makefile @@ -366,6 +366,14 @@ SEP_WW_TESTS = test/sep/sepbuild_test.ww test/sep/sepimport_test.ww \ test/sep/seplink_test.ww test/sep/sepscratch_test.ww \ test/sep/septest_test.ww test/sep/m3sep_test.ww SEP_WW_TARGETS = $(SEP_WW_TESTS:%=wwtest/%) +# Ww-native multi-module-TREE observers: single-file ww tests under +# test/xmod/ on the test/testenv helper package, porting the residual +# multi-module C carriers (import-tree collisions, path-mangle and +# label-mangle runs, cross-module typecheck rejects, enumerator +# capacity, directive adjacency). Compiler/driver gates like test/sep: +# they run under test-compiler. +XMOD_WW_TESTS = test/xmod/collide_test.ww +XMOD_WW_TARGETS = $(XMOD_WW_TESTS:%=wwtest/%) BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \ test/wcc/991_w6a_ww.c \ test/wcc/992_w6l_ww.c test/wcc/993_ww_ww.c \ @@ -451,7 +459,7 @@ test-compiler-smoke: $(WWFIXTURE_BIN) $(BIN)/w6c_ww $(BIN)/w6a_ww \ # gates that cannot be represented by a single compiler fixture, plus the # ww-native sep observers that replaced the 989_sep* carriers. test-compiler: $(WWFIXTURE_BIN) $(WRAPPER_TOOLS) $(COMPILER_WRAPPER_BINS) \ - $(SEP_WW_TARGETS) + $(SEP_WW_TARGETS) $(XMOD_WW_TARGETS) @$(CURDIR)/$(WWFIXTURE_BIN) -j $(JOBS) @set -e; for t in $(COMPILER_WRAPPER_BINS); do \ echo "compiler artifact $$t"; BIN=$(CURDIR)/$(BIN) $(CURDIR)/$$t; \ @@ -499,6 +507,18 @@ $(SEP_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ -w $(WWBUILD)/wwtest/$(subst /,_,$*) \ -I $(CURDIR)/test $* +# The xmod observers drive both driver stages (and the modreset row the +# raw w6c/w6a/w6l chain) end-to-end, so both full per-stage toolchains +# are prerequisites. +$(XMOD_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(LIB)/libwwrt.a $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww \ + $(BIN)/w6l_ww + @echo "ww test $*" + @mkdir -p $(WWBUILD)/wwtest/$(subst /,_,$*) + @WW_TEST_REPO=$(CURDIR) $(BIN)/ww test \ + -w $(WWBUILD)/wwtest/$(subst /,_,$*) \ + -I $(CURDIR)/test $* + test-lang: $(LANG_TEST_TARGETS) test-library: $(LIBRARY_TEST_TARGETS) $(BIN)/w6c $(BIN)/w6c_ww \ diff --git a/test/wcc/989_barefn_collide_run.c b/test/wcc/989_barefn_collide_run.c deleted file mode 100644 index 48a12023..00000000 --- a/test/wcc/989_barefn_collide_run.c +++ /dev/null @@ -1,268 +0,0 @@ -/* - * 989_barefn_collide_run — #84 cgen bare-module fn-leaf collision gate. - * - * A bare-module fn (module==NULL — a package-LESS `//ww:module-reset` - * primary, e.g. a user test file) whose leaf collides with an IMPORTED - * module's same-leaf fn was MIS-MANGLED to the imported qualified name: - * mod_collect skipped bare decls, so the bare fn never entered mod_map, - * and at emission mod_lookup_for_fn(leaf, hint=NULL) first-matched the - * imported entry → emitted `.` instead of bare ``. That - * produced a DUPLICATE symbol with the imported fn (w6l-tolerated, #31- - * class) and the bare fn became silently dead — a #40 residual / #263- - * class silent miscompile, gate-blind and symmetric cs==ww. Surfaced by - * #80's `ww test` coexist (lib/test exports `run`). - * - * Strict-package (#24a) form: a dir-package `aa` exporting `run`, imported - * by a `package main;` root that defines its OWN `fn run` and calls it from - * `main`. The root's `run` mangles `main.run` — DISTINCT from the imported - * `aa.run` and from the bare entry `main`. The original #84 trigger (a - * truly BARE `run` from a PACKAGE-LESS root) is unreachable now that - * strict-package forbids package-less primaries; the #84 cgen machinery - * (mod_collect / mod_lookup_for_fn) stays dormant-but-load-bearing for - * genuinely package-less `//ww:module-reset` deps until #24b/#26. - * - * Asserts (default separate compilation, both driver stages): - * 1. Build + run, BOTH stages → exit 9 (the user's `main.run`, return 9), - * never aa.run (return 5). - * 2. cs==ww (rule 10): the concatenated per-package `.s` is byte-identical - * between the - * two driver stages. - * 3. DISTINCTNESS: the `.s` has EXACTLY ONE `TEXT main.run` (the user's) - * AND EXACTLY ONE `TEXT aa.run` (the import) — distinct symbols, no dup. - * - * Light wwstage-driver test (CLAUDE.md rule 14): all intermediates are - * `-o`-redirected to /tmp, phase-parallel-safe. Models 989_sepbuild_run.c. - */ -#include -#include -#include -#include -#include -#include -#include - -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 *body) -{ - FILE *f = fopen(path, "wb"); - if (!f) return -1; - fputs(body, f); - fclose(f); - return 0; -} - -/* Count occurrences of a line beginning with `^TEXT ,` in the .s. */ -static int -count_text_label(const char *sfile, const char *sym) -{ - char *buf = NULL; - size_t n = 0; - if (slurp(sfile, &buf, &n) < 0) return -1; - char needle[128]; - snprintf(needle, sizeof needle, "TEXT %s,", sym); - size_t nl = strlen(needle); - int count = 0; - size_t i = 0; - while (i < n) { - size_t j = i; - while (j < n && buf[j] != '\n') j++; - if (j - i >= nl && strncmp(buf + i, needle, nl) == 0) - count++; - i = j + 1; - } - free(buf); - return count; -} - -static const char *aa_src = - "package aa;\n" - "export fn run() i32 = { return 5; };\n"; - -/* Strict-package (#24a) root: declares `package main;`, so its `fn run` - * mangles `main.run` — DISTINCT from the imported `aa.run`, and from the - * bare entry `main`. The bare call `run()` resolves to the local `main.run` - * via the standard exact-hint path (the #84 special-case is bypassed for a - * moduled root). */ -static const char *root_src = - "package main;\n" - "import aa;\n" - "fn run() i32 = { return 9; };\n" - "export fn main() i32 = { return run(); };\n"; - -int -main(void) -{ - const char *bin = absbin(); - if (!bin) { fprintf(stderr, "84 FAIL: getcwd\n"); return 1; } - - char td[] = "/tmp/wwbare84_XXXXXX"; - char cmd[8192]; - int fail = 0, aaowned = 0, aafile = 0, rootfile = 0; - int stage_started[2] = { 0, 0 }; - if (mkdtemp(td) == NULL) { - perror("wwbare84: mkdtemp"); - return 1; - } - /* All paths derive from `td` (a small fixed 64-byte buffer) so the - * snprintfs are provably non-truncating (warning-clean). */ - char aadir[1024], aaww[1024], rootww[1024]; - snprintf(aadir, sizeof aadir, "%s/aa", td); - snprintf(aaww, sizeof aaww, "%s/aa/aa.ww", td); - snprintf(rootww, sizeof rootww, "%s/root.ww", td); - if (mkdir(aadir, 0755) != 0) { - perror(aadir); - fail++; - goto out; - } - aaowned = 1; - if (write_file(aaww, aa_src) != 0) { - fprintf(stderr, "84 FAIL: write fixture\n"); - fail++; - goto out; - } - aafile = 1; - if (write_file(rootww, root_src) != 0) { - fprintf(stderr, "84 FAIL: write fixture\n"); - fail++; - goto out; - } - rootfile = 1; - - struct { const char *drv, *tag; char prog[1024], sfile[1024]; } - stg[] = { { "ww", "cs", {0}, {0} }, { "ww_ww", "ww", {0}, {0} } }; - - for (int s = 0; s < 2; s++) { - snprintf(stg[s].prog, sizeof stg[s].prog, "%s/prog.%s", td, stg[s].tag); - snprintf(stg[s].sfile, sizeof stg[s].sfile, "%s/prog.%s.s", td, stg[s].tag); - /* #93 sep layout: `-o ` splits the asm across - * .sepwork/.s — the bare user `TEXT run,` lands in - * __root.s and the imported `TEXT aa.run,` in aa.s. Concat those - * exact per-unit .s files in a fixed order into the - * flat .s the label-count + byte-id checks consume. The - * `-I %s` source path is preserved, and all outputs stay under - * the tmpdir. */ - snprintf(cmd, sizeof cmd, - "timeout 240 %s/%s build -o %s " - "-I %s %s >/dev/null 2>&1 && " - "cat %s.sepwork/__root.s %s.sepwork/aa.s > %s", - bin, stg[s].drv, stg[s].prog, td, rootww, - stg[s].prog, stg[s].prog, stg[s].sfile); - stage_started[s] = 1; - if (runwait(cmd) != 0) { - fprintf(stderr, "84 FAIL: %s build\n", stg[s].drv); - fail++; - continue; - } - /* (1) the user's `main.run` (9) must win over imported aa.run (5). */ - int rc = runwait(stg[s].prog); - if (rc != 9) { - fprintf(stderr, "84 FAIL: %s prog exit=%d expected 9 " - "(user main.run, not aa.run=5)\n", stg[s].drv, rc); - fail++; - } - /* (3) distinct symbols, no dup: exactly one `main.run` + one - * `aa.run`. */ - int nrun = count_text_label(stg[s].sfile, "main.run"); - int naa = count_text_label(stg[s].sfile, "aa.run"); - if (nrun != 1 || naa != 1) { - fprintf(stderr, "84 FAIL: %s labels TEXT main.run=%d aa.run=%d " - "(want 1/1 — user main.run distinct from import, no dup)\n", - stg[s].drv, nrun, naa); - fail++; - } - } - - /* (2) cs==ww (rule 10): concatenated per-package .s is byte-identical. */ - if (files_eq(stg[0].sfile, stg[1].sfile) != 0) { - fprintf(stderr, "84 FAIL: cs .s != ww .s (rule 10)\n"); - fail++; - } - -out: - { - int cleanfail = 0; - char path[1024]; - const char *tags[] = { "cs", "ww" }; - for (int i = 0; i < 2; i++) { - if (!stage_started[i]) continue; - snprintf(path, sizeof path, "%s/prog.%s.sepwork", td, tags[i]); - snprintf(cmd, sizeof cmd, "rm -rf %s", path); - if (runwait(cmd) != 0) cleanfail = 1; - snprintf(path, sizeof path, "%s/prog.%s", td, tags[i]); - if (unlink(path) != 0 && errno != ENOENT) cleanfail = 1; - snprintf(path, sizeof path, "%s/prog.%s.s", td, tags[i]); - if (unlink(path) != 0 && errno != ENOENT) cleanfail = 1; - } - if (rootfile && unlink(rootww) != 0 && errno != ENOENT) cleanfail = 1; - if (aafile && unlink(aaww) != 0 && errno != ENOENT) cleanfail = 1; - if (aaowned && rmdir(aadir) != 0) cleanfail = 1; - if (rmdir(td) != 0) cleanfail = 1; - if (cleanfail) { - fprintf(stderr, "84 FAIL: temporary cleanup failed\n"); - fail++; - } - } - if (fail) { - fprintf(stderr, "84: %d check(s) failed\n", fail); - return 1; - } - printf("barefn_collide: `package main;` root `fn run` (main.run) coexists " - "with imported aa.run — distinct labels, no dup, user run wins " - "(exit 9), cs==ww, both driver stages (#84/#24a)\n"); - return 0; -} diff --git a/test/wcc/989_fnptrcollide_run.c b/test/wcc/989_fnptrcollide_run.c deleted file mode 100644 index 60e3ff84..00000000 --- a/test/wcc/989_fnptrcollide_run.c +++ /dev/null @@ -1,162 +0,0 @@ -/* - * 989_fnptrcollide_run — F7-c7 (#14): nodefnptr must be type-keyed, not - * name-keyed, so a value ident whose LEAF collides with a fn name is not - * mis-folded into the fn's TEXT reloc. - * - * THE BUG (cat-A silent miscompile, gate-blind): nodefnptr - * (selfhost/cmd/wcc/cgen.ww) decided whether `&x` (in a static-init / DATAR - * fold context) was the address-of a top-level fn by NAME — `fnretlookup( - * c, x.str) != nil`. So `&slot` for a data global `slot: i64` that shares a - * leaf with a fn `slot` (e.g. an imported `bar.slot`) matched the fn and - * folded into a DATAR reloc to the fn's TEXT symbol instead of the data - * symbol. cstage is type-keyed (node_fnptr_sym: type_chase_named(opnd-> - * type)->kind == TY_FN, cmd/w6c/cgen.c:15542) — `slot`'s stamped type is - * i64, not TY_FN, so it does NOT fold the reloc and the build errors loud. - * Pre-fix wwstage silently produced a binary (the cat-A divergence: cs - * loud-fails, ww builds). THE FIX: read the stamped operand type - * (opnd.type_ chased == TY_FN), aligning wwstage UP — a non-fn operand can - * never fold to a fn reloc, closing the leaf-name collision by construction. - * - * This shape is gate-blind (the corpus never collides a data-global leaf - * with a fn name on the &-fold path); only this row catches it. The - * conversion's neutrality on the corpus's REAL &fn static-inits (the - * #117/#119 reloc machinery) is proven separately by the c7 self-compile - * byte-id bind (B1/B2 zero-move) — see /tmp/implf7_result.txt. - * - * Assertion: the construction must FAIL TO BUILD on BOTH stages (cstage - * already rejects; wwstage now rejects too — rule-10 align-up). Pre-fix - * wwstage BUILT it (rc=0) — the row was RED on the pre-c7 binary. - */ -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -/* build_must_fail — write a bar/ module (fn `slot`) and a main.ww that - * declares a data global `slot: i64` plus `let fp: *i64 = &slot`, then - * `drv build -I bar main.ww`. Returns 0 when the build correctly FAILS - * (the leaf-name collision no longer diverts &slot into the fn reloc), - * non-zero when it wrongly built. */ -static int -build_must_fail(const char *drv, int tag, int *cleanup_failed) -{ - int pid = getpid(); - char dir[96], bard[160], p[224], cmd[1024]; - int rc = -1, setupfail = 1, cleanfail = 0, bard_owned = 0; - *cleanup_failed = 0; - snprintf(dir, sizeof dir, "/tmp/fnpc_%d_%d", pid, tag); - snprintf(bard, sizeof bard, "%s/bar", dir); - if (mkdir(dir, 0755) != 0) return -2; - if (mkdir(bard, 0755) != 0) goto cleanup; - bard_owned = 1; - - snprintf(p, sizeof p, "%s/bar.ww", bard); - FILE *f = fopen(p, "wb"); - if (!f) goto cleanup; - fputs("package bar;\n" - "export fn slot() i64 = { return 99; };\n", f); - if (fclose(f) != 0) goto cleanup; - - snprintf(p, sizeof p, "%s/main.ww", dir); - f = fopen(p, "wb"); - if (!f) goto cleanup; - fputs("package main;\n" - "import bar;\n" - "let slot: i64 = 7;\n" - "let fp: *i64 = &slot;\n" - "export fn main() int = { return (*fp): int; };\n", f); - if (fclose(f) != 0) goto cleanup; - - snprintf(cmd, sizeof cmd, - "cd %s && %s build -I bar main.ww >/dev/null 2>&1", dir, drv); - rc = runwait(cmd); - setupfail = 0; - -cleanup: - snprintf(p, sizeof p, "%s/main.sepwork", dir); - snprintf(cmd, sizeof cmd, "rm -rf %s", p); - if (runwait(cmd) != 0) cleanfail = 1; - snprintf(p, sizeof p, "%s/main", dir); - if (unlink(p) != 0 && access(p, F_OK) == 0) cleanfail = 1; - snprintf(p, sizeof p, "%s/main.ww", dir); - if (unlink(p) != 0 && access(p, F_OK) == 0) cleanfail = 1; - if (bard_owned) { - snprintf(p, sizeof p, "%s/bar.ww", bard); - if (unlink(p) != 0 && access(p, F_OK) == 0) cleanfail = 1; - if (rmdir(bard) != 0) cleanfail = 1; - } - if (rmdir(dir) != 0) cleanfail = 1; - *cleanup_failed = cleanfail; - - if (setupfail) return -2; - return rc == 0 ? -1 : 0; /* build must NOT succeed */ -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - if (bin[0] != '/') { - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char cdrv[1024], wdrv[1024]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); - - int total = 0, fail = 0; - - total++; - int cleanfail = 0; - int got = build_must_fail(cdrv, 1, &cleanfail); - if (got != 0) { - fprintf(stderr, "fnptrcollide_run[cstage]: %s\n", - got == -2 ? "setup failed" : - "built ok, expected the leaf-name collision to be rejected"); - fail++; - } - if (cleanfail) { - fprintf(stderr, "fnptrcollide_run[cstage]: temporary cleanup failed\n"); - fail++; - } - if (access(wdrv, X_OK) == 0) { /* wwstage gated */ - total++; - got = build_must_fail(wdrv, 2, &cleanfail); - if (got != 0) { - fprintf(stderr, "fnptrcollide_run[wwstage]: %s\n", - got == -2 ? "setup failed" : - "built ok, expected reject (#14 — &slot mis-folded " - "to the fn TEXT reloc by the name-keyed nodefnptr)"); - fail++; - } - if (cleanfail) { - fprintf(stderr, "fnptrcollide_run[wwstage]: temporary cleanup failed\n"); - fail++; - } - } else { - fprintf(stderr, "fnptrcollide_run: skip wwstage (no %s)\n", wdrv); - } - - if (fail) { - fprintf(stderr, "fnptrcollide_run: %d/%d checks failed\n", - fail, total); - return 1; - } - printf("fnptrcollide_run: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/xmod/collide_test.ww b/test/xmod/collide_test.ww new file mode 100644 index 00000000..51a62d87 --- /dev/null +++ b/test/xmod/collide_test.ww @@ -0,0 +1,134 @@ +package collide_test; + +// Cross-module leaf-name collision observers on both driver stages. +// Ports of the retired native carriers test/wcc/989_fnptrcollide_run.c +// and 989_barefn_collide_run.c; every assertion preserved. +// +// fnptrcollide (#14 F7-c7) — a data global `slot: i64` whose LEAF +// collides with the imported fn bar.slot must FAIL to build on BOTH +// stages: type-keyed nodefnptr refuses to fold `&slot` into the fn's +// TEXT reloc. Pre-fix wwstage was name-keyed and silently BUILT it +// (a DATAR to the fn symbol — cat-A: cs loud-fails, ww builds). The +// collision REQUIRES the import path (imported fn leaf vs local data +// global), so a single-file fixture cannot express it. +// +// barefn (#84/#24a) — a `package main;` root `fn run` coexists with +// imported aa.run: build+run exit 9 on BOTH stages (the user's +// main.run wins, never aa.run=5); the concatenated +// __root.s+aa.s (fixed #93 sep order) carries EXACTLY ONE +// `TEXT main.run,` and EXACTLY ONE `TEXT aa.run,` (distinct symbols, +// no #31-class w6l-tolerated duplicate, no silently-dead bare fn); +// cs==ww byte-for-byte (rule 10). +// +// Dropped C machinery, not assertions: the ww_ww-absent skip gate +// (the Make target declares both drivers), the shell `timeout 240` +// (runcommand's deadline carries it), and the unlink/rmdir accounting +// (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("collide 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; +}; + +@test fn fnptrcollide() void = { + let drvs: []str = ["ww", "ww_ww"]; + let i: i32 = 0; + for (i < 2) { + let td: str = testenv.fresh(); + assert(os.mkdir(strings.concat(td, "/bar"), 493) == 0); + testenv.writefile(strings.concat(td, "/bar/bar.ww"), + strings.concat( + "package bar;\n", + "export fn slot() i64 = { return 99; };\n")); + testenv.writefile(strings.concat(td, "/main.ww"), strings.concat( + "package main;\n", + "import bar;\n", + "let slot: i64 = 7;\n", + "let fp: *i64 = &slot;\n", + "export fn main() int = { return (*fp): int; };\n")); + let av: []str = [testenv.driver(drvs[i]), "build", "-I", "bar", + "main.ww"]; + if (runcode(td, strings.concat("build_", drvs[i]), av) == 0) { + fail("fnptrcollide", strings.concat(drvs[i], " built ok, ", + "expected the leaf-name collision to be rejected (#14 -- ", + "&slot mis-folded to the fn TEXT reloc)")); + }; + testenv.clean(td); + i += 1; + }; +}; + +// __root.s then aa.s: the carrier's fixed #93 sep concat order. +fn catfixed(stem: str) str = { + return strings.concat( + testenv.readfile(strings.concat(stem, ".sepwork/__root.s")), + testenv.readfile(strings.concat(stem, ".sepwork/aa.s"))); +}; + +// column-0 anchored label lines; the '\n' prepend counts a file-leading +// label too. +fn textcount(s: str, sym: str) i32 = { + return testenv.occurrences(strings.concat("\n", s), + strings.concat("\nTEXT ", sym, ",")); +}; + +@test fn barefn() void = { + let td: str = testenv.fresh(); + assert(os.mkdir(strings.concat(td, "/aa"), 493) == 0); + testenv.writefile(strings.concat(td, "/aa/aa.ww"), strings.concat( + "package aa;\n", + "export fn run() i32 = { return 5; };\n")); + testenv.writefile(strings.concat(td, "/root.ww"), strings.concat( + "package main;\n", + "import aa;\n", + "fn run() i32 = { return 9; };\n", + "export fn main() i32 = { return run(); };\n")); + let drvs: []str = ["ww", "ww_ww"]; + let tags: []str = ["cs", "ww"]; + let asms: []str = ["", ""]; + let s: i32 = 0; + for (s < 2) { + let stem: str = strings.concat(td, "/prog.", tags[s]); + let av: []str = [testenv.driver(drvs[s]), "build", "-o", stem, + "-I", td, strings.concat(td, "/root.ww")]; + if (runcode(td, strings.concat("build_", tags[s]), av) != 0) { + fail("barefn", strings.concat(drvs[s], " build failed")); + }; + let rav: []str = [stem]; + if (runcode(td, strings.concat("run_", tags[s]), rav) != 9) { + fail("barefn", strings.concat(drvs[s], " exit != 9 ", + "(user main.run must win, not aa.run=5)")); + }; + asms[s] = catfixed(stem); + if (textcount(asms[s], "main.run") != 1 + || textcount(asms[s], "aa.run") != 1) { + fail("barefn", strings.concat(drvs[s], " TEXT label counts ", + "!= 1/1 (user main.run distinct from import, no dup)")); + }; + s += 1; + }; + if (!testenv.same(asms[0], asms[1])) { + fail("barefn", "cs .s != ww .s (rule 10)"); + }; + testenv.clean(td); +};