Files
ww/lib/log/log_test.ww
Hojun-Cho c3b81eb793 test: split log silent rows into silent_test.ww per ref/hare/log/silent.ha
Pure file-boundary move: the two silent-logger rows
(silentwritesnothing, silentignoresprintfln) leave lib/log/log_test.ww
for lib/log/silent_test.ww, mirroring ref/hare/log/silent.ha's
ownership (ref/hare/log has no test files of its own; the file name
follows the impl layout as directed). Only the two banner lines are
deleted; both moved blocks are byte-identical. silent_test.ww imports
log + memio only (neither row uses streq/fmt/io).

The specced funcs_test/global_test halves stay merged in log_test.ww:
the streq helper is consumed by six lprint* rows AND the
setloggerswap/printflnglobal global rows, and same-package test files
compile as one unit in dir mode (duplicate fn rejected), so that
boundary cannot be a pure move; reported upstream rather than
relocating the majority of rows.

Ordering: defaultwiredtoglobal must precede any log.setlogger caller.
All setlogger callers (setloggerswap, printflnglobal) remain after it
in log_test.ww; the silent rows never touch setlogger, and dir-mode
concat order (pkgsort: log_test.ww < silent_test.ww) keeps
defaultwiredtoglobal first overall.

Consumers: Makefile LIBRARY_TESTS gains lib/log/silent_test.ww beside
the log_test.ww entry; test/byteid/libbyteid_test.ww roster gains
fx("lib/log/silent_test.ww") after the log_test row, roster 44->45
(+1, NENTEXPECT bumped).

Validation: standalone ww test lib/log/log_test.ww (9 passed) and
lib/log/silent_test.ww (2 passed); dir mode ww test lib/log (11
passed, defaultwiredtoglobal first); test/byteid/libbyteid_test.ww
compile-only build exit 0, 45 roster appends == NENTEXPECT.
2026-08-08 17:17:42 +09:00

200 lines
5.5 KiB
Plaintext

// 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.