From 53919b34046e28f671aa97af19737ea2b390ee2f Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 17:34:00 +0900 Subject: [PATCH] lib: split log tests into funcs+global per ref/hare/log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pure move: log_test.ww dissolves along its banner seams into funcs_test.ww (the six logger-arg rows — lprintlnbasic/single/empty, lprintflnbasic/indexed/mods — plus the fatal-family subprocess TODO; ref/hare/log/funcs.ha owns lprintln/lprintfln and the fatal/lfatal forms) and global_test.ww (defaultwiredtoglobal, setloggerswap, printflnglobal; ref/hare/log/global.ha owns default/global/setlogger). Moved blocks are byte-identical; the 9 banner lines are deleted. Imports minimized per file (log + memio; the stale fmt/io imports drop). Helper inventory: streq spans both halves (all six funcs rows; setloggerswap + printflnglobal on the global side), so it is duplicated under per-file names per the sanctioned strconv streq/fstreq precedent — fstreq in funcs_test.ww (decl + 6 call sites), gstreq in global_test.ww (decl + 6 call sites); bodies byte-identical apart from the name. Line-multiset diff old-vs-concat (heads/banners excluded, names normalized): the only residue is the 9-line streq helper counted twice. Order-safety, proven on the composed run: dir-mode executes declaration order per file, files byte-lexicographic (funcs_test < global_test < silent_test). Observed order: lprintlnbasic, lprintlnsingle, lprintlnempty, lprintflnbasic, lprintflnindexed, lprintflnmods, defaultwiredtoglobal, setloggerswap, printflnglobal, silentwritesnothing, silentignoresprintfln — defaultwiredtoglobal still precedes every log.setlogger caller (setloggerswap and printflnglobal, both later in its own file; funcs and silent rows never call setlogger). Consumers: Makefile LIBRARY_TESTS replaces the log_test.ww entry with the two new entries in place; test/byteid/libbyteid_test.ww roster row becomes two fx rows, NENTEXPECT 59->60. Validation: standalone ww test lib/log/funcs_test.ww (6 passed) and lib/log/global_test.ww (3 passed); dir mode ww test lib/log (11 passed, no duplicate symbols); byteid compile-only build exit 0, 60 roster appends == NENTEXPECT. --- Makefile | 3 +- lib/log/funcs_test.ww | 104 ++++++++++++++++++ lib/log/global_test.ww | 95 ++++++++++++++++ lib/log/log_test.ww | 199 ---------------------------------- test/byteid/libbyteid_test.ww | 7 +- 5 files changed, 205 insertions(+), 203 deletions(-) create mode 100644 lib/log/funcs_test.ww create mode 100644 lib/log/global_test.ww delete mode 100644 lib/log/log_test.ww diff --git a/Makefile b/Makefile index a9015cbe..e2484fa8 100644 --- a/Makefile +++ b/Makefile @@ -496,7 +496,8 @@ LIBRARY_TESTS = lib/errors/errno_test.ww lib/ascii/ascii_test.ww \ lib/bufio/stream_test.ww lib/bufio/scanner_test.ww \ lib/math/random/random_test.ww \ lib/math/checked/checked_test.ww lib/fmt/fmt_test.ww \ - lib/log/log_test.ww lib/log/silent_test.ww lib/fnmatch/fnmatch_test.ww \ + lib/log/funcs_test.ww lib/log/global_test.ww \ + lib/log/silent_test.ww lib/fnmatch/fnmatch_test.ww \ lib/shlex/shlex_test.ww lib/time/arithm_test.ww \ lib/time/duration_test.ww lib/time/instant_test.ww \ lib/encoding/hex/hex_test.ww lib/memio/memio_test.ww \ diff --git a/lib/log/funcs_test.ww b/lib/log/funcs_test.ww new file mode 100644 index 00000000..1b47122f --- /dev/null +++ b/lib/log/funcs_test.ww @@ -0,0 +1,104 @@ +// funcstest — exercises the logger-arg print family (lprintln / +// lprintfln) per ref/hare/log/funcs.ha ownership. Each scenario +// constructs a stdlogger over a memio.stream and asserts the bytes +// the logger produced. No row here touches log.setlogger, so these +// rows are order-independent of the global-wiring rows in +// global_test.ww. + +package log_test; + +import log; +import memio; + +fn fstreq(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; +}; + +@test fn lprintlnbasic() void = { + let buf: [32]u8; + let mem = memio.fixed(buf[0:32]); + let s = &mem.vt; + + let sl = log.new(s); + + log.lprintln(&sl.logger, "hello", 42i64); + + assert(!(!fstreq(memio.string(&mem), "hello 42\n"))); +}; + +@test fn lprintlnsingle() void = { + let buf: [16]u8; + let mem = memio.fixed(buf[0:16]); + let s = &mem.vt; + + let sl = log.new(s); + + log.lprintln(&sl.logger, "only"); + + assert(!(!fstreq(memio.string(&mem), "only\n"))); +}; + +@test fn lprintlnempty() void = { + let buf: [4]u8; + let mem = memio.fixed(buf[0:4]); + let s = &mem.vt; + + let sl = log.new(s); + + log.lprintln(&sl.logger); + + assert(!(!fstreq(memio.string(&mem), "\n"))); +}; + +@test fn lprintflnbasic() void = { + let buf: [32]u8; + let mem = memio.fixed(buf[0:32]); + let s = &mem.vt; + + let sl = log.new(s); + + log.lprintfln(&sl.logger, "x={} y={}", 42i64, "hi"); + + assert(!(!fstreq(memio.string(&mem), "x=42 y=hi\n"))); +}; + +// The fmt parser proper is covered in fmttest; this pins that log's +// variadic forwarding propagates argv order so indexed placeholders +// resolve correctly. +@test fn lprintflnindexed() void = { + let buf: [16]u8; + let mem = memio.fixed(buf[0:16]); + let s = &mem.vt; + + let sl = log.new(s); + + log.lprintfln(&sl.logger, "{1} {0}", "a", "b"); + + assert(!(!fstreq(memio.string(&mem), "b a\n"))); +}; + +// Pins that mod-bearing placeholders flow through log's forwarding +// intact (parser proper covered in fmttest). +@test fn lprintflnmods() void = { + let buf: [16]u8; + let mem = memio.fixed(buf[0:16]); + let s = &mem.vt; + + let sl = log.new(s); + + log.lprintfln(&sl.logger, "{:5}", 42i64); + + assert(!(!fstreq(memio.string(&mem), " 42\n"))); +}; + +// TODO subprocess: log.fatal / log.lfatal / log.fatalf / log.lfatalf +// exit(255). Verifying them needs a fork+wait fixture so the parent +// can assert WEXITSTATUS == 255 without the test process itself +// terminating. lib/os doesn't ship process spawning yet; revisit +// when that lands. diff --git a/lib/log/global_test.ww b/lib/log/global_test.ww new file mode 100644 index 00000000..76729beb --- /dev/null +++ b/lib/log/global_test.ww @@ -0,0 +1,95 @@ +// globaltest — exercises the global-logger wiring (default / global / +// setlogger) per ref/hare/log/global.ha ownership. Each scenario +// constructs stdloggers over memio.streams and asserts the bytes the +// global dispatch produced. defaultwiredtoglobal must stay the first +// @test row: it pins the pristine global == default wiring that every +// later setlogger caller mutates. Composed dir-mode execution is +// declaration order per file, files in byte-lexicographic order +// (funcs_test < global_test < silent_test), and no funcs/silent row +// calls setlogger. + +package log_test; + +import log; +import memio; + +fn gstreq(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; +}; + +// log.default writes to stderr through the private stderrsink — we +// can't easily capture stderr from inside the test, so this scenario +// verifies the initial wiring only: after lazy [[ensureinit]] runs, +// [[log.default]] / [[log.silent]] are non-nil and [[log.global]] +// equals [[log.default]]. The check is order-sensitive: any later +// scenario that calls [[log.setlogger]] mutates the global, so this +// scenario must run before [[setloggerswap]]. +@test fn defaultwiredtoglobal() void = { + let buf: [4]u8; + let mem = memio.fixed(buf[0:4]); + let s = &mem.vt; + let sl = log.new(s); // triggers ensureinit + + assert(!(log.silent == nil)); + assert(!(log.default == nil)); + assert(!(log.global == nil)); + assert(!(log.global != log.default)); +}; + +// Three-phase: install sl1 as global, println writes there; swap to +// sl2, println writes there only; swap to silent, println discards. +// Each memio sink starts empty and is asserted at each phase to pin +// the swap semantics. +@test fn setloggerswap() void = { + let buf1: [32]u8; + let mem1 = memio.fixed(buf1[0:32]); + let s1 = &mem1.vt; + let sl1 = log.new(s1); + + let buf2: [32]u8; + let mem2 = memio.fixed(buf2[0:32]); + let s2 = &mem2.vt; + let sl2 = log.new(s2); + + log.setlogger(&sl1.logger); + log.println("first"); + assert(!(!gstreq(memio.string(&mem1), "first\n"))); + assert(!(mem2.pos != 0)); + + log.setlogger(&sl2.logger); + log.println("second"); + assert(!(!gstreq(memio.string(&mem2), "second\n"))); + // mem1 must be unchanged. + assert(!(!gstreq(memio.string(&mem1), "first\n"))); + + log.setlogger(log.silent); + log.println("dropped"); + // Both sinks unchanged. + assert(!(!gstreq(memio.string(&mem1), "first\n"))); + assert(!(!gstreq(memio.string(&mem2), "second\n"))); +}; + +// Mirrors [[setloggerswap]] but exercises the format-string path: +// install sl1 as global, printfln writes there; mem2 stays empty. +@test fn printflnglobal() void = { + let buf1: [32]u8; + let mem1 = memio.fixed(buf1[0:32]); + let s1 = &mem1.vt; + let sl1 = log.new(s1); + + let buf2: [32]u8; + let mem2 = memio.fixed(buf2[0:32]); + let s2 = &mem2.vt; + let sl2 = log.new(s2); + + log.setlogger(&sl1.logger); + log.printfln("v={}", 7i64); + assert(!(!gstreq(memio.string(&mem1), "v=7\n"))); + assert(!(mem2.pos != 0)); +}; diff --git a/lib/log/log_test.ww b/lib/log/log_test.ww deleted file mode 100644 index 9c2937f1..00000000 --- a/lib/log/log_test.ww +++ /dev/null @@ -1,199 +0,0 @@ -// logtest — exercises lib/log. Run with `out/bin/ww run lib/log/logtest.ww`. -// -// One scenario per @test fn (the ww-stdlib idiom): each scenario -// constructs a stdlogger over a memio.stream and asserts the bytes -// the logger produced. The test surface mirrors what drew called out -// in the design read: lprintln-to-memio, silent-writes-nothing, and -// setlogger-swap. The process-terminating arm ([[log.fatal]] / -// [[log.lfatal]]) needs a subprocess to verify exit(255) without -// killing the test driver — left as a TODO until the project grows -// a subprocess fixture. - -package log_test; - -import fmt; -import io; -import log; -import memio; - -fn streq(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; -}; - -// ---- default points at global after init (must run first) ------------ - -// log.default writes to stderr through the private stderrsink — we -// can't easily capture stderr from inside the test, so this scenario -// verifies the initial wiring only: after lazy [[ensureinit]] runs, -// [[log.default]] / [[log.silent]] are non-nil and [[log.global]] -// equals [[log.default]]. The check is order-sensitive: any later -// scenario that calls [[log.setlogger]] mutates the global, so this -// scenario must run before [[setloggerswap]]. -@test fn defaultwiredtoglobal() void = { - let buf: [4]u8; - let mem = memio.fixed(buf[0:4]); - let s = &mem.vt; - let sl = log.new(s); // triggers ensureinit - - assert(!(log.silent == nil)); - assert(!(log.default == nil)); - assert(!(log.global == nil)); - assert(!(log.global != log.default)); -}; - -// ---- lprintln to a memio sink: exact-byte assertion ------------------- - -@test fn lprintlnbasic() void = { - let buf: [32]u8; - let mem = memio.fixed(buf[0:32]); - let s = &mem.vt; - - let sl = log.new(s); - - log.lprintln(&sl.logger, "hello", 42i64); - - assert(!(!streq(memio.string(&mem), "hello 42\n"))); -}; - -// ---- lprintln single-arg: no leading space, just trailing newline ----- - -@test fn lprintlnsingle() void = { - let buf: [16]u8; - let mem = memio.fixed(buf[0:16]); - let s = &mem.vt; - - let sl = log.new(s); - - log.lprintln(&sl.logger, "only"); - - assert(!(!streq(memio.string(&mem), "only\n"))); -}; - -// ---- lprintln zero args: bare newline --------------------------------- - -@test fn lprintlnempty() void = { - let buf: [4]u8; - let mem = memio.fixed(buf[0:4]); - let s = &mem.vt; - - let sl = log.new(s); - - log.lprintln(&sl.logger); - - assert(!(!streq(memio.string(&mem), "\n"))); -}; - -// ---- setlogger swap: global redirects to a new sink, then to silent -- - -// Three-phase: install sl1 as global, println writes there; swap to -// sl2, println writes there only; swap to silent, println discards. -// Each memio sink starts empty and is asserted at each phase to pin -// the swap semantics. -@test fn setloggerswap() void = { - let buf1: [32]u8; - let mem1 = memio.fixed(buf1[0:32]); - let s1 = &mem1.vt; - let sl1 = log.new(s1); - - let buf2: [32]u8; - let mem2 = memio.fixed(buf2[0:32]); - let s2 = &mem2.vt; - let sl2 = log.new(s2); - - log.setlogger(&sl1.logger); - log.println("first"); - assert(!(!streq(memio.string(&mem1), "first\n"))); - assert(!(mem2.pos != 0)); - - log.setlogger(&sl2.logger); - log.println("second"); - assert(!(!streq(memio.string(&mem2), "second\n"))); - // mem1 must be unchanged. - assert(!(!streq(memio.string(&mem1), "first\n"))); - - log.setlogger(log.silent); - log.println("dropped"); - // Both sinks unchanged. - assert(!(!streq(memio.string(&mem1), "first\n"))); - assert(!(!streq(memio.string(&mem2), "second\n"))); -}; - -// ---- lprintfln: {n}-placeholder render into a memio sink ------------- - -@test fn lprintflnbasic() void = { - let buf: [32]u8; - let mem = memio.fixed(buf[0:32]); - let s = &mem.vt; - - let sl = log.new(s); - - log.lprintfln(&sl.logger, "x={} y={}", 42i64, "hi"); - - assert(!(!streq(memio.string(&mem), "x=42 y=hi\n"))); -}; - -// ---- printfln through global: setlogger then dispatch --------------- - -// Mirrors [[setloggerswap]] but exercises the format-string path: -// install sl1 as global, printfln writes there; mem2 stays empty. -@test fn printflnglobal() void = { - let buf1: [32]u8; - let mem1 = memio.fixed(buf1[0:32]); - let s1 = &mem1.vt; - let sl1 = log.new(s1); - - let buf2: [32]u8; - let mem2 = memio.fixed(buf2[0:32]); - let s2 = &mem2.vt; - let sl2 = log.new(s2); - - log.setlogger(&sl1.logger); - log.printfln("v={}", 7i64); - assert(!(!streq(memio.string(&mem1), "v=7\n"))); - assert(!(mem2.pos != 0)); -}; - -// ---- lprintfln: indexed {N} placeholder across log → fmt seam ------- - -// The fmt parser proper is covered in fmttest; this pins that log's -// variadic forwarding propagates argv order so indexed placeholders -// resolve correctly. -@test fn lprintflnindexed() void = { - let buf: [16]u8; - let mem = memio.fixed(buf[0:16]); - let s = &mem.vt; - - let sl = log.new(s); - - log.lprintfln(&sl.logger, "{1} {0}", "a", "b"); - - assert(!(!streq(memio.string(&mem), "b a\n"))); -}; - -// ---- lprintfln: {:mods} modifier across log → fmt seam -------------- - -// Pins that mod-bearing placeholders flow through log's forwarding -// intact (parser proper covered in fmttest). -@test fn lprintflnmods() void = { - let buf: [16]u8; - let mem = memio.fixed(buf[0:16]); - let s = &mem.vt; - - let sl = log.new(s); - - log.lprintfln(&sl.logger, "{:5}", 42i64); - - assert(!(!streq(memio.string(&mem), " 42\n"))); -}; - -// TODO subprocess: log.fatal / log.lfatal / log.fatalf / log.lfatalf -// exit(255). Verifying them needs a fork+wait fixture so the parent -// can assert WEXITSTATUS == 255 without the test process itself -// terminating. lib/os doesn't ship process spawning yet; revisit -// when that lands. diff --git a/test/byteid/libbyteid_test.ww b/test/byteid/libbyteid_test.ww index 00e5f351..c8b6ec33 100644 --- a/test/byteid/libbyteid_test.ww +++ b/test/byteid/libbyteid_test.ww @@ -43,7 +43,7 @@ import time; def MID: i32 = 0; def MDIVERGE: i32 = 1; def MWWREJECT: i32 = 2; -def NENTEXPECT: i32 = 59; +def NENTEXPECT: i32 = 60; type ent = struct { fixture: str, // repo-relative .ww; "" -> probe entry @@ -83,7 +83,7 @@ fn pri(p: str, inc: str, sentinel: str, moddir: str) ent = { return e; }; -// The 59-unit roster. Graduation history lives in git (the retired C +// The 60-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 = { @@ -155,7 +155,8 @@ fn corpus() []ent = { 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/funcs_test.ww")); + append(es, fx("lib/log/global_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