From dc6fff3af82cc119026853426ffd39e948ca91af Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 14:32:24 +0900 Subject: [PATCH] test: port the enumerator-capacity observer to ww --- Makefile | 3 +- test/wcc/989_enumcap_run.c | 246 ------------------------------------- test/xmod/enumcap_test.ww | 109 ++++++++++++++++ 3 files changed, 111 insertions(+), 247 deletions(-) delete mode 100644 test/wcc/989_enumcap_run.c create mode 100644 test/xmod/enumcap_test.ww diff --git a/Makefile b/Makefile index ffc79413..1b48299b 100644 --- a/Makefile +++ b/Makefile @@ -373,7 +373,8 @@ SEP_WW_TARGETS = $(SEP_WW_TESTS:%=wwtest/%) # capacity, directive adjacency). Compiler/driver gates like test/sep: # they run under test-compiler. XMOD_WW_TESTS = test/xmod/collide_test.ww test/xmod/m1_test.ww \ - test/xmod/label_test.ww test/xmod/typecheck_test.ww + test/xmod/label_test.ww test/xmod/typecheck_test.ww \ + test/xmod/enumcap_test.ww XMOD_WW_TARGETS = $(XMOD_WW_TESTS:%=wwtest/%) BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \ test/wcc/991_w6a_ww.c \ diff --git a/test/wcc/989_enumcap_run.c b/test/wcc/989_enumcap_run.c deleted file mode 100644 index 1b9fb5e2..00000000 --- a/test/wcc/989_enumcap_run.c +++ /dev/null @@ -1,246 +0,0 @@ -/* - * 989_enumcap_run (#65, F13 c3) — the driver's directory enumerator must - * bundle EVERY eligible *.ww source, not silently cap at 256. - * - * THE BUG (wwstage driver only, cat-A silent wrong unit under rc=0): - * enumeratedir (selfhost/cmd/ww/main.ww) capped at maxnames=256 and the - * `if (n < maxnames)` guard silently discarded every further eligible file — - * no diagnostic. cstage enumerate_dir_ww (cmd/ww/main.c:209) grows via - * realloc doubling, unbounded. A module dir with >256 sources bundled all of - * them under cstage vs 256 under wwstage, a silently divergent package unit - * (and binary) under rc=0 — invisible to the byte-id gates (no in-tree - * module nears 256 files). THE FIX (CAP-SEMANTICS rule: the cstage twin grows, - * so grow-dynamic): mirror the realloc-doubling growth in enumeratedir. - * - * #94 sep layout: separate compilation stops emitting a single - * .combined.ww; each package is enumerated into its OWN - * .sepwork/.unit.ww - * (the bigmod dir → bigmod.unit.ww, holding one `package bigmod;` clause per - * enumerated source file — the exact set the combined.ww used to hold for that - * package). So the gate now drives `ww build` / `ww_ww build` and - * re-targets the enumeration check onto bigmod.unit.ww: each stage must enroll - * `nfiles` package clauses, and the two stages' bigmod.unit.ww must be byte- - * identical. A cap-drop on one side shrinks that stage's bigmod.unit.ww — the - * count check AND the cs/ww byte-compare both redden (the cap can't pass blind). - * - * row | files | result (cs==ww) - * --------+-------+------------------------------------------ - * big_300 | 300 | both enroll 300, bigmod.unit.ww byte-id - * small_5 | 5 | both enroll 5, bigmod.unit.ww byte-id (control) - * - * big_300 was RED pre-c3 (ww enrolled 256, cs 300 — divergent unit). - * small_5 pins the common ≤cap path stays byte-identical. - * - * All intermediates redirect under each stage's -o stem in /tmp, so out/ - * and the source tree stay clean and the gate runs parallel-safe. - */ -#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; -} - -/* Count '^package bigmod;' lines in a file. -1 on open failure. */ -static int -count_pkgs(const char *path) -{ - FILE *f = fopen(path, "rb"); - if (!f) return -1; - char line[256]; - int n = 0; - while (fgets(line, sizeof line, f)) - if (strncmp(line, "package bigmod;", 15) == 0) n++; - fclose(f); - return n; -} - -/* Returns 0 if both driver builds enrolled `nfiles` package clauses into - * their bigmod.unit.ww AND the two units are byte-identical; non-zero - * (and prints) otherwise. */ -static int -one_row(const char *label, const char *cdrv, const char *wdrv, int nfiles, - int have_ww) -{ - char dir[64], bigdir[128], main_ww[128], cstem[128], wstem[128]; - char cunit[192], wunit[192], cmd[1024], fn[160]; - snprintf(dir, sizeof dir, "/tmp/encap_%d_%s", getpid(), label); - if (mkdir(dir, 0755) != 0) { - perror(dir); - return 1; - } - int fail = 0, have_bigdir = 0; - snprintf(bigdir, sizeof bigdir, "%s/bigmod", dir); - if (mkdir(bigdir, 0755) != 0) { - perror(bigdir); - fail++; - goto cleanup; - } - have_bigdir = 1; - - for (int i = 0; i < nfiles; i++) { - snprintf(fn, sizeof fn, "%s/bigmod/f%03d.ww", dir, i); - FILE *f = fopen(fn, "wb"); - if (!f) { - perror(fn); - fail++; - goto cleanup; - } - int werr = fprintf(f, "package bigmod;\nexport fn f%03d() i32 = " - "{ return %d; };\n", i, i) < 0; - if (fclose(f) != 0) werr = 1; - if (werr) { - perror(fn); - fail++; - goto cleanup; - } - } - snprintf(main_ww, sizeof main_ww, "%s/main.ww", dir); - FILE *mf = fopen(main_ww, "wb"); - if (!mf) { - perror(main_ww); - fail++; - goto cleanup; - } - int werr = fputs("package main;\nimport bigmod;\nfn main() void = {};\n", mf) == EOF; - if (fclose(mf) != 0) werr = 1; - if (werr) { - perror(main_ww); - fail++; - goto cleanup; - } - - snprintf(cstem, sizeof cstem, "%s/X_c", dir); - snprintf(wstem, sizeof wstem, "%s/X_w", dir); - /* the bigmod dir enumerates into its own per-package sep unit */ - snprintf(cunit, sizeof cunit, "%s.sepwork/bigmod.unit.ww", cstem); - snprintf(wunit, sizeof wunit, "%s.sepwork/bigmod.unit.ww", wstem); - - snprintf(cmd, sizeof cmd, - "%s build -o %s %s 2>/dev/null", - cdrv, cstem, main_ww); - if (runwait(cmd) != 0) { - fprintf(stderr, "enumcap[cstage][%s]: build failed\n", label); - fail++; - } - int cn = count_pkgs(cunit); - if (cn != nfiles) { - fprintf(stderr, "enumcap[cstage][%s]: enrolled %d want %d\n", - label, cn, nfiles); - fail++; - } - - if (have_ww) { - snprintf(cmd, sizeof cmd, - "%s build -o %s %s 2>/dev/null", - wdrv, wstem, main_ww); - if (runwait(cmd) != 0) { - fprintf(stderr, "enumcap[wwstage][%s]: build failed\n", label); - fail++; - } - int wn = count_pkgs(wunit); - if (wn != nfiles) { - fprintf(stderr, "enumcap[wwstage][%s]: enrolled %d want %d " - "(silent enumeratedir cap — #65)\n", label, wn, nfiles); - fail++; - } - /* the decisive rule-10 assertion: identical per-package unit — - * a cap-drop on either side shrinks its bigmod.unit.ww */ - snprintf(cmd, sizeof cmd, "cmp -s %s %s", cunit, wunit); - if (runwait(cmd) != 0) { - fprintf(stderr, "enumcap[%s]: cs/ww bigmod.unit.ww differ (#65)\n", - label); - fail++; - } - } - -cleanup: - if (have_bigdir) { - for (int i = 0; i < nfiles; i++) { - snprintf(fn, sizeof fn, "%s/bigmod/f%03d.ww", dir, i); - if (unlink(fn) != 0 && errno != ENOENT) { - perror(fn); - fail++; - } - } - } - snprintf(main_ww, sizeof main_ww, "%s/main.ww", dir); - if (unlink(main_ww) != 0 && errno != ENOENT) { - perror(main_ww); - fail++; - } - snprintf(cstem, sizeof cstem, "%s/X_c", dir); - snprintf(wstem, sizeof wstem, "%s/X_w", dir); - if (unlink(cstem) != 0 && errno != ENOENT) { - perror(cstem); - fail++; - } - if (unlink(wstem) != 0 && errno != ENOENT) { - perror(wstem); - fail++; - } - char work[192]; - snprintf(work, sizeof work, "%s.sepwork", cstem); - snprintf(cmd, sizeof cmd, "rm -rf %s", work); - if (runwait(cmd) != 0) { - fprintf(stderr, "enumcap[%s]: cleanup failed: %s\n", label, work); - fail++; - } - snprintf(work, sizeof work, "%s.sepwork", wstem); - snprintf(cmd, sizeof cmd, "rm -rf %s", work); - if (runwait(cmd) != 0) { - fprintf(stderr, "enumcap[%s]: cleanup failed: %s\n", label, work); - fail++; - } - if (have_bigdir && rmdir(bigdir) != 0) { - perror(bigdir); - fail++; - } - if (rmdir(dir) != 0) { - perror(dir); - fail++; - } - return fail; -} - -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 have_ww = (access(wdrv, X_OK) == 0); - if (!have_ww) - fprintf(stderr, "enumcap: skip wwstage (no %s)\n", wdrv); - - int fail = 0, total = 2; - fail += one_row("big_300", cdrv, wdrv, 300, have_ww); - fail += one_row("small_5", cdrv, wdrv, 5, have_ww); - - if (fail) { - fprintf(stderr, "enumcap_run: %d check(s) failed\n", fail); - return 1; - } - printf("enumcap_run: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/xmod/enumcap_test.ww b/test/xmod/enumcap_test.ww new file mode 100644 index 00000000..512cf96c --- /dev/null +++ b/test/xmod/enumcap_test.ww @@ -0,0 +1,109 @@ +package enumcap_test; + +// #65 (F13 c3) driver directory-enumerator capacity gate. Port of the +// retired native carrier test/wcc/989_enumcap_run.c; every assertion +// preserved. The module tree is GENERATED at runtime (301 files on the +// big row) -- it cannot be checked in as a fixture. +// +// Pre-c3 the wwstage enumeratedir silently capped at 256 files while +// cstage grows unbounded: a >256-file module dir bundled a silently +// divergent package unit under rc=0. Per row (big_300, small_5 +// control), both driver builds must succeed, each stage's +// .sepwork/bigmod.unit.ww must hold EXACTLY nfiles +// line-anchored `package bigmod;` clauses, and the two units must be +// byte-identical (the decisive rule-10 leg -- a cap-drop on either +// side shrinks its unit). +// +// Dropped C machinery, not assertions: the ww_ww-absent skip gate +// (the Make target declares both drivers) and the per-file cleanup +// accounting (testenv.clean asserts the removal). + +import os; +import os.exec; +import strconv; +import strings; +import testenv; +import time; + +fn fail(label: str, why: str) void = { + let m: str = strings.concat("enumcap 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; +}; + +// itos returns a view into a static buffer; dup materializes it so two +// numbers can coexist in one concat. +fn dec(n: i32) str = { + return strings.dup(strconv.itos(n: int, strconv.base.DEC)); +}; + +fn pad3(n: i32) str = { + let d: str = dec(n); + if (n < 10) { return strings.concat("00", d); }; + if (n < 100) { return strings.concat("0", d); }; + return d; +}; + +// line-anchored `package bigmod;` clause count; the '\n' prepend +// counts a file-leading clause too. +fn countpkg(path: str) i32 = { + let body: str = strings.concat("\n", testenv.readfile(path)); + return testenv.occurrences(body, "\npackage bigmod;"); +}; + +fn row(label: str, nfiles: i32) void = { + let td: str = testenv.fresh(); + assert(os.mkdir(strings.concat(td, "/bigmod"), 493) == 0); + let i: i32 = 0; + for (i < nfiles) { + let nn: str = pad3(i); + testenv.writefile(strings.concat(td, "/bigmod/f", nn, ".ww"), + strings.concat("package bigmod;\nexport fn f", nn, + "() i32 = { return ", dec(i), "; };\n")); + i += 1; + }; + let mainww: str = strings.concat(td, "/main.ww"); + testenv.writefile(mainww, + "package main;\nimport bigmod;\nfn main() void = {};\n"); + + let drvs: []str = ["ww", "ww_ww"]; + let stems: []str = ["X_c", "X_w"]; + let units: []str = ["", ""]; + let s: i32 = 0; + for (s < 2) { + let stem: str = strings.concat(td, "/", stems[s]); + let av: []str = [testenv.driver(drvs[s]), "build", "-o", stem, + mainww]; + if (runcode(td, strings.concat("build_", stems[s]), av) != 0) { + fail(label, strings.concat(drvs[s], " build failed")); + }; + units[s] = strings.concat(stem, ".sepwork/bigmod.unit.ww"); + if (countpkg(units[s]) != nfiles) { + fail(label, strings.concat(drvs[s], " enrolled clause count ", + "!= nfiles (silent enumerator cap -- #65)")); + }; + s += 1; + }; + if (!testenv.same(testenv.readfile(units[0]), + testenv.readfile(units[1]))) { + fail(label, "cs/ww bigmod.unit.ww differ (#65)"); + }; + testenv.clean(td); +}; + +@test fn big_300() void = { row("big_300", 300); }; + +@test fn small_5() void = { row("small_5", 5); };