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 // .sepwork/.s — the lib body lands in .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 = 59; 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 59-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/equal_test.ww")); append(es, fx("lib/bytes/index_test.ww")); append(es, fx("lib/bytes/contains_test.ww")); append(es, fx("lib/bytes/tokenize_test.ww")); append(es, fx("lib/bytes/trim_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/rune_test.ww")); append(es, fx("lib/encoding/utf8/encode_test.ww")); append(es, fx("lib/encoding/utf8/decode_test.ww")); append(es, fx("lib/encoding/utf8/types_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/strings/suffix_test.ww")); append(es, fx("lib/strings/contains_test.ww")); append(es, fx("lib/strings/index_test.ww")); append(es, fx("lib/strings/compare_test.ww")); append(es, fx("lib/temp/temp_test.ww")); append(es, fx("lib/time/arithm_test.ww")); append(es, fx("lib/time/duration_test.ww")); append(es, fx("lib/time/instant_test.ww")); append(es, fx("lib/bufio/stream_test.ww")); append(es, fx("lib/bufio/scanner_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/log/silent_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; }; };