test: port 989_lib_byteid to ww; birth test/testenv + test/byteid
test/testenv (package testenv) is the shared plumbing for ww-native test drivers — subprocess launch with captured output, file IO, string search, byte-sorted directory listing, scratch ownership — lifted from the proven package_test.ww idiom; a utility, not a framework. test/byteid/libbyteid_test.ww carries every assertion of the retired C carrier: the 44-entry roster (fixtures + sentinel-guarded import probes + the zero-dep root-only build), both-stage sep builds with the resolved-unit proof, per-package .s concat compare with the empty-concat guard, the ID/DIVERGE/WWREJECT pin discipline, and the lib/ corpus completeness scan (negative-verified against a planted un-enrolled module). Concat order strengthened from shell-glob to explicit byte-lexicographic. The roster fills by cursor-driven field writes because append() rejects struct-call-result sources (#34). Byteid carriers 11 -> 10; docs counts move; test-byteid runs the ww test via the wwtest/ pattern with WW_TEST_REPO.
This commit is contained in:
22
Makefile
22
Makefile
@@ -356,7 +356,14 @@ BYTEID_WRAPPER_SOURCES = test/wcc/753_convwrap_audit.c \
|
|||||||
test/wcc/946_structret_run.c \
|
test/wcc/946_structret_run.c \
|
||||||
test/wcc/989_m1mangle_sym.c \
|
test/wcc/989_m1mangle_sym.c \
|
||||||
test/wcc/989_m2wwi_run.c \
|
test/wcc/989_m2wwi_run.c \
|
||||||
test/wcc/989_wwileaf_run.c test/wcc/989_lib_byteid.c
|
test/wcc/989_wwileaf_run.c
|
||||||
|
|
||||||
|
# Ww-native byte/artifact observers: single-file ww tests under
|
||||||
|
# test/byteid/ on the test/testenv helper package. Each drives both
|
||||||
|
# driver stages itself, so the build needs only cstage plus the two
|
||||||
|
# stage frontends it launches.
|
||||||
|
BYTEID_WW_TESTS = test/byteid/libbyteid_test.ww
|
||||||
|
BYTEID_WW_TARGETS = $(BYTEID_WW_TESTS:%=wwtest/%)
|
||||||
BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \
|
BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \
|
||||||
test/wcc/991_w6a_ww.c \
|
test/wcc/991_w6a_ww.c \
|
||||||
test/wcc/992_w6l_ww.c test/wcc/993_ww_ww.c \
|
test/wcc/992_w6l_ww.c test/wcc/993_ww_ww.c \
|
||||||
@@ -455,6 +462,14 @@ $(LIBRARY_TEST_TARGETS): wwtest/%: $(BIN)/ww \
|
|||||||
@$(BIN)/ww test -w $(WWBUILD)/wwtest/$(subst /,_,$*) \
|
@$(BIN)/ww test -w $(WWBUILD)/wwtest/$(subst /,_,$*) \
|
||||||
-I $(CURDIR)/lib/ww $*
|
-I $(CURDIR)/lib/ww $*
|
||||||
|
|
||||||
|
$(BYTEID_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a \
|
||||||
|
$(BIN)/w6l $(LIB)/libwwrt.a $(BIN)/ww_ww $(BIN)/w6c_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-lang: $(LANG_TEST_TARGETS)
|
||||||
|
|
||||||
test-library: $(LIBRARY_TEST_TARGETS) $(BIN)/w6c $(BIN)/w6c_ww \
|
test-library: $(LIBRARY_TEST_TARGETS) $(BIN)/w6c $(BIN)/w6c_ww \
|
||||||
@@ -617,7 +632,8 @@ test-data-byteid: $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww
|
|||||||
fi; \
|
fi; \
|
||||||
exit $$rc
|
exit $$rc
|
||||||
|
|
||||||
test-byteid: test-lang-byteid test-data-byteid test-native-byteid
|
test-byteid: test-lang-byteid test-data-byteid test-native-byteid \
|
||||||
|
$(BYTEID_WW_TARGETS)
|
||||||
|
|
||||||
# Incremental == clean. The -w reuse path must be byte-identical to a
|
# Incremental == clean. The -w reuse path must be byte-identical to a
|
||||||
# from-scratch build, or it is not a build-graph optimization but a
|
# from-scratch build, or it is not a build-graph optimization but a
|
||||||
@@ -821,7 +837,7 @@ nocc:
|
|||||||
.PHONY: all cstage wwstage wwfixture test-wwfixture-process test-wwfixture \
|
.PHONY: all cstage wwstage wwfixture test-wwfixture-process test-wwfixture \
|
||||||
test test-unit test-compiler-smoke test-compiler test-package \
|
test test-unit test-compiler-smoke test-compiler test-package \
|
||||||
test-lang test-library test-commit test-lang-byteid \
|
test-lang test-library test-commit test-lang-byteid \
|
||||||
$(LANG_TEST_TARGETS) $(LIBRARY_TEST_TARGETS) \
|
$(LANG_TEST_TARGETS) $(LIBRARY_TEST_TARGETS) $(BYTEID_WW_TARGETS) \
|
||||||
test-data-byteid test-native-byteid test-byteid \
|
test-data-byteid test-native-byteid test-byteid \
|
||||||
test-bootstrap-native test-bootstrap test-incremental \
|
test-bootstrap-native test-bootstrap test-incremental \
|
||||||
test-platform test-all install clean bootstrap nocc bootstrap-snapshot
|
test-platform test-all install clean bootstrap nocc bootstrap-snapshot
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ The live declarative compiler corpus has 1,495 fixtures and 2,990 C/WW cells:
|
|||||||
338 expected rejections (314 shared and 24 stage-specific), 17 compile-only
|
338 expected rejections (314 shared and 24 stage-specific), 17 compile-only
|
||||||
successes, 198 exit-zero programs, and 942 explicit-exit programs.
|
successes, 198 exit-zero programs, and 942 explicit-exit programs.
|
||||||
|
|
||||||
134 native C carriers remain. They are partitioned exactly once as five
|
133 native C carriers remain. They are partitioned exactly once as five
|
||||||
in-process units, 11 byte/artifact gates, six bootstrap gates, one platform
|
in-process units, 10 byte/artifact gates, six bootstrap gates, one platform
|
||||||
gate, and 111 residual compiler, package-layout, ABI, diagnostic-observer,
|
gate, and 111 residual compiler, package-layout, ABI, diagnostic-observer,
|
||||||
driver, linker, or FFI gates. Rows migrated to fixtures or native `@test`
|
driver, linker, or FFI gates. Rows migrated to fixtures or native `@test`
|
||||||
owners were removed from those carriers; there is no compatibility execution
|
owners were removed from those carriers; there is no compatibility execution
|
||||||
@@ -46,11 +46,15 @@ was a cstage-vs-wwstage `.s` byte-compare of sources now living in the
|
|||||||
corpus were retired into the blanket `test-data-byteid` comparator (the
|
corpus were retired into the blanket `test-data-byteid` comparator (the
|
||||||
last 13 had their remaining inline sources added as fixtures first); the
|
last 13 had their remaining inline sources added as fixtures first); the
|
||||||
survivors observe something the blanket cannot: assembly patterns, symbol
|
survivors observe something the blanket cannot: assembly patterns, symbol
|
||||||
tables, `.wwi` round-trips, or the wwstage-driver leg. Wwstage-driver-leg byte identity (`ww_ww` versus `ww`
|
tables, `.wwi` round-trips, or the wwstage-driver leg. Wwstage-driver-leg
|
||||||
over the emitted `.s` set) has one owner, `989_lib_byteid`, whose unit
|
byte identity (`ww_ww` versus `ww` over the emitted `.s` set) has one
|
||||||
sweep spans lib test fixtures, import probes, and a zero-dep root-only
|
owner, now ww-native: `test/byteid/libbyteid_test.ww` on the
|
||||||
build; the former 815/940/951 driver-parity carriers were folded into it,
|
`test/testenv` helper package, whose unit sweep spans the 44-entry lib
|
||||||
their content identity already owned by their corpus twins.
|
roster (test fixtures, sentinel-guarded import probes, a zero-dep
|
||||||
|
root-only build) plus the lib/ completeness scan; the former 815/940/951
|
||||||
|
driver-parity carriers were folded into it, their content identity
|
||||||
|
already owned by their corpus twins, and the native `989_lib_byteid.c`
|
||||||
|
carrier retired with the port.
|
||||||
|
|
||||||
## Carrier endgame
|
## Carrier endgame
|
||||||
|
|
||||||
|
|||||||
434
test/byteid/libbyteid_test.ww
Normal file
434
test/byteid/libbyteid_test.ww
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
package libbyteid_test;
|
||||||
|
|
||||||
|
// cstage-vs-wwstage byte-identity gate over the whole lib/ surface, and
|
||||||
|
// the ONE owner of wwstage-DRIVER-leg byte identity. Port of the
|
||||||
|
// retired native carrier test/wcc/989_lib_byteid.c; every assertion
|
||||||
|
// preserved, .s concat order strengthened from shell-glob to explicit
|
||||||
|
// byte-lexicographic.
|
||||||
|
//
|
||||||
|
// The bootstrap gates (991-995) byte-id only the modules the selfhost
|
||||||
|
// tools import. Every other lib/ module compiles through the CSTAGE
|
||||||
|
// driver alone elsewhere, so a cs≠ww divergence there ships gate-green
|
||||||
|
// (regex.finish did, task #21 FC0). This gate closes the class: each
|
||||||
|
// lib test fixture is sep-built through BOTH driver stages and the
|
||||||
|
// per-package asm compared.
|
||||||
|
//
|
||||||
|
// #94 sep layout: under sep EACH package compiles to its OWN
|
||||||
|
// <stem>.sepwork/<pkg>.s — the lib body lands in <pkg>.s, NOT in
|
||||||
|
// __root.s. The byte-id concats EVERY per-package .s (lib body +
|
||||||
|
// lib/test's auto-bundled runner), never just __root.s: comparing the
|
||||||
|
// root alone would byte-id the harness wrapper while the engine ships
|
||||||
|
// uncovered.
|
||||||
|
//
|
||||||
|
// Three pinned outcomes, loud over blind (task #21 ruling):
|
||||||
|
// ID — byte-identical across all per-package .s, zero tolerance.
|
||||||
|
// DIVERGE — known cs≠ww divergence (task #59, cite). Both stages
|
||||||
|
// must still compile and the asm must still DIFFER; a fix
|
||||||
|
// fails the entry demanding graduation to ID.
|
||||||
|
// WWREJECT — cstage compiles, wwstage errors (task #59, cite);
|
||||||
|
// graduation pinned the same way.
|
||||||
|
//
|
||||||
|
// Fixtureless modules are covered by import-probe stubs: `import`
|
||||||
|
// drags the whole module into its own sep package. The driver silently
|
||||||
|
// skips an unresolvable import, so each probe carries a sentinel that
|
||||||
|
// must appear in a per-package .unit.ww, and the completeness scan
|
||||||
|
// fails loudly on any lib/ dir not enrolled here.
|
||||||
|
|
||||||
|
import os;
|
||||||
|
import os.exec;
|
||||||
|
import strings;
|
||||||
|
import testenv;
|
||||||
|
import time;
|
||||||
|
|
||||||
|
def MID: i32 = 0;
|
||||||
|
def MDIVERGE: i32 = 1;
|
||||||
|
def MWWREJECT: i32 = 2;
|
||||||
|
def NENTEXPECT: i32 = 44;
|
||||||
|
|
||||||
|
type ent = struct {
|
||||||
|
fixture: str, // repo-relative .ww; "" -> probe entry
|
||||||
|
probe: str, // inline import-probe source
|
||||||
|
inc: str, // extra -I dir beyond dirname(fixture)
|
||||||
|
mode: i32,
|
||||||
|
cite: str, // task #59 entry for non-ID modes
|
||||||
|
sentinel: str, // probe-only: proof the module body landed
|
||||||
|
moddir: str, // probe-only: completeness key
|
||||||
|
};
|
||||||
|
|
||||||
|
// #34: append() rejects struct-call-result sources, so the roster is a
|
||||||
|
// preallocated slice filled by per-row field writes behind a running
|
||||||
|
// cursor; the cursor total re-proves the NENTEXPECT count.
|
||||||
|
fn zeroent(e: *ent) void = {
|
||||||
|
e.fixture = ""; e.probe = ""; e.inc = ""; e.mode = MID;
|
||||||
|
e.cite = ""; e.sentinel = ""; e.moddir = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
fn fx(es: []ent, k: *i32, f: str) void = {
|
||||||
|
let i: i32 = *k;
|
||||||
|
es[i].fixture = f;
|
||||||
|
*k = i + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn fxi(es: []ent, k: *i32, f: str, inc: str) void = {
|
||||||
|
let i: i32 = *k;
|
||||||
|
es[i].fixture = f;
|
||||||
|
es[i].inc = inc;
|
||||||
|
*k = i + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn pr(es: []ent, k: *i32, p: str, sentinel: str, moddir: str) void = {
|
||||||
|
let i: i32 = *k;
|
||||||
|
es[i].probe = p;
|
||||||
|
es[i].sentinel = sentinel;
|
||||||
|
es[i].moddir = moddir;
|
||||||
|
*k = i + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn pri(es: []ent, k: *i32, p: str, inc: str, sentinel: str,
|
||||||
|
moddir: str) void = {
|
||||||
|
let i: i32 = *k;
|
||||||
|
es[i].probe = p;
|
||||||
|
es[i].inc = inc;
|
||||||
|
es[i].sentinel = sentinel;
|
||||||
|
es[i].moddir = moddir;
|
||||||
|
*k = i + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The 44-unit roster. Graduation history lives in git (the retired C
|
||||||
|
// carrier's table comments); cites are kept only where a non-ID pin
|
||||||
|
// would need them.
|
||||||
|
fn corpus() []ent = {
|
||||||
|
let es: []ent = alloc([], NENTEXPECT: u64)!;
|
||||||
|
es.len = NENTEXPECT;
|
||||||
|
let z: i32 = 0;
|
||||||
|
for (z < es.len) { zeroent(&es[z]); z += 1; };
|
||||||
|
let n: i32 = 0;
|
||||||
|
let k: *i32 = &n;
|
||||||
|
fx(es, k, "lib/bytes/bytestest.ww");
|
||||||
|
fx(es, k, "lib/dirs/dirstest.ww");
|
||||||
|
fx(es, k, "lib/encoding/base32/base32_test.ww");
|
||||||
|
fx(es, k, "lib/encoding/hex/hextest.ww");
|
||||||
|
fx(es, k, "lib/encoding/utf8/utf8test.ww");
|
||||||
|
fx(es, k, "lib/getopt/getopttest.ww");
|
||||||
|
fx(es, k, "lib/hash/adler32/adler32_test.ww");
|
||||||
|
fx(es, k, "lib/hash/crc16/crc16_test.ww");
|
||||||
|
fx(es, k, "lib/hash/crc32/crc32_test.ww");
|
||||||
|
fx(es, k, "lib/hash/crc64/crc64_test.ww");
|
||||||
|
fx(es, k, "lib/hash/siphash/siphash_test.ww");
|
||||||
|
fx(es, k, "lib/math/checked/checked_test.ww");
|
||||||
|
fx(es, k, "lib/math/random/random_test.ww");
|
||||||
|
fx(es, k, "lib/memio/memiotest.ww");
|
||||||
|
fx(es, k, "lib/os/ostest.ww");
|
||||||
|
pr(es, k, "package main;\nimport os.exec;\nfn main() i32 = { return 0; };\n",
|
||||||
|
"package exec;", "lib/os/exec");
|
||||||
|
fx(es, k, "lib/regex/regex_test.ww");
|
||||||
|
fx(es, k, "lib/strconv/test/ftostest.ww");
|
||||||
|
fx(es, k, "lib/strconv/test/stoftest.ww");
|
||||||
|
fx(es, k, "lib/strconv/test/inttest.ww");
|
||||||
|
fx(es, k, "lib/strings/stringstest.ww");
|
||||||
|
fx(es, k, "lib/temp/temptest.ww");
|
||||||
|
fx(es, k, "lib/time/timetest.ww");
|
||||||
|
fx(es, k, "lib/bufio/bufiotest.ww");
|
||||||
|
fx(es, k, "lib/fmt/fmttest.ww");
|
||||||
|
pr(es, k, "package main;\nimport sort;\nfn main() i32 = { return 0; };\n",
|
||||||
|
"package sort;", "lib/sort");
|
||||||
|
pr(es, k, "package main;\nimport path;\nfn main() i32 = { return 0; };\n",
|
||||||
|
"package path;", "lib/path");
|
||||||
|
pr(es, k, "package main;\nimport endian;\nfn main() i32 = { return 0; };\n",
|
||||||
|
"package endian;", "lib/endian");
|
||||||
|
pr(es, k, "package main;\nimport net;\nfn main() i32 = { return 0; };\n",
|
||||||
|
"package net;", "lib/net");
|
||||||
|
pr(es, k, "package main;\nimport hash;\nfn main() i32 = { return 0; };\n",
|
||||||
|
"package hash;", "lib/hash");
|
||||||
|
// fnv lives off the driver's default root (lib/hash/fnv)
|
||||||
|
pri(es, k, "package main;\nimport fnv;\nfn main() i32 = { return 0; };\n",
|
||||||
|
"lib/hash/fnv", "package fnv;", "lib/hash/fnv");
|
||||||
|
// sentinel is a fn, not `package math;` — lib/math is also package
|
||||||
|
// math, so a silent fallback there would still match
|
||||||
|
pr(es, k, "package main;\nimport crypto.math;\nfn main() i32 = { return 0; };\n",
|
||||||
|
"fn rotl32", "lib/crypto/math");
|
||||||
|
pr(es, k, "package main;\nimport c.libc;\nfn main() i32 = { return 0; };\n",
|
||||||
|
"package libc;", "lib/c/libc");
|
||||||
|
// root-only, ZERO-dep build -S: the one driver-leg edge every
|
||||||
|
// import probe misses (dep-count-0 unit composition). Folded in
|
||||||
|
// from the retired 815/940/951 driver-parity carriers. No
|
||||||
|
// sentinel: there is no dep unit to prove.
|
||||||
|
pr(es, k, "package main;\nfn main() i32 = { return 0; };\n", "", "");
|
||||||
|
fx(es, k, "lib/ascii/asciitest.ww");
|
||||||
|
fx(es, k, "lib/encoding/base64/base64_test.ww");
|
||||||
|
fx(es, k, "lib/errors/errnotest.ww");
|
||||||
|
fx(es, k, "lib/log/logtest.ww");
|
||||||
|
fx(es, k, "lib/os/stattest.ww");
|
||||||
|
// toktest/asttest resolve `import syntax` via -I lib/ww, cf 905
|
||||||
|
fxi(es, k, "lib/ww/syntax/toktest.ww", "lib/ww");
|
||||||
|
fxi(es, k, "lib/ww/syntax/asttest.ww", "lib/ww");
|
||||||
|
fx(es, k, "lib/crypto/sha256/sha256_test.ww");
|
||||||
|
fx(es, k, "lib/fnmatch/fnmatchtest.ww");
|
||||||
|
fx(es, k, "lib/shlex/shlextest.ww");
|
||||||
|
assert(n == NENTEXPECT);
|
||||||
|
return es;
|
||||||
|
};
|
||||||
|
|
||||||
|
// every lib/ dir holding .ww source must be accounted for — enrolled in
|
||||||
|
// the corpus (fixture dirname or probe moddir) or on this covered list;
|
||||||
|
// otherwise a new module ships with zero byte-id coverage.
|
||||||
|
fn covered() []str = {
|
||||||
|
let cs: []str = [];
|
||||||
|
// selfhost-embedded: byte-id'd by the 990-997 gates
|
||||||
|
append(cs, "lib/io");
|
||||||
|
append(cs, "lib/math");
|
||||||
|
append(cs, "lib/rt");
|
||||||
|
append(cs, "lib/types");
|
||||||
|
// module body dragged into the lib/strconv/test fixtures
|
||||||
|
append(cs, "lib/strconv");
|
||||||
|
// #17: the @test runner is AUTO-BUNDLED into every -T sep build,
|
||||||
|
// so every @test fixture byte-ids it cs/ww; 911_attest_record
|
||||||
|
// also compares it directly. It has no _test.ww of its own.
|
||||||
|
append(cs, "lib/test");
|
||||||
|
return cs;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn fail(label: str, why: str) void = {
|
||||||
|
let m: str = strings.concat("lib_byteid FAIL: ", label, " -- ", why, "\n");
|
||||||
|
os.write(2, m.ptr, m.len: u64);
|
||||||
|
assert(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn dirnameof(p: str) str = {
|
||||||
|
let last: i32 = -1;
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < p.len) {
|
||||||
|
if (p[i] == '/') { last = i; };
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
assert(last > 0);
|
||||||
|
return strings.sub(p, 0, last);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn basenameof(p: str) str = {
|
||||||
|
let last: i32 = -1;
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < p.len) {
|
||||||
|
if (p[i] == '/') { last = i; };
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
return strings.sub(p, last + 1, p.len);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn stripext(base: str) str = {
|
||||||
|
assert(strings.hassuffix(base, ".ww"));
|
||||||
|
return strings.sub(base, 0, base.len - 3);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Concatenate every per-package .s under `sepdir` 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")) {
|
||||||
|
let body: str = testenv.readfile(
|
||||||
|
strings.concat(sepdir, "/", names[i]));
|
||||||
|
out = strings.concat(out, body);
|
||||||
|
};
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn buildstage(td: str, name: str, drv: str, sub: str, incs: []str,
|
||||||
|
stem: str, base: str) bool = {
|
||||||
|
let av: []str = [];
|
||||||
|
append(av, drv);
|
||||||
|
append(av, sub);
|
||||||
|
append(av, "-S");
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < incs.len) {
|
||||||
|
append(av, "-I");
|
||||||
|
append(av, incs[i]);
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
append(av, "-o");
|
||||||
|
append(av, stem);
|
||||||
|
append(av, base);
|
||||||
|
let co: testenv.commandout;
|
||||||
|
testenv.runcommand(td, td, name, av,
|
||||||
|
(180i64 * (time.second: i64)): time.duration, &co);
|
||||||
|
return co.termination == exec.termination.EXIT && co.code == 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn checkone(e: *ent) void = {
|
||||||
|
let td: str = testenv.fresh();
|
||||||
|
let label: str = "import-probe";
|
||||||
|
if (e.fixture.len != 0) { label = e.fixture; }
|
||||||
|
else { if (e.moddir.len != 0) { label = e.moddir; }; };
|
||||||
|
|
||||||
|
let base: str = "probe.ww";
|
||||||
|
if (e.fixture.len != 0) {
|
||||||
|
base = basenameof(e.fixture);
|
||||||
|
testenv.writefile(strings.concat(td, "/", base),
|
||||||
|
testenv.readfile(strings.concat(testenv.repo(), "/",
|
||||||
|
e.fixture)));
|
||||||
|
} else {
|
||||||
|
testenv.writefile(strings.concat(td, "/", base), e.probe);
|
||||||
|
};
|
||||||
|
|
||||||
|
// dirname(fixture) leads the search path so bare same-module
|
||||||
|
// imports resolve as they do under the in-tree driver run.
|
||||||
|
let incs: []str = [];
|
||||||
|
if (e.fixture.len != 0) {
|
||||||
|
append(incs, strings.concat(testenv.repo(), "/",
|
||||||
|
dirnameof(e.fixture)));
|
||||||
|
};
|
||||||
|
if (e.inc.len != 0) {
|
||||||
|
append(incs, strings.concat(testenv.repo(), "/", e.inc));
|
||||||
|
};
|
||||||
|
|
||||||
|
// @test fixtures are main-less, so `test -S` carries -T
|
||||||
|
// (synthesizes the entry + auto-bundles lib/test); import probes
|
||||||
|
// carry their own fn main(), which -T loud-rejects (910), so they
|
||||||
|
// `build -S`.
|
||||||
|
let sub: str = "test";
|
||||||
|
if (e.fixture.len == 0) { sub = "build"; };
|
||||||
|
let stem: str = stripext(base);
|
||||||
|
let stemc: str = strings.concat(td, "/c_", stem);
|
||||||
|
let stemw: str = strings.concat(td, "/w_", stem);
|
||||||
|
|
||||||
|
let ce: bool = buildstage(td, "cstage", testenv.driver("ww"), sub,
|
||||||
|
incs, stemc, base);
|
||||||
|
let we: bool = buildstage(td, "wwstage", testenv.driver("ww_ww"), sub,
|
||||||
|
incs, stemw, base);
|
||||||
|
|
||||||
|
// the cstage build resolves the units; its __root.unit.ww proves
|
||||||
|
// resolution ran. WWREJECT still requires cstage to compile.
|
||||||
|
if (!ce || !testenv.exists(strings.concat(stemc,
|
||||||
|
".sepwork/__root.unit.ww"))) {
|
||||||
|
fail(label, "cstage produced no resolved unit");
|
||||||
|
};
|
||||||
|
|
||||||
|
// probe coverage: the driver SILENTLY SKIPS an unresolvable
|
||||||
|
// import, shrinking the probe to an empty main that byte-ids
|
||||||
|
// trivially. The sentinel must appear in some per-package
|
||||||
|
// .unit.ww to prove the module landed.
|
||||||
|
if (e.sentinel.len != 0) {
|
||||||
|
let sepc: str = strings.concat(stemc, ".sepwork");
|
||||||
|
let names: []str = testenv.listdir(sepc);
|
||||||
|
let found: bool = false;
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < names.len) {
|
||||||
|
if (strings.hassuffix(names[i], ".unit.ww")) {
|
||||||
|
if (testenv.has(testenv.readfile(
|
||||||
|
strings.concat(sepc, "/", names[i])),
|
||||||
|
e.sentinel)) {
|
||||||
|
found = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
if (!found) {
|
||||||
|
fail(label, "import silently dropped; probe covers nothing");
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (e.mode == MWWREJECT) {
|
||||||
|
if (we) {
|
||||||
|
fail(label, strings.concat("wwstage now compiles this; ",
|
||||||
|
"graduate the ", e.cite, " pin to ID or DIVERGE"));
|
||||||
|
};
|
||||||
|
testenv.clean(td);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (!we) { fail(label, "wwstage rejected"); };
|
||||||
|
|
||||||
|
let cs: str = catasm(strings.concat(stemc, ".sepwork"));
|
||||||
|
let ws: str = catasm(strings.concat(stemw, ".sepwork"));
|
||||||
|
// two empty concats compare equal — that green covers nothing
|
||||||
|
if (cs.len == 0 || ws.len == 0) {
|
||||||
|
fail(label, "empty .s concat");
|
||||||
|
};
|
||||||
|
let idsame: bool = testenv.same(cs, ws);
|
||||||
|
if (e.mode == MID && !idsame) {
|
||||||
|
fail(label, "cs vs ww asm differs (byte-id broken)");
|
||||||
|
};
|
||||||
|
if (e.mode == MDIVERGE && idsame) {
|
||||||
|
fail(label, strings.concat("now byte-identical; graduate the ",
|
||||||
|
e.cite, " pin to ID"));
|
||||||
|
};
|
||||||
|
testenv.clean(td);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn accounted(dir: str, es: []ent, cov: []str) bool = {
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < es.len) {
|
||||||
|
if (es[i].fixture.len != 0) {
|
||||||
|
if (testenv.same(dir, dirnameof(es[i].fixture))) {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
} else { if (es[i].moddir.len != 0) {
|
||||||
|
if (testenv.same(dir, es[i].moddir)) { return true; };
|
||||||
|
}; };
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
i = 0;
|
||||||
|
for (i < cov.len) {
|
||||||
|
if (testenv.same(dir, cov[i])) { return true; };
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Directories under `dir` (repo-relative `rel`) holding .ww source,
|
||||||
|
// excluding generated .sepwork trees.
|
||||||
|
fn wwdirs(dir: str, rel: str) []str = {
|
||||||
|
let out: []str = [];
|
||||||
|
let names: []str = testenv.listdir(dir);
|
||||||
|
let hasww: bool = false;
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < names.len) {
|
||||||
|
let p: str = strings.concat(dir, "/", names[i]);
|
||||||
|
if (testenv.isdir(p)) {
|
||||||
|
if (!strings.hassuffix(names[i], ".sepwork")) {
|
||||||
|
let sub: []str = wwdirs(p,
|
||||||
|
strings.concat(rel, "/", names[i]));
|
||||||
|
let j: i32 = 0;
|
||||||
|
for (j < sub.len) {
|
||||||
|
append(out, sub[j]);
|
||||||
|
j += 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if (strings.hassuffix(names[i], ".ww")) { hasww = true; };
|
||||||
|
};
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
if (hasww) { append(out, rel); };
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn corpuscomplete() void = {
|
||||||
|
let es: []ent = corpus();
|
||||||
|
assert(es.len == NENTEXPECT);
|
||||||
|
let dirs: []str = wwdirs(strings.concat(testenv.repo(), "/lib"), "lib");
|
||||||
|
// an empty scan means the walk itself broke — never pass on that
|
||||||
|
assert(dirs.len > 0);
|
||||||
|
let cov: []str = covered();
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < dirs.len) {
|
||||||
|
if (!accounted(dirs[i], es, cov)) {
|
||||||
|
fail(dirs[i], "un-enrolled lib module; add it to the corpus");
|
||||||
|
};
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn roster() void = {
|
||||||
|
let es: []ent = corpus();
|
||||||
|
assert(es.len == NENTEXPECT);
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < es.len) {
|
||||||
|
checkone(&es[i]);
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
235
test/testenv/testenv.ww
Normal file
235
test/testenv/testenv.ww
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
package testenv;
|
||||||
|
|
||||||
|
// Shared plumbing for ww-native test drivers under test/: subprocess
|
||||||
|
// launch with captured output, file IO, string search, and scratch
|
||||||
|
// ownership. A utility surface lifted from the proven
|
||||||
|
// test/package/package_test.ww idiom — not a framework: callers own
|
||||||
|
// discovery, assertions, and verdicts.
|
||||||
|
|
||||||
|
import os;
|
||||||
|
import os.exec;
|
||||||
|
import strings;
|
||||||
|
import temp;
|
||||||
|
import time;
|
||||||
|
|
||||||
|
export type commandout = struct {
|
||||||
|
termination: exec.termination,
|
||||||
|
code: i32,
|
||||||
|
stdout: str,
|
||||||
|
stderr: str,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn envrequired(name: str) str = {
|
||||||
|
match (os.getenv(name)) {
|
||||||
|
case let value: str => {
|
||||||
|
assert(value.len != 0);
|
||||||
|
return strings.dup(value);
|
||||||
|
};
|
||||||
|
case void => abort("missing test environment");
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// The repository root, provided by the Make target as WW_TEST_REPO.
|
||||||
|
export fn repo() str = { return envrequired("WW_TEST_REPO"); };
|
||||||
|
|
||||||
|
export fn driver(name: str) str = {
|
||||||
|
return strings.concat(repo(), "/out/bin/", name);
|
||||||
|
};
|
||||||
|
|
||||||
|
export fn fresh() str = { return strings.dup(temp.dir()); };
|
||||||
|
|
||||||
|
export fn readfile(path: str) str = {
|
||||||
|
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
|
||||||
|
assert(fd >= 0);
|
||||||
|
let sr: (i64 | os.oserror) = os.filesize(fd);
|
||||||
|
let n: i64 = -1i64;
|
||||||
|
match (sr) {
|
||||||
|
case let v: i64 => n = v;
|
||||||
|
case let e: os.oserror => abort("filesize failed");
|
||||||
|
};
|
||||||
|
assert(n >= 0i64);
|
||||||
|
let b: []u8 = alloc([], (n + 1i64): u64)!;
|
||||||
|
b.len = (n + 1i64): i32;
|
||||||
|
let rr: (i64 | os.oserror) = os.readall(fd, b.ptr, n: u64);
|
||||||
|
os.close(fd);
|
||||||
|
let got: i64 = -1i64;
|
||||||
|
match (rr) {
|
||||||
|
case let v: i64 => got = v;
|
||||||
|
case let e: os.oserror => abort("read failed");
|
||||||
|
};
|
||||||
|
assert(got == n);
|
||||||
|
let ni: i32 = n: i32;
|
||||||
|
b[ni] = 0u8;
|
||||||
|
let out: str;
|
||||||
|
out.ptr = b.ptr;
|
||||||
|
out.len = n: i32;
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
export fn writefile(path: str, content: str) void = {
|
||||||
|
let fd: i32 = os.open(path,
|
||||||
|
os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 384i32);
|
||||||
|
assert(fd >= 0);
|
||||||
|
match (os.writeall(fd, content.ptr, content.len: u64)) {
|
||||||
|
case let n: i64 => assert(n == content.len: i64);
|
||||||
|
case let e: os.oserror => abort("write failed");
|
||||||
|
};
|
||||||
|
assert(os.close(fd) == 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
export fn exists(path: str) bool = {
|
||||||
|
return os.access(path, 0i32) == 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export fn isdir(path: str) bool = {
|
||||||
|
let st: os.filestat;
|
||||||
|
match (os.stat(&st, path)) {
|
||||||
|
case void => {
|
||||||
|
return ((st.mode: u32) & 61440u32) == (os.mode.DIR: u32);
|
||||||
|
};
|
||||||
|
case let e: os.oserror => return false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run argv[0] with `dir` as the working directory, capturing stdout and
|
||||||
|
// stderr into `<root>/<name>.{stdout,stderr}`. Launch and cleanup
|
||||||
|
// failures abort; termination and exit code are the caller's verdict.
|
||||||
|
export fn runcommand(dir: str, root: str, name: str, argv: []str,
|
||||||
|
lifetime: time.duration, out: *commandout) void = {
|
||||||
|
let c: exec.command;
|
||||||
|
c.path = argv[0];
|
||||||
|
c.argv = argv;
|
||||||
|
c.env = os.getenvs();
|
||||||
|
c.dir = dir;
|
||||||
|
c.stdoutpath = strings.concat(root, "/", name, ".stdout");
|
||||||
|
c.stderrpath = strings.concat(root, "/", name, ".stderr");
|
||||||
|
c.deadline = time.add(time.now(time.clock.monotonic), lifetime);
|
||||||
|
c.grace = (100i64 * (time.millisecond: i64)): time.duration;
|
||||||
|
let r: exec.result;
|
||||||
|
exec.run(&c, &r);
|
||||||
|
assert(r.errno == 0 && r.cleanuperrno == 0);
|
||||||
|
out.termination = r.termination;
|
||||||
|
out.code = r.code;
|
||||||
|
out.stdout = readfile(c.stdoutpath);
|
||||||
|
out.stderr = readfile(c.stderrpath);
|
||||||
|
};
|
||||||
|
|
||||||
|
export fn clean(root: str) void = {
|
||||||
|
let av: []str = ["/bin/rm", "-rf", "--", root];
|
||||||
|
let c: exec.command;
|
||||||
|
c.path = av[0];
|
||||||
|
c.argv = av;
|
||||||
|
c.env = os.getenvs();
|
||||||
|
c.dir = "/";
|
||||||
|
c.stdoutpath = strings.concat(root, ".cleanup.stdout");
|
||||||
|
c.stderrpath = strings.concat(root, ".cleanup.stderr");
|
||||||
|
c.deadline = time.add(time.now(time.clock.monotonic), time.second);
|
||||||
|
c.grace = (50i64 * (time.millisecond: i64)): time.duration;
|
||||||
|
let r: exec.result;
|
||||||
|
exec.run(&c, &r);
|
||||||
|
assert(r.termination == exec.termination.EXIT && r.code == 0);
|
||||||
|
assert(os.remove(c.stdoutpath) == 0);
|
||||||
|
assert(os.remove(c.stderrpath) == 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
export fn pos(haystack: str, needle: str) i32 = {
|
||||||
|
match (strings.index(haystack, needle)) {
|
||||||
|
case let n: i32 => return n;
|
||||||
|
case void => return -1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export fn has(haystack: str, needle: str) bool = {
|
||||||
|
return pos(haystack, needle) >= 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export fn same(a: str, b: str) bool = {
|
||||||
|
if (a.len != b.len) { return false; };
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < a.len) {
|
||||||
|
if (a[i] != b[i]) { return false; };
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export fn occurrences(haystack: str, needle: str) i32 = {
|
||||||
|
if (needle.len == 0 || haystack.len < needle.len) { return 0; };
|
||||||
|
let count: i32 = 0;
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i + needle.len <= haystack.len) {
|
||||||
|
let j: i32 = 0;
|
||||||
|
let hit: bool = true;
|
||||||
|
for (j < needle.len) {
|
||||||
|
if (haystack[i + j] != needle[j]) { hit = false; j = needle.len; }
|
||||||
|
else { j += 1; };
|
||||||
|
};
|
||||||
|
if (hit) { count += 1; };
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
return count;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Byte-lexicographic order, so directory listings sort identically on
|
||||||
|
// every host regardless of locale.
|
||||||
|
export fn lexless(a: str, b: str) bool = {
|
||||||
|
let n: i32 = a.len;
|
||||||
|
if (b.len < n) { n = b.len; };
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < n) {
|
||||||
|
if (a[i] != b[i]) { return a[i] < b[i]; };
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
return a.len < b.len;
|
||||||
|
};
|
||||||
|
|
||||||
|
def DIRBUF: i32 = 16384;
|
||||||
|
|
||||||
|
// Entry names of `path` (excluding . and ..), byte-sorted. Aborts on an
|
||||||
|
// unreadable directory or a malformed dirent stream.
|
||||||
|
export fn listdir(path: str) []str = {
|
||||||
|
let names: []str = alloc([], 16u64)!;
|
||||||
|
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
|
||||||
|
assert(fd >= 0);
|
||||||
|
let buf: []u8 = alloc([], DIRBUF: u64)!;
|
||||||
|
buf.len = DIRBUF;
|
||||||
|
let n: i64 = os.getdents64(fd, buf.ptr, buf.len: u64);
|
||||||
|
for (n > 0i64) {
|
||||||
|
let off: i32 = 0;
|
||||||
|
for (off < n: i32) {
|
||||||
|
// linux_dirent64: d_reclen at 16..17, name at 19.
|
||||||
|
let reclen: i32 = (buf[off + 16]: i32)
|
||||||
|
+ ((buf[off + 17]: i32) * 256);
|
||||||
|
assert(reclen >= 20 && off + reclen <= n: i32);
|
||||||
|
let nb: []u8 = alloc([], 256u64)!;
|
||||||
|
let k: i32 = 0;
|
||||||
|
for (buf[off + 19 + k] != 0u8) {
|
||||||
|
append(nb, buf[off + 19 + k]);
|
||||||
|
k += 1;
|
||||||
|
};
|
||||||
|
let name: str = strings.frombytes(nb);
|
||||||
|
if (!same(name, ".") && !same(name, "..")) {
|
||||||
|
append(names, name);
|
||||||
|
};
|
||||||
|
off += reclen;
|
||||||
|
};
|
||||||
|
n = os.getdents64(fd, buf.ptr, buf.len: u64);
|
||||||
|
};
|
||||||
|
assert(n == 0i64);
|
||||||
|
assert(os.close(fd) == 0);
|
||||||
|
// insertion sort; listings are small
|
||||||
|
let i: i32 = 1;
|
||||||
|
for (i < names.len) {
|
||||||
|
let v: str = names[i];
|
||||||
|
let j: i32 = i;
|
||||||
|
for (j > 0) {
|
||||||
|
if (lexless(v, names[j - 1])) {
|
||||||
|
names[j] = names[j - 1];
|
||||||
|
j -= 1;
|
||||||
|
} else { break; };
|
||||||
|
};
|
||||||
|
names[j] = v;
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
return names;
|
||||||
|
};
|
||||||
@@ -1,534 +0,0 @@
|
|||||||
/*
|
|
||||||
* 989_lib_byteid — cstage-vs-wwstage byte-identity gate over the whole
|
|
||||||
* lib/ surface, and the ONE owner of wwstage-DRIVER-leg byte identity.
|
|
||||||
*
|
|
||||||
* The bootstrap gates (991-995) byte-id only the modules the selfhost
|
|
||||||
* tools import (ascii/bytes/errors/io/math/memio/os/rt/strconv/strings/
|
|
||||||
* time/types/utf8 + lex/parse/wcc/ww). Every other lib/ module compiles
|
|
||||||
* through the CSTAGE driver alone elsewhere, so a cs≠ww divergence
|
|
||||||
* there ships gate-green — regex.finish was cs≠ww for weeks this way
|
|
||||||
* (task #21, FC0). This gate closes the class: each lib test fixture is
|
|
||||||
* sep-built through BOTH driver stages and the per-package asm compared.
|
|
||||||
*
|
|
||||||
* #94 sep layout: the gate drives `ww` (cstage) and `ww_ww` (wwstage).
|
|
||||||
* Under sep EACH package compiles to its OWN <stem>.sepwork/<pkg>.s — the
|
|
||||||
* lib body lands in <pkg>.s, NOT in __root.s (which holds only the test
|
|
||||||
* fixture / probe main). So the byte-id MUST concat EVERY <pkg>.s (the
|
|
||||||
* imported lib body + lib/test's auto-bundled runner included), never
|
|
||||||
* just __root.s — comparing the root alone would byte-id the harness
|
|
||||||
* wrapper while the engine ships uncovered (the green-covering-nothing
|
|
||||||
* trap this gate exists to close).
|
|
||||||
*
|
|
||||||
* Three pinned outcomes, loud over blind (task #21 ruling):
|
|
||||||
* ID — byte-identical across all per-package .s, zero tolerance.
|
|
||||||
* DIVERGE — known cs≠ww divergence, documented in task #59 with the
|
|
||||||
* first-diff symbol. Both stages must still compile and
|
|
||||||
* the concatenated asm must still DIFFER: when a fix lands,
|
|
||||||
* the entry fails loudly demanding graduation to ID rather
|
|
||||||
* than silently widening or narrowing coverage.
|
|
||||||
* WWREJECT — cstage compiles, wwstage errors (front-end
|
|
||||||
* gap, task #59). Graduation is pinned the same way.
|
|
||||||
*
|
|
||||||
* Modules with no test fixture and no embedding (sort/path/endian/net/
|
|
||||||
* hash/fnv/crypto.math/c.libc) are covered via import-probe stubs:
|
|
||||||
* `import` drags the whole module into its own sep package, so both
|
|
||||||
* cgens chew the full module body. The driver silently skips an
|
|
||||||
* unresolvable import, so each probe carries a sentinel proving the
|
|
||||||
* body landed in a per-package .unit.ww; and a corpus-completeness scan
|
|
||||||
* fails loudly on any lib/ dir not enrolled here, so new modules can't
|
|
||||||
* ship uncovered.
|
|
||||||
*
|
|
||||||
* Compile+cmp only; -o redirects every artifact to a getpid-keyed
|
|
||||||
* /tmp directory for isolation.
|
|
||||||
*
|
|
||||||
* 9xx is full; shares the 989 prefix per the 989_sha256 precedent (the
|
|
||||||
* `short` name keys the binary).
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
enum { M_ID, M_DIVERGE, M_WWREJECT, NENT_EXPECT = 44 };
|
|
||||||
|
|
||||||
struct ent {
|
|
||||||
const char *fixture; /* repo-relative .ww; NULL → probe entry */
|
|
||||||
const char *probe; /* inline import-probe source */
|
|
||||||
const char *inc; /* extra -I dir beyond dirname(fixture) */
|
|
||||||
int mode;
|
|
||||||
const char *cite; /* task #59 entry for non-ID modes */
|
|
||||||
/* probe-only: the driver SILENTLY SKIPS an unresolvable import
|
|
||||||
* (cmd/ww/main.c locate_import), so a probe whose module fails
|
|
||||||
* to resolve shrinks to an empty main and byte-id's trivially —
|
|
||||||
* green while covering nothing. sentinel must appear in the
|
|
||||||
* resolved unit to prove the module body actually landed.
|
|
||||||
* (Fixtures self-protect: they call module symbols, so a drop
|
|
||||||
* is a loud w6c reject.) moddir feeds the completeness check. */
|
|
||||||
const char *sentinel;
|
|
||||||
const char *moddir;
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct ent ents[] = {
|
|
||||||
/* -------- byte-identical: zero tolerance ----------------- */
|
|
||||||
{ .fixture = "lib/bytes/bytestest.ww", .mode = M_ID },
|
|
||||||
{ .fixture = "lib/dirs/dirstest.ww", .mode = M_ID },
|
|
||||||
{ .fixture = "lib/encoding/base32/base32_test.ww", .mode = M_ID },
|
|
||||||
/* graduated from #59.4 DIVERGE by the #22a N_DOT tagged-arg
|
|
||||||
* cursor arm (ww widened t.N args with a clamped tag 0 — the
|
|
||||||
* base .s diff is exactly that PUSHQ pair, 2 sites);
|
|
||||||
* runtime-correct per 979_hex_run */
|
|
||||||
{ .fixture = "lib/encoding/hex/hextest.ww", .mode = M_ID },
|
|
||||||
{ .fixture = "lib/encoding/utf8/utf8test.ww", .mode = M_ID },
|
|
||||||
{ .fixture = "lib/getopt/getopttest.ww", .mode = M_ID },
|
|
||||||
{ .fixture = "lib/hash/adler32/adler32_test.ww", .mode = M_ID },
|
|
||||||
{ .fixture = "lib/hash/crc16/crc16_test.ww", .mode = M_ID },
|
|
||||||
{ .fixture = "lib/hash/crc32/crc32_test.ww", .mode = M_ID },
|
|
||||||
{ .fixture = "lib/hash/crc64/crc64_test.ww", .mode = M_ID },
|
|
||||||
/* graduated from #59.7 DIVERGE by the #61 *[N]T index-stride fix */
|
|
||||||
{ .fixture = "lib/hash/siphash/siphash_test.ww", .mode = M_ID },
|
|
||||||
{ .fixture = "lib/math/checked/checked_test.ww", .mode = M_ID },
|
|
||||||
{ .fixture = "lib/math/random/random_test.ww", .mode = M_ID },
|
|
||||||
{ .fixture = "lib/memio/memiotest.ww", .mode = M_ID },
|
|
||||||
{ .fixture = "lib/os/ostest.ww", .mode = M_ID },
|
|
||||||
{ .probe = "package main;\nimport os.exec;\nfn main() i32 = { return 0; };\n",
|
|
||||||
.mode = M_ID, .sentinel = "package exec;", .moddir = "lib/os/exec" },
|
|
||||||
{ .fixture = "lib/regex/regex_test.ww", .mode = M_ID },
|
|
||||||
{ .fixture = "lib/strconv/test/ftostest.ww", .mode = M_ID },
|
|
||||||
/* graduated from #59.10 DIVERGE by the #62 float-literal fold fix
|
|
||||||
* (wwstage lexer now folds through strconv.stof64, matching
|
|
||||||
* cstage's strtod bit-for-bit) */
|
|
||||||
{ .fixture = "lib/strconv/test/stoftest.ww", .mode = M_ID },
|
|
||||||
{ .fixture = "lib/strconv/test/inttest.ww", .mode = M_ID },
|
|
||||||
{ .fixture = "lib/strings/stringstest.ww", .mode = M_ID },
|
|
||||||
{ .fixture = "lib/temp/temptest.ww", .mode = M_ID },
|
|
||||||
{ .fixture = "lib/time/timetest.ww", .mode = M_ID },
|
|
||||||
/* graduated from #59.2 DIVERGE by the #129 sretretsize fixes
|
|
||||||
* (same-module alias priority + callsretsize callee-context swap;
|
|
||||||
* bufio imports io so cross-module io.stream returns were spuriously
|
|
||||||
* sret-classified, producing divergent caller prologues) */
|
|
||||||
{ .fixture = "lib/bufio/bufiotest.ww", .mode = M_ID },
|
|
||||||
/* graduated from #59.6 DIVERGE by the #129 same fix (fmt imports
|
|
||||||
* io transitively through bufio; sretretsize curmod swap closed
|
|
||||||
* the getopt.printusage spurious LEAQ DI caller divergence) */
|
|
||||||
{ .fixture = "lib/fmt/fmttest.ww", .mode = M_ID },
|
|
||||||
/* fixtureless modules, import-probe shape */
|
|
||||||
{ .probe = "package main;\nimport sort;\nfn main() i32 = { return 0; };\n",
|
|
||||||
.mode = M_ID, .sentinel = "package sort;", .moddir = "lib/sort" },
|
|
||||||
/* #142 graduated (the old #120/#29 global-slice-const blame was
|
|
||||||
* stale — that family drained earlier): path declares three
|
|
||||||
* structurally identical !void error singletons, and a VALUE use
|
|
||||||
* (`return too_long;`) stamped the flattened BODY type, so
|
|
||||||
* flatvariantidxt loud-rejected the ambiguity. The SK_TYPE value
|
|
||||||
* arms now stamp the per-decl NAMED (the #66 N_STRUCTLIT
|
|
||||||
* precedent). Runtime pin: r142_singleton_value_named. */
|
|
||||||
{ .probe = "package main;\nimport path;\nfn main() i32 = { return 0; };\n",
|
|
||||||
.mode = M_ID, .cite = "#142 graduated by the SK_TYPE NAMED value stamp",
|
|
||||||
.sentinel = "package path;", .moddir = "lib/path" },
|
|
||||||
{ .probe = "package main;\nimport endian;\nfn main() i32 = { return 0; };\n",
|
|
||||||
.mode = M_ID, .sentinel = "package endian;", .moddir = "lib/endian" },
|
|
||||||
{ .probe = "package main;\nimport net;\nfn main() i32 = { return 0; };\n",
|
|
||||||
.mode = M_ID, .sentinel = "package net;", .moddir = "lib/net" },
|
|
||||||
{ .probe = "package main;\nimport hash;\nfn main() i32 = { return 0; };\n",
|
|
||||||
.mode = M_ID, .sentinel = "package hash;", .moddir = "lib/hash" },
|
|
||||||
/* fnv lives off the driver's default root (lib/hash/fnv) */
|
|
||||||
{ .probe = "package main;\nimport fnv;\nfn main() i32 = { return 0; };\n",
|
|
||||||
.inc = "lib/hash/fnv",
|
|
||||||
.mode = M_ID, .sentinel = "package fnv;", .moddir = "lib/hash/fnv" },
|
|
||||||
/* sentinel is a fn, not `package math;` — lib/math is also
|
|
||||||
* package math, so a silent fallback there would still match */
|
|
||||||
{ .probe = "package main;\nimport crypto.math;\nfn main() i32 = { return 0; };\n",
|
|
||||||
.mode = M_ID, .sentinel = "fn rotl32", .moddir = "lib/crypto/math" },
|
|
||||||
{ .probe = "package main;\nimport c.libc;\nfn main() i32 = { return 0; };\n",
|
|
||||||
.mode = M_ID, .sentinel = "package libc;", .moddir = "lib/c/libc" },
|
|
||||||
/* root-only, ZERO-dep build -S: the one driver-leg edge every
|
|
||||||
* import probe misses (dep-count-0 unit composition). Folded in
|
|
||||||
* from the retired 815/940/951 driver-parity carriers, whose
|
|
||||||
* content byteid the corpus twins (r815_/r940_/r951_) own via
|
|
||||||
* test-data-byteid. No sentinel: there is no dep unit to prove. */
|
|
||||||
{ .probe = "package main;\nfn main() i32 = { return 0; };\n",
|
|
||||||
.mode = M_ID },
|
|
||||||
/* -------- documented-allowed cs≠ww (task #59) ------------ */
|
|
||||||
{ .fixture = "lib/ascii/asciitest.ww",
|
|
||||||
.mode = M_ID, .cite = "#59.1 graduated by #146 str==" },
|
|
||||||
/* #59.2 bufio graduated to M_ID above (#129 fix) */
|
|
||||||
/* #59.3 base64 graduated to M_ID (pushargsrev N_DOT array-field
|
|
||||||
* default-hi fix — clear()'s borrow no longer zero-length) */
|
|
||||||
{ .fixture = "lib/encoding/base64/base64_test.ww",
|
|
||||||
.mode = M_ID },
|
|
||||||
/* #59.4 hextest graduated to M_ID above (#22a reviewer fixes) */
|
|
||||||
/* #59.5 graduated: the wwstage nodeisstr/nodeisslice recognizers
|
|
||||||
* fell to false for an indirect fn-pointer callee (`(*f)()`) and
|
|
||||||
* for module-global let/const idents, so a 3-word str/slice value
|
|
||||||
* pushed one word (or zero-filled its widen box) and cgcall
|
|
||||||
* under-drained; both now fall back to the checker stamp. Runtime
|
|
||||||
* pins: r595_indirect_call_str_arg, globalconst_{str,slice}_widen. */
|
|
||||||
{ .fixture = "lib/errors/errnotest.ww",
|
|
||||||
.mode = M_ID, .cite = "#59.5 graduated by the stamp-keyed recognizers" },
|
|
||||||
/* #59.6 fmt graduated to M_ID above (#129 fix) */
|
|
||||||
/* #59.7 siphash graduated to M_ID above (#61 fix) */
|
|
||||||
/* #59.8 graduated: the wwstage variadic call classification was
|
|
||||||
* name-keyed — a local fn-ptr field callee whose base shadowed the
|
|
||||||
* current module name (lib/log's `log.println(log, args...)`)
|
|
||||||
* picked the module fn's signature (nfixed off by one; the fixed
|
|
||||||
* arg boxed into the gather, the spread emitted as zeros), and a
|
|
||||||
* no-collision fn-ptr callee missed entirely (raw N_SPREAD leaked
|
|
||||||
* as one $0 word). fnptrcalleetfn now resolves local fn-ptr
|
|
||||||
* callees for CALL target and arg prep alike. Runtime pin:
|
|
||||||
* r598_fnptr_field_variadic. */
|
|
||||||
{ .fixture = "lib/log/logtest.ww",
|
|
||||||
.mode = M_ID, .cite = "#59.8 graduated by the local-first callee resolver" },
|
|
||||||
/* #59.9 graduated: the wwstage checker never typed an N_BIN operand
|
|
||||||
* of `as`, so an enum OR-fold reached cgen unstamped and lowered as
|
|
||||||
* a phantom tagged assert (unconditional exit 1). checkisas now
|
|
||||||
* types the N_BIN lhs and the post-order restamp preserves the enum
|
|
||||||
* stamp. Runtime pin: r599_enum_orfold_as. */
|
|
||||||
{ .fixture = "lib/os/stattest.ww",
|
|
||||||
.mode = M_ID, .cite = "#59.9 graduated by the checkisas N_BIN stamp" },
|
|
||||||
/* #59.10 stoftest graduated to M_ID above (#62 fix) */
|
|
||||||
{ .fixture = "lib/ww/syntax/toktest.ww", .inc = "lib/ww",
|
|
||||||
.mode = M_ID, .cite = "#59.11 graduated by #146 str==" },
|
|
||||||
/* toktest/asttest resolve `import syntax` via -I lib/ww, cf 905 */
|
|
||||||
{ .fixture = "lib/ww/syntax/asttest.ww", .inc = "lib/ww",
|
|
||||||
.mode = M_ID, .cite = "#59.12 graduated by #146 str==" },
|
|
||||||
/* -------- wwstage front-end gaps (task #59) -------------- */
|
|
||||||
/* #59.13 graduated: wwstage struct embedding (parser three member
|
|
||||||
* forms, checker tinfo flatten + promoted-name walkers, cgen
|
|
||||||
* registerstruct embed descend) — the hash.hash embed compiles
|
|
||||||
* byte-identically end to end. Runtime pins: r5913_*. */
|
|
||||||
{ .fixture = "lib/crypto/sha256/sha256_test.ww",
|
|
||||||
.mode = M_ID, .cite = "#59.13 graduated by the embed port" },
|
|
||||||
/* #59.14 graduated to M_ID by #27: the #27b enum-as-int cgen fix
|
|
||||||
* (value passthrough, not a tagged assertion) + the fnmatch.ww:126
|
|
||||||
* `'\\': u8` explicit cast (the rune-lit→u8 narrow w6c_ww rejects at
|
|
||||||
* a checked position) make w6c_ww compile fnmatch byte-identically. */
|
|
||||||
{ .fixture = "lib/fnmatch/fnmatchtest.ww",
|
|
||||||
.mode = M_ID, .cite = "#59.14 graduated by #27" },
|
|
||||||
/* #59.15 shlex graduated to M_ID by B-full Layer 1 (#14 nominal
|
|
||||||
* typeeqast): the cross-module bare-vs-qualified variant forward
|
|
||||||
* w6c_ww over-rejected now compiles byte-identically. */
|
|
||||||
{ .fixture = "lib/shlex/shlextest.ww",
|
|
||||||
.mode = M_ID, .cite = "#59.15 graduated by #14 nominal typeeqast" },
|
|
||||||
{ .fixture = NULL },
|
|
||||||
};
|
|
||||||
|
|
||||||
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
|
|
||||||
check_one(const char *bin, const char *cwd, const struct ent *e, int idx)
|
|
||||||
{
|
|
||||||
const char *label = e->fixture ? e->fixture : "import-probe";
|
|
||||||
char td[64], cmd[8192];
|
|
||||||
int rc = -1;
|
|
||||||
|
|
||||||
snprintf(td, sizeof td, "/tmp/wwlbi_%d_%d", getpid(), idx);
|
|
||||||
if (mkdir(td, 0755) != 0) {
|
|
||||||
perror(td);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
char base[256];
|
|
||||||
if (e->fixture) {
|
|
||||||
const char *b = strrchr(e->fixture, '/');
|
|
||||||
snprintf(base, sizeof base, "%s", b ? b + 1 : e->fixture);
|
|
||||||
snprintf(cmd, sizeof cmd, "cp %s/%s %s/%s", cwd, e->fixture,
|
|
||||||
td, base);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "lib_byteid FAIL: %s — cp\n", label);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
snprintf(base, sizeof base, "probe.ww");
|
|
||||||
char p[512];
|
|
||||||
snprintf(p, sizeof p, "%s/probe.ww", td);
|
|
||||||
FILE *f = fopen(p, "wb");
|
|
||||||
if (!f) goto out;
|
|
||||||
fputs(e->probe, f);
|
|
||||||
fclose(f);
|
|
||||||
if (e->moddir)
|
|
||||||
label = e->moddir;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* dirname(fixture) leads the search path so bare same-module
|
|
||||||
* imports (`import base32` from lib/encoding/base32) resolve as
|
|
||||||
* they do under the in-tree driver run. */
|
|
||||||
char incs[2048] = "";
|
|
||||||
if (e->fixture) {
|
|
||||||
const char *slash = strrchr(e->fixture, '/');
|
|
||||||
int n = (int)(slash - e->fixture);
|
|
||||||
snprintf(incs, sizeof incs, "-I %s/%.*s", cwd, n, e->fixture);
|
|
||||||
}
|
|
||||||
if (e->inc) {
|
|
||||||
size_t n = strlen(incs);
|
|
||||||
snprintf(incs + n, sizeof incs - n, " -I %s/%s", cwd, e->inc);
|
|
||||||
}
|
|
||||||
|
|
||||||
char stem[512];
|
|
||||||
snprintf(stem, sizeof stem, "%s", base);
|
|
||||||
char *sd = strrchr(stem, '.');
|
|
||||||
if (sd) *sd = '\0';
|
|
||||||
|
|
||||||
/* Two compiler-only sep driver builds. @test fixtures are main-less,
|
|
||||||
* so `test -S ` carries -T (synthesizes the entry + keeps @test fns + auto-
|
|
||||||
* bundles lib/test's run() callee) and emits per-package asm without
|
|
||||||
* a harness run; import-probes carry their own `fn main()`, which -T
|
|
||||||
* loud-rejects (910), so they `build -S ` (non-T). Each stage's
|
|
||||||
* artifacts land under its own -o stem so out/ and the source tree
|
|
||||||
* stay clean. */
|
|
||||||
const char *sub = e->fixture ? "test -S " : "build -S ";
|
|
||||||
char stemc[600], stemw[600];
|
|
||||||
snprintf(stemc, sizeof stemc, "%s/c_%s", td, stem);
|
|
||||||
snprintf(stemw, sizeof stemw, "%s/w_%s", td, stem);
|
|
||||||
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"cd %s && timeout 180 %s/ww %s %s -o %s %s "
|
|
||||||
">/dev/null 2>&1", td, bin, sub, incs, stemc, base);
|
|
||||||
int ce = runwait(cmd);
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"cd %s && timeout 180 %s/ww_ww %s %s -o %s %s "
|
|
||||||
">/dev/null 2>&1", td, bin, sub, incs, stemw, base);
|
|
||||||
int we = runwait(cmd);
|
|
||||||
|
|
||||||
/* the cstage build resolves the units (root + each package);
|
|
||||||
* its __root.unit.ww proves resolution ran. WWREJECT still requires
|
|
||||||
* cstage to compile, so this holds for every mode. */
|
|
||||||
char rootu[760];
|
|
||||||
snprintf(rootu, sizeof rootu, "%s.sepwork/__root.unit.ww", stemc);
|
|
||||||
if (ce != 0 || access(rootu, 0) != 0) {
|
|
||||||
fprintf(stderr, "lib_byteid FAIL: %s — cstage produced "
|
|
||||||
"no resolved unit\n", label);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* probe coverage: the driver SILENTLY SKIPS an unresolvable import
|
|
||||||
* (cmd/ww/main.c locate_import), shrinking the probe to an empty main
|
|
||||||
* that byte-ids trivially. The sentinel must appear in some per-
|
|
||||||
* package .unit.ww (the lib body's own sep unit, not __root) to prove
|
|
||||||
* the module landed. Fixtures self-protect by calling module symbols. */
|
|
||||||
if (e->sentinel) {
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"grep -Fq -- '%s' %s.sepwork/*.unit.ww >/dev/null 2>&1",
|
|
||||||
e->sentinel, stemc);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "lib_byteid FAIL: %s — import "
|
|
||||||
"silently dropped (no \"%s\" in any per-package "
|
|
||||||
"unit); probe covers nothing\n", label, e->sentinel);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e->mode == M_WWREJECT) {
|
|
||||||
if (we == 0) {
|
|
||||||
fprintf(stderr, "lib_byteid FAIL: %s — wwstage now "
|
|
||||||
"compiles this; graduate the %s pin to ID or "
|
|
||||||
"DIVERGE\n", label, e->cite);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
rc = 0;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (we != 0) {
|
|
||||||
fprintf(stderr, "lib_byteid FAIL: %s — wwstage rejected\n",
|
|
||||||
label);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* byte-id over EVERY per-package .s (lib body + lib/test included),
|
|
||||||
* never just __root.s — see the header note. Identical package sets
|
|
||||||
* both stages, so the *.s glob (shell-sorted) concatenates in the
|
|
||||||
* same order on each side. */
|
|
||||||
char cs[800], ws[800];
|
|
||||||
snprintf(cs, sizeof cs, "%s/all_c.s", td);
|
|
||||||
snprintf(ws, sizeof ws, "%s/all_w.s", td);
|
|
||||||
snprintf(cmd, sizeof cmd, "cat %s.sepwork/*.s > %s 2>/dev/null",
|
|
||||||
stemc, cs);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "lib_byteid FAIL: %s — cstage .s concat "
|
|
||||||
"failed (no per-package .s)\n", label);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
snprintf(cmd, sizeof cmd, "cat %s.sepwork/*.s > %s 2>/dev/null",
|
|
||||||
stemw, ws);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "lib_byteid FAIL: %s — wwstage .s concat "
|
|
||||||
"failed (no per-package .s)\n", label);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *bc = NULL, *bw = NULL;
|
|
||||||
size_t nc = 0, nw = 0;
|
|
||||||
if (slurp(cs, &bc, &nc) < 0 || slurp(ws, &bw, &nw) < 0) {
|
|
||||||
fprintf(stderr, "lib_byteid FAIL: %s — cannot read .s\n", label);
|
|
||||||
free(bc); free(bw);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
int same = (nc == nw && memcmp(bc, bw, nc) == 0);
|
|
||||||
free(bc); free(bw);
|
|
||||||
/* two empty concats compare equal — that green covers nothing */
|
|
||||||
if (nc == 0 || nw == 0) {
|
|
||||||
fprintf(stderr, "lib_byteid FAIL: %s — empty .s concat "
|
|
||||||
"(cs %zu, ww %zu bytes)\n", label, nc, nw);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e->mode == M_ID && !same) {
|
|
||||||
fprintf(stderr, "lib_byteid FAIL: %s — cs %zu vs ww %zu bytes "
|
|
||||||
"(byte-id broken)\n", label, nc, nw);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (e->mode == M_DIVERGE && same) {
|
|
||||||
fprintf(stderr, "lib_byteid FAIL: %s — now byte-identical; "
|
|
||||||
"graduate the %s pin to ID\n", label, e->cite);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
rc = 0;
|
|
||||||
out:
|
|
||||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "lib_byteid: cleanup rm -rf %s failed\n", td);
|
|
||||||
if (rc == 0) rc = -1;
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* every lib/ dir holding .ww source must be accounted for — enrolled
|
|
||||||
* in ents[] (fixture dirname or probe moddir) or on the covered[]
|
|
||||||
* list below; otherwise a new module ships with zero byte-id coverage
|
|
||||||
* and the corpus rots silently as lib/ grows. */
|
|
||||||
static const char *covered[] = {
|
|
||||||
/* selfhost-embedded: byte-id'd by the 990-997 gates */
|
|
||||||
"lib/io", "lib/math", "lib/rt", "lib/types",
|
|
||||||
/* module body dragged into the lib/strconv/test fixtures */
|
|
||||||
"lib/strconv",
|
|
||||||
/* #17: the @test runner is AUTO-BUNDLED into every -T sep build (its
|
|
||||||
* own test.sepwork/test.s), so every @test fixture above byte-ids it
|
|
||||||
* cs/ww; 911_attest_record also compares it directly. It has no
|
|
||||||
* _test.ww of its own. */
|
|
||||||
"lib/test",
|
|
||||||
NULL,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int
|
|
||||||
accounted(const char *dir)
|
|
||||||
{
|
|
||||||
for (int i = 0; ents[i].fixture || ents[i].probe; i++) {
|
|
||||||
if (ents[i].fixture) {
|
|
||||||
const char *s = strrchr(ents[i].fixture, '/');
|
|
||||||
size_t n = (size_t)(s - ents[i].fixture);
|
|
||||||
if (strlen(dir) == n &&
|
|
||||||
strncmp(dir, ents[i].fixture, n) == 0)
|
|
||||||
return 1;
|
|
||||||
} else if (ents[i].moddir && strcmp(dir, ents[i].moddir) == 0)
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
for (int i = 0; covered[i]; i++)
|
|
||||||
if (strcmp(dir, covered[i]) == 0)
|
|
||||||
return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
corpus_complete(const char *cwd)
|
|
||||||
{
|
|
||||||
char cmd[2048];
|
|
||||||
/* E3-C1: the flip routes lib @test builds through sep, which drops
|
|
||||||
* <stem>.sepwork/<pkg>.unit.ww scratch next to source; exclude it so
|
|
||||||
* the scan enrols real source dirs only, not generated work dirs. */
|
|
||||||
snprintf(cmd, sizeof cmd, "cd %s && find lib -name '*.ww' "
|
|
||||||
"! -path '*.sepwork/*' "
|
|
||||||
"| sed 's|/[^/]*$||' | sort -u", cwd);
|
|
||||||
FILE *p = popen(cmd, "r");
|
|
||||||
if (!p) return -1;
|
|
||||||
char dir[512];
|
|
||||||
int bad = 0, seen = 0;
|
|
||||||
while (fgets(dir, sizeof dir, p)) {
|
|
||||||
char *nl = strchr(dir, '\n');
|
|
||||||
if (nl) *nl = '\0';
|
|
||||||
seen++;
|
|
||||||
if (!accounted(dir)) {
|
|
||||||
fprintf(stderr, "lib_byteid FAIL: %s — un-enrolled "
|
|
||||||
"lib module; add it to the 989 corpus\n", dir);
|
|
||||||
bad++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pclose(p);
|
|
||||||
/* an empty scan means find itself broke — never pass on that */
|
|
||||||
if (seen == 0) {
|
|
||||||
fprintf(stderr, "lib_byteid FAIL: lib/ scan found nothing\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return bad ? -1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(void)
|
|
||||||
{
|
|
||||||
const char *bin = absbin();
|
|
||||||
if (!bin) return 1;
|
|
||||||
char cwd[1024];
|
|
||||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
|
||||||
int nent = (int)(sizeof ents / sizeof ents[0]) - 1;
|
|
||||||
if (nent != NENT_EXPECT) {
|
|
||||||
fprintf(stderr, "lib_byteid FAIL: corpus has %d entries, want %d\n",
|
|
||||||
nent, NENT_EXPECT);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int fail = 0, n = 0, nid = 0, ndiv = 0, nrej = 0;
|
|
||||||
if (corpus_complete(cwd) != 0) fail++;
|
|
||||||
for (int i = 0; ents[i].fixture || ents[i].probe; i++) {
|
|
||||||
if (check_one(bin, cwd, &ents[i], i) != 0) fail++;
|
|
||||||
else if (ents[i].mode == M_ID) nid++;
|
|
||||||
else if (ents[i].mode == M_DIVERGE) ndiv++;
|
|
||||||
else nrej++;
|
|
||||||
n++;
|
|
||||||
}
|
|
||||||
if (fail) {
|
|
||||||
fprintf(stderr, "lib_byteid: %d/%d entr(ies) failed\n", fail, n);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("lib_byteid: %d byte-identical, %d pinned-divergent (#59), "
|
|
||||||
"%d pinned-wwreject (#59) across %d lib units\n",
|
|
||||||
nid, ndiv, nrej, n);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user