419 lines
14 KiB
Plaintext
419 lines
14 KiB
Plaintext
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
|
|
};
|
|
|
|
// Row constructors; the roster appends their results directly (#34
|
|
// closed — append accepts struct call rvalues).
|
|
fn fx(f: str) ent = {
|
|
let e: ent;
|
|
e.fixture = f; e.probe = ""; e.inc = ""; e.mode = MID;
|
|
e.cite = ""; e.sentinel = ""; e.moddir = "";
|
|
return e;
|
|
};
|
|
|
|
fn fxi(f: str, inc: str) ent = {
|
|
let e: ent = fx(f);
|
|
e.inc = inc;
|
|
return e;
|
|
};
|
|
|
|
fn pr(p: str, sentinel: str, moddir: str) ent = {
|
|
let e: ent = fx("");
|
|
e.probe = p;
|
|
e.sentinel = sentinel; e.moddir = moddir;
|
|
return e;
|
|
};
|
|
|
|
fn pri(p: str, inc: str, sentinel: str, moddir: str) ent = {
|
|
let e: ent = pr(p, sentinel, moddir);
|
|
e.inc = inc;
|
|
return e;
|
|
};
|
|
|
|
// 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)!;
|
|
append(es, fx("lib/bytes/bytes_test.ww"));
|
|
append(es, fx("lib/dirs/dirs_test.ww"));
|
|
append(es, fx("lib/encoding/base32/base32_test.ww"));
|
|
append(es, fx("lib/encoding/hex/hex_test.ww"));
|
|
append(es, fx("lib/encoding/utf8/utf8_test.ww"));
|
|
append(es, fx("lib/getopt/getopt_test.ww"));
|
|
append(es, fx("lib/hash/adler32/adler32_test.ww"));
|
|
append(es, fx("lib/hash/crc16/crc16_test.ww"));
|
|
append(es, fx("lib/hash/crc32/crc32_test.ww"));
|
|
append(es, fx("lib/hash/crc64/crc64_test.ww"));
|
|
append(es, fx("lib/hash/siphash/siphash_test.ww"));
|
|
append(es, fx("lib/math/checked/checked_test.ww"));
|
|
append(es, fx("lib/math/random/random_test.ww"));
|
|
append(es, fx("lib/memio/memio_test.ww"));
|
|
append(es, fx("lib/os/os_test.ww"));
|
|
append(es, pr("package main;\nimport os.exec;\nfn main() i32 = { return 0; };\n",
|
|
"package exec;", "lib/os/exec"));
|
|
append(es, fx("lib/regex/regex_test.ww"));
|
|
append(es, fx("lib/strconv/test/ftos_test.ww"));
|
|
append(es, fx("lib/strconv/test/stof_test.ww"));
|
|
append(es, fx("lib/strconv/test/int_test.ww"));
|
|
append(es, fx("lib/strings/strings_test.ww"));
|
|
append(es, fx("lib/temp/temp_test.ww"));
|
|
append(es, fx("lib/time/time_test.ww"));
|
|
append(es, fx("lib/bufio/bufio_test.ww"));
|
|
append(es, fx("lib/fmt/fmt_test.ww"));
|
|
append(es, pr("package main;\nimport sort;\nfn main() i32 = { return 0; };\n",
|
|
"package sort;", "lib/sort"));
|
|
append(es, pr("package main;\nimport path;\nfn main() i32 = { return 0; };\n",
|
|
"package path;", "lib/path"));
|
|
append(es, pr("package main;\nimport endian;\nfn main() i32 = { return 0; };\n",
|
|
"package endian;", "lib/endian"));
|
|
append(es, pr("package main;\nimport net;\nfn main() i32 = { return 0; };\n",
|
|
"package net;", "lib/net"));
|
|
append(es, pr("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)
|
|
append(es, pri("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
|
|
append(es, pr("package main;\nimport crypto.math;\nfn main() i32 = { return 0; };\n",
|
|
"fn rotl32", "lib/crypto/math"));
|
|
append(es, pr("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.
|
|
append(es, pr("package main;\nfn main() i32 = { return 0; };\n", "", ""));
|
|
append(es, fx("lib/ascii/ascii_test.ww"));
|
|
append(es, fx("lib/encoding/base64/base64_test.ww"));
|
|
append(es, fx("lib/errors/errno_test.ww"));
|
|
append(es, fx("lib/log/log_test.ww"));
|
|
append(es, fx("lib/os/stat_test.ww"));
|
|
// toktest/asttest resolve `import syntax` via -I lib/ww, cf 905
|
|
append(es, fxi("lib/ww/syntax/tok_test.ww", "lib/ww"));
|
|
append(es, fxi("lib/ww/syntax/ast_test.ww", "lib/ww"));
|
|
append(es, fx("lib/crypto/sha256/sha256_test.ww"));
|
|
append(es, fx("lib/fnmatch/fnmatch_test.ww"));
|
|
append(es, fx("lib/shlex/shlex_test.ww"));
|
|
assert(es.len == 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;
|
|
};
|
|
};
|