test: port the enumerator-capacity observer to ww
This commit is contained in:
3
Makefile
3
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 \
|
||||
|
||||
@@ -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
|
||||
* <stem>.combined.ww; each package is enumerated into its OWN
|
||||
* <stem>.sepwork/<pkg>.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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
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;
|
||||
}
|
||||
109
test/xmod/enumcap_test.ww
Normal file
109
test/xmod/enumcap_test.ww
Normal file
@@ -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
|
||||
// <stem>.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); };
|
||||
Reference in New Issue
Block a user