// 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"))); }; // ---- silent: lprintln through log.silent writes no bytes -------------- // The silent logger's callback discards args without touching fmt or // any sink. Verified indirectly: we wire a memio.stream and never // pass it to log.silent — but the @test fn calling log.new first // triggers [[log.ensureinit]] so log.silent is non-nil here. Then // lprintln(silent, ...) must not crash and the memio sink must stay // empty (silent has no path to it anyway). @test fn silentwritesnothing() void = { let buf: [16]u8; let mem = memio.fixed(buf[0:16]); let s = &mem.vt; let sl = log.new(s); // triggers log.ensureinit; populates log.silent assert(!(log.silent == nil)); log.lprintln(log.silent, "ignored", 1i64, true); assert(!(mem.pos != 0)); }; // ---- 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)); }; // ---- silent.printfln writes nothing --------------------------------- // The silent logger's printfln callback discards args without touching // fmt or any sink. Same pattern as [[silentwritesnothing]] for the // bare-args path. @test fn silentignoresprintfln() void = { let buf: [16]u8; let mem = memio.fixed(buf[0:16]); let s = &mem.vt; let sl = log.new(s); // triggers log.ensureinit; populates log.silent assert(!(log.silent == nil)); log.lprintfln(log.silent, "ignored={}", 1i64); assert(!(mem.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.