Files
ww/test/byteid/libbyteid_test.ww
Hojun-Cho cec382ef8a lib: split time_test into duration/instant/arithm tests per ref/hare/time
Mirror ref/hare/time's impl layout in the test siblings: constants ->
duration_test.ww (ref/hare/time/duration.ha:9-18 owns the constants);
now/sleep rows -> instant_test.ww (instant is the subject type,
ref/hare/time/instant.ha; now/sleep bodies live in
ref/hare/time/+linux/functions.ha, a platform split ww does not have);
add/diff/compare rows -> arithm_test.ww (ref/hare/time/arithm.ha).

Pure move: every @test block is byte-identical; only the section
banner lines are deleted (the file names now carry them). The stale
file header naming the retired `ww run lib/time/timetest.ww` entry
point is dropped, not relocated (rule 8: it described a WHAT that no
longer exists).

Consumers: Makefile LIBRARY_TESTS entry replaced in place;
test/byteid roster 44->46 (+2).
2026-08-08 17:19:14 +09:00

422 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 = 47;
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 46-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/arithm_test.ww"));
append(es, fx("lib/time/duration_test.ww"));
append(es, fx("lib/time/instant_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/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;
};
};