lib+test: fmt rename fprint→fdprint; add io.stream fprint sink
This commit is contained in:
213
lib/fmt/fmttest.ww
Normal file
213
lib/fmt/fmttest.ww
Normal file
@@ -0,0 +1,213 @@
|
||||
// fmttest — exercises lib/fmt's stream sinks (fprint / fprintln).
|
||||
// Run with `out/bin/ww run lib/fmt/fmttest.ww`.
|
||||
//
|
||||
// Each @test writes through a memio.stream and compares the resulting
|
||||
// bytes against an inline `want` literal. Bodies are short enough that
|
||||
// the parallel-array idiom used by memiotest doesn't apply — every row
|
||||
// here threads a different variadic argument-pack into fprint, and the
|
||||
// variadic shape can't be table-driven within a single fn body.
|
||||
|
||||
use fmt;
|
||||
use io;
|
||||
use memio;
|
||||
|
||||
// Direct exit(2) binding rather than `use os;` — os exports
|
||||
// read/write/close, which collide with io.read/write/close under
|
||||
// the driver's flat-scope concat.
|
||||
@symbol("rt_syscall") fn syscall1ww(num: i64, a: i64) i64;
|
||||
fn doexit(code: i32) void = {
|
||||
syscall1ww(60i64, code: i64);
|
||||
};
|
||||
|
||||
// signalled — bumped before each scenario so a failing exit code
|
||||
// pinpoints the offending case.
|
||||
let signalled: i32 = 0;
|
||||
|
||||
fn fail() void = { doexit(signalled + 10); };
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
// ---- A write-always-closed stream for the io.closed surfacing test ----
|
||||
|
||||
fn closedread(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
|
||||
let e: io.closed; return e;
|
||||
};
|
||||
fn closedwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
|
||||
let e: io.closed; return e;
|
||||
};
|
||||
fn closedclose(s: *io.stream) (void | io.closed) = { return; };
|
||||
|
||||
fn closedstream(s: *io.stream) void = {
|
||||
s.ctx = nil;
|
||||
s.read = closedread;
|
||||
s.write = closedwrite;
|
||||
s.close = closedclose;
|
||||
};
|
||||
|
||||
// ---- fprint: bare str --------------------------------------------------
|
||||
|
||||
@test fn fprintbarestr() void = {
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.dynamic(&mem, &s);
|
||||
|
||||
let r: (i32 | io.closed) = fmt.fprint(&s, "hello");
|
||||
match (r) {
|
||||
case let n: i32 => { if (n != 5) { fail(); }; };
|
||||
case io.closed => fail();
|
||||
};
|
||||
if (!streq(memio.string(&mem), "hello")) { fail(); };
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- fprint: int + str mix, space-separated ----------------------------
|
||||
|
||||
@test fn fprintintstr() void = {
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.dynamic(&mem, &s);
|
||||
|
||||
let r: (i32 | io.closed) = fmt.fprint(&s, 42i64, "x");
|
||||
match (r) {
|
||||
case let n: i32 => { if (n != 4) { fail(); }; }; // "42 x"
|
||||
case io.closed => fail();
|
||||
};
|
||||
if (!streq(memio.string(&mem), "42 x")) { fail(); };
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- fprint: bool + rune renders as "true A" --------------------------
|
||||
|
||||
@test fn fprintboolrune() void = {
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.dynamic(&mem, &s);
|
||||
|
||||
let r: (i32 | io.closed) = fmt.fprint(&s, true, 'A': rune);
|
||||
match (r) {
|
||||
case let n: i32 => { if (n != 6) { fail(); }; }; // "true A"
|
||||
case io.closed => fail();
|
||||
};
|
||||
if (!streq(memio.string(&mem), "true A")) { fail(); };
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- fprint: zero args returns 0, no bytes written --------------------
|
||||
|
||||
@test fn fprintempty() void = {
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.dynamic(&mem, &s);
|
||||
|
||||
let r: (i32 | io.closed) = fmt.fprint(&s);
|
||||
match (r) {
|
||||
case let n: i32 => { if (n != 0) { fail(); }; };
|
||||
case io.closed => fail();
|
||||
};
|
||||
if (memio.string(&mem).len != 0) { fail(); };
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- fprintln: multi-arg, trailing '\n' --------------------------------
|
||||
|
||||
@test fn fprintlnmulti() void = {
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.dynamic(&mem, &s);
|
||||
|
||||
let r: (i32 | io.closed) = fmt.fprintln(&s, "a", 1i64, false);
|
||||
match (r) {
|
||||
case let n: i32 => { if (n != 10) { fail(); }; }; // "a 1 false\n"
|
||||
case io.closed => fail();
|
||||
};
|
||||
if (!streq(memio.string(&mem), "a 1 false\n")) { fail(); };
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- fprintln: zero args writes just the newline ----------------------
|
||||
|
||||
@test fn fprintlnempty() void = {
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.dynamic(&mem, &s);
|
||||
|
||||
let r: (i32 | io.closed) = fmt.fprintln(&s);
|
||||
match (r) {
|
||||
case let n: i32 => { if (n != 1) { fail(); }; };
|
||||
case io.closed => fail();
|
||||
};
|
||||
if (!streq(memio.string(&mem), "\n")) { fail(); };
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- fprint over a fixed stream: short writes return partial count ----
|
||||
// memio.fixedwrite caps each call at the remaining buffer space and
|
||||
// never closes the stream, so fprint sees a short i32 result, not
|
||||
// io.closed. Verifies the inner-loop arithmetic adds the actual byte
|
||||
// count rather than the requested length.
|
||||
|
||||
@test fn fprintfixedshort() void = {
|
||||
let buf: [3]u8;
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.fixed(&mem, &s, buf[0:3]);
|
||||
|
||||
let r: (i32 | io.closed) = fmt.fprint(&s, "hello");
|
||||
match (r) {
|
||||
case let n: i32 => { if (n != 3) { fail(); }; };
|
||||
case io.closed => fail();
|
||||
};
|
||||
if (!streq(memio.string(&mem), "hel")) { fail(); };
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- fprint over a closed stream surfaces io.closed -------------------
|
||||
// Exercises the early-return arm in fprint's inner match — distinct from
|
||||
// the short-write path above, which keeps returning i32 from a partial
|
||||
// accept. Single arm is enough: every formattable case routes errors
|
||||
// through the same putbytes / io.write wiring.
|
||||
|
||||
@test fn fprintclosed() void = {
|
||||
let s: io.stream;
|
||||
closedstream(&s);
|
||||
|
||||
let r: (i32 | io.closed) = fmt.fprint(&s, "x");
|
||||
match (r) {
|
||||
case let n: i32 => fail();
|
||||
case io.closed => {};
|
||||
};
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
signalled = 1; fprintbarestr();
|
||||
signalled = 2; fprintintstr();
|
||||
signalled = 3; fprintboolrune();
|
||||
signalled = 4; fprintempty();
|
||||
signalled = 5; fprintlnmulti();
|
||||
signalled = 6; fprintlnempty();
|
||||
signalled = 7; fprintfixedshort();
|
||||
signalled = 8; fprintclosed();
|
||||
return 0;
|
||||
};
|
||||
Reference in New Issue
Block a user