851 lines
27 KiB
Plaintext
851 lines
27 KiB
Plaintext
// 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.
|
|
|
|
package fmt_test;
|
|
|
|
import fmt;
|
|
import io;
|
|
import memio;
|
|
import os;
|
|
import strconv;
|
|
import test;
|
|
|
|
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;
|
|
};
|
|
|
|
let errvt: io.vtable;
|
|
|
|
fn errread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
|
|
let nm: nomem; let e: io.error = nm; return e;
|
|
};
|
|
fn errwrite(s: io.stream, buf: []u8) (size | io.error) = {
|
|
let nm: nomem; let e: io.error = nm; return e;
|
|
};
|
|
fn errsource() io.stream = {
|
|
errvt.reader = (&errread): *io.reader;
|
|
errvt.writer = (&errwrite): *io.writer;
|
|
return &errvt;
|
|
};
|
|
|
|
type shortstream = struct {
|
|
vt: io.vtable,
|
|
buf: [32]u8,
|
|
pos: i32,
|
|
calls: i32,
|
|
};
|
|
|
|
fn shortwrite(s: io.stream, buf: []u8) (size | io.error) = {
|
|
let out: *shortstream = s: *shortstream;
|
|
let n: i32 = buf.len;
|
|
if (n > 2) { n = 2; };
|
|
let i: i32 = 0;
|
|
for (i < n) {
|
|
out.buf[out.pos + i] = buf[i];
|
|
i += 1;
|
|
};
|
|
out.pos += n;
|
|
out.calls += 1;
|
|
return n: size;
|
|
};
|
|
|
|
type countstream = struct {
|
|
vt: io.vtable,
|
|
calls: i32,
|
|
};
|
|
|
|
fn countwrite(s: io.stream, buf: []u8) (size | io.error) = {
|
|
let out: *countstream = s: *countstream;
|
|
out.calls += 1;
|
|
return buf.len: size;
|
|
};
|
|
|
|
@test fn fprintbarestr() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
|
|
let r: (size | io.error) = fmt.fprint(s, "hello");
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 5)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "hello")));
|
|
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintintstr() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
|
|
let r: (size | io.error) = fmt.fprint(s, 42i64, "x");
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 4)); }; // "42 x"
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "42 x")));
|
|
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
// These rows preserve the I64_MIN boundary while fprint delegates integer
|
|
// conversion to strconv.i64tos instead of keeping a second decimal engine.
|
|
|
|
@test fn fprinti64min() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
|
|
let m: i64 = -9223372036854775807i64 - 1i64;
|
|
let r: (size | io.error) = fmt.fprint(s, m);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 20)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "-9223372036854775808")));
|
|
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintintmin() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
|
|
let m: int = -9223372036854775807 - 1;
|
|
let r: (size | io.error) = fmt.fprint(s, m, -7i64, 0i64);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 25)); }; // "-9223372036854775808 -7 0"
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "-9223372036854775808 -7 0")));
|
|
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintboolrune() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
|
|
let r: (size | io.error) = fmt.fprint(s, true, 'A': rune);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 6)); }; // "true A"
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "true A")));
|
|
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintempty() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
|
|
let r: (size | io.error) = fmt.fprint(s);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 0)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(memio.string(&mem).len != 0));
|
|
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintlnmulti() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
|
|
let r: (size | io.error) = fmt.fprintln(s, "a", 1i64, false);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 10)); }; // "a 1 false\n"
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "a 1 false\n")));
|
|
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintlnempty() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
|
|
let r: (size | io.error) = fmt.fprintln(s);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 1)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "\n")));
|
|
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
// A partial fixed-buffer write cannot be reported as a successful prefix.
|
|
// putbytes retries the unwritten suffix; the full sink then returns nomem.
|
|
|
|
@test fn fprintfixedshort() void = {
|
|
let buf: [3]u8;
|
|
let mem: memio.stream = memio.fixed(buf[0:3]);
|
|
let s: io.stream = &mem.vt;
|
|
|
|
let r: (size | io.error) = fmt.fprint(s, "hello");
|
|
match (r) {
|
|
case let n: size => abort();
|
|
case let eioe: io.error => { assert(eioe is nomem); };
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "hel")));
|
|
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintcompleteshortwrites() void = {
|
|
let out: shortstream;
|
|
out.vt.writer = (&shortwrite): *io.writer;
|
|
out.pos = 0;
|
|
out.calls = 0;
|
|
let r: (size | io.error) = fmt.fprint(&out.vt, "hello");
|
|
match (r) {
|
|
case let n: size => assert(n == 5: size);
|
|
case let e: io.error => abort();
|
|
};
|
|
let got: str;
|
|
got.ptr = &out.buf[0];
|
|
got.len = out.pos;
|
|
got.cap = got.len;
|
|
assert(streq(got, "hello"));
|
|
assert(out.calls == 3);
|
|
};
|
|
|
|
// 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 = errsource();
|
|
|
|
let r: (size | io.error) = fmt.fprint(s, "x");
|
|
match (r) {
|
|
case let n: size => abort();
|
|
case let eioe: io.error => {};
|
|
};
|
|
};
|
|
|
|
// Variadic call-site shape forces one body per shape; see the file header.
|
|
|
|
@test fn fprintf_implicit() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "hello {} {}", "world", 42i64);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 14)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "hello world 42")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_coalesces_literals() void = {
|
|
let out: countstream;
|
|
out.vt.writer = (&countwrite): *io.writer;
|
|
out.calls = 0;
|
|
let r: (size | io.error) = fmt.fprintf(&out.vt,
|
|
"hello {} world", 42i64);
|
|
match (r) {
|
|
case let n: size => assert(n == 14: size);
|
|
case let e: io.error => abort();
|
|
};
|
|
// One call for each literal run and one for the formatted integer.
|
|
assert(out.calls == 3);
|
|
};
|
|
|
|
@test fn fprintf_indexed() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{1} {0} {1}", "hello", "world");
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 17)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "world hello world")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_parametric_mods() void = {
|
|
let m: fmt.mods;
|
|
m.alignment = fmt.alignment.RIGHT;
|
|
m.pad = '0': rune;
|
|
m.neg = fmt.neg.NONE;
|
|
m.width = 5;
|
|
m.prec = 0;
|
|
m.base = strconv.base.DEC;
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s,
|
|
"{%} {2%3}", 42i64, &m, 7i64, &m);
|
|
match (r) {
|
|
case let n: size => assert(n: i32 == 11);
|
|
case let e: io.error => abort();
|
|
};
|
|
assert(streq(memio.string(&mem), "00042 00007"));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let e: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_literal_braces() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{{ {} }}", 1i64);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 5)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "{ 1 }")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_width_right() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{:5}", 42i64);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 5)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), " 42")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_width_left() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{:-5}", 42i64);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 5)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "42 ")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_width_center() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{:=5}", "hi");
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 5)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), " hi ")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
// #66: writeone/formatraw's rune arm did `buf[0] = r: u8; putbytes(...,1)`,
|
|
// emitting only the low byte (invalid UTF-8 for r > 0x7F). The fix routes
|
|
// through utf8.encoderune (ref/hare/fmt/print.ha:84). Edge runes: é (2B),
|
|
// € (3B), 😀 (4B), A (1B).
|
|
|
|
@test fn fprintrune() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
|
|
let r: (size | io.error) = fmt.fprint(s, 233: rune, 0x20AC: rune,
|
|
0x1F600: rune, 65: rune);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 13)); }; // 2+1+3+1+4+1+1
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "é € 😀 A")));
|
|
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
// rawlen's rune arm hardcoded 1, desyncing width-padding for a multibyte
|
|
// rune. With runesz it counts the encoded length: € (3B) in width 5 pads
|
|
// to " €" (2 spaces + 3 bytes = 5).
|
|
@test fn fprintf_rune_width() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
|
|
let r: (size | io.error) = fmt.fprintf(s, "{:5}", 0x20AC: rune);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 5)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), " €")));
|
|
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_pad_underscore() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{:_05}", "hi");
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 5)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "000hi")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_pad_multibyte() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{:_€5}", "hi");
|
|
match (r) {
|
|
case let n: size => assert(n: i32 == 5);
|
|
case let e: io.error => abort();
|
|
};
|
|
assert(streq(memio.string(&mem), "€hi"));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let e: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_base_hex() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{:x} {:X}", 48879i64, 61453i64);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 9)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "beef F00D")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_base_oct_bin() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{:o} {:b}", 493i64, 27i64);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 9)); }; // "755 11011"
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "755 11011")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_prec_int() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{:.5}", 42i64);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 5)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "00042")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_prec_str_trunc() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{:.3}", "hello");
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 3)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "hel")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_prec_str_runes() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s,
|
|
"{:.1}|{:.2}|{:.2}", "éx", "éx", "😀");
|
|
match (r) {
|
|
case let n: size => assert(n: i32 == 11);
|
|
case let e: io.error => abort();
|
|
};
|
|
assert(streq(memio.string(&mem), "é|éx|😀"));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let e: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_prec_str_rune_width() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{:5.1}", "éx");
|
|
match (r) {
|
|
case let n: size => assert(n: i32 == 5);
|
|
case let e: io.error => abort();
|
|
};
|
|
assert(streq(memio.string(&mem), " é"));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let e: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_sign_neg() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{} {:+} {: }", -7i64, 7i64, 7i64);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 8)); }; // "-7 +7 7"
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "-7 +7 7")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintfln_basic() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintfln(s, "x={}", 9i64);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 4)); }; // "x=9\n"
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "x=9\n")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_closed() void = {
|
|
let s: io.stream = errsource();
|
|
let r: (size | io.error) = fmt.fprintf(s, "hello {}", 1i64);
|
|
match (r) {
|
|
case let n: size => abort();
|
|
case let eioe: io.error => {};
|
|
};
|
|
};
|
|
|
|
@test fn bsprintf_basic() void = {
|
|
let buf: [16]u8;
|
|
let r: (str | io.error) = fmt.bsprintf(buf[0:16], "{} {}", "hi", 42i64);
|
|
match (r) {
|
|
case let s: str => { assert(!(!streq(s, "hi 42"))); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
};
|
|
|
|
// Positive control: an EXACT-fit render must still succeed — the sink
|
|
// fills to the last byte but no write lands on a full sink, so the
|
|
// nomem guard (memio.ww:190) must not fire. Guards against the
|
|
// truncation fix over-rejecting a render that exactly fits.
|
|
@test fn bsprintf_exact() void = {
|
|
let buf: [5]u8;
|
|
let r: (str | io.error) = fmt.bsprintf(buf[0:5], "hello");
|
|
match (r) {
|
|
case let s: str => { assert(!(!streq(s, "hello"))); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
};
|
|
|
|
@test fn bsprintf_final_short() void = {
|
|
let buf: [3]u8;
|
|
let r: (str | io.error) = fmt.bsprintf(buf[0:3], "hello");
|
|
match (r) {
|
|
case let s: str => abort();
|
|
case let e: io.error => assert(e is nomem);
|
|
};
|
|
};
|
|
|
|
// Pins the formatfield bool / rune arms inside fprintf's for-loop —
|
|
// the codegen-smell repro shape (task #18). Pre-task-#18, the str arm
|
|
// is also exercised by fprintf_implicit; this adds the two remaining
|
|
// tagged arms so a future task-#18 fix that re-introduces the 24B
|
|
// widen-by-value cannot regress silently.
|
|
@test fn fprintf_bool_rune() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{} {}", true, 'A': rune);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 6)); }; // "true A"
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "true A")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
// Verifies bsprintf's truncation contract — overflowing a fixed sink
|
|
// surfaces nomem, not a prefix (memio.fixedwrite returns nomem once
|
|
// full, ref/hare/fmt/wrappers.ha:50). Pairs with fprintfixedshort
|
|
// above which covers the partial-write (short i32) path.
|
|
@test fn bsprintf_trunc() void = {
|
|
let buf: [3]u8;
|
|
let r: (str | io.error) = fmt.bsprintf(buf[0:3], "{} {}", "hi", 42i64);
|
|
match (r) {
|
|
case let s: str => abort();
|
|
case let eioe: io.error => { assert(eioe is nomem); };
|
|
};
|
|
};
|
|
|
|
// Truncation through the width/pad path also surfaces nomem. Buffer
|
|
// 3B, target " 42" (5B): the 3 lead pad spaces fill the sink, then
|
|
// formatraw writes "42" into the full sink → nomem. Also pins
|
|
// formatone's tail-pad counter (the loop must not spin on the full
|
|
// sink before the error propagates).
|
|
@test fn bsprintf_width_trunc() void = {
|
|
let buf: [3]u8;
|
|
let r: (str | io.error) = fmt.bsprintf(buf[0:3], "{:5}", 42i64);
|
|
match (r) {
|
|
case let s: str => abort();
|
|
case let eioe: io.error => { assert(eioe is nomem); };
|
|
};
|
|
};
|
|
|
|
// Empty-sink discriminator: the cleanest single-write red/green split.
|
|
// The first non-empty write to a zero-length sink hits the full-sink
|
|
// guard → nomem; pre-fix it returned 0 and bsprintf yielded "" as a
|
|
// success.
|
|
@test fn bsprintf_empty() void = {
|
|
let buf: [0]u8;
|
|
let r: (str | io.error) = fmt.bsprintf(buf[0:0], "x");
|
|
match (r) {
|
|
case let s: str => abort();
|
|
case let eioe: io.error => { assert(eioe is nomem); };
|
|
};
|
|
};
|
|
|
|
// asprintf — heap-allocated render through memio.dynamic + shrink-to-fit.
|
|
// Caller frees via os.free(r.ptr, r.len) when r.len > 0.
|
|
|
|
@test fn asprintf_basic() void = {
|
|
let r: str = fmt.asprintf("{} {}", "hi", 42i64);
|
|
assert(!(!streq(r, "hi 42")));
|
|
os.free(r.ptr: *void, r.len: u64);
|
|
};
|
|
|
|
// Output > memio.dynamic's initial 8B cap to exercise the
|
|
// double-and-copy grow path; 34B target traverses 8→16→32→64.
|
|
@test fn asprintf_growth() void = {
|
|
let r: str = fmt.asprintf("hello {} world {} value {}", "alpha", "beta", "gamma");
|
|
assert(!(!streq(r, "hello alpha world beta value gamma")));
|
|
os.free(r.ptr: *void, r.len: u64);
|
|
};
|
|
|
|
// Empty format pins the no-allocation path: memio.dynamic.grow never
|
|
// fires (cap stays 0), and the asprintf shrink-to-fit path returns
|
|
// {nil, 0} without calling os.alloc. Free is skipped — matches the
|
|
// strings.dup empty-input contract.
|
|
@test fn asprintf_empty() void = {
|
|
let r: str = fmt.asprintf("");
|
|
assert(!(r.len != 0));
|
|
};
|
|
|
|
// Indexed placeholder + modifier through asprintf — confirms the full
|
|
// {n:mods} parser path works through the heap-allocated sink, not just
|
|
// the memio.fixed (bsprintf) and memio.dynamic-via-fprintf paths.
|
|
@test fn asprintf_indexed_mods() void = {
|
|
let r: str = fmt.asprintf("{1:_05}", "zzz", 42i64);
|
|
assert(!(!streq(r, "00042")));
|
|
os.free(r.ptr: *void, r.len: u64);
|
|
};
|
|
|
|
// strconv.f64tos drives shortest round-trippable decimal output, including
|
|
// signed zero, infinities, NaNs, subnormals, and scientific notation.
|
|
//
|
|
// Width / alignment / pad / sign mods are honored. Precision and
|
|
// non-decimal bases are rejected until strconv grows ffmt/fflags.
|
|
|
|
@test fn fprintf_f64_precision_aborts() void = {
|
|
test.expectabort();
|
|
let buf: [16]u8;
|
|
let r: (str | io.error) = fmt.bsprintf(buf[0:16], "{:.1}", 1.5);
|
|
};
|
|
|
|
@test fn fprintf_f64_base_aborts() void = {
|
|
test.expectabort();
|
|
let buf: [16]u8;
|
|
let r: (str | io.error) = fmt.bsprintf(buf[0:16], "{:x}", 1.5);
|
|
};
|
|
|
|
@test fn fprintf_f64_basic() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{}", 1.5);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 3)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "1.5")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_f64_int_valued() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{}", 1.0);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 1)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "1")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_f64_neg() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{}", -2.5);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 4)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "-2.5")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_f64_zero() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{}", 0.0);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 1)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "0")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_f64_small_frac() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{}", 0.05);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 4)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "0.05")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
// Pins a large magnitude through the fmt dispatch — post-Ryū-graduation
|
|
// (fold-5) this is scientific notation, not the old lossy "huge" fallback.
|
|
// Catches any regression where mods accidentally pre-truncate the view.
|
|
@test fn fprintf_f64_huge() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{}", 9.5e18);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 6)); }; // "9.5e18"
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "9.5e18")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
// Sign-mod fold: '+' on positive, '-' still wins on negative (the
|
|
// natural '-' from strconv is peeled and signof reapplies).
|
|
@test fn fprintf_f64_sign_plus() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{:+} {:+}", 1.5, -1.5);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 9)); }; // "+1.5 -1.5"
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "+1.5 -1.5")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_f64_sign_space() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{: }", 1.5);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 4)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), " 1.5")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
// Width + alignment round-trip through rawlenf64 (sign-aware len count).
|
|
@test fn fprintf_f64_width_right() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{:8}", 1.5);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 8)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), " 1.5")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn fprintf_f64_width_left() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprintf(s, "{:-8}", 1.5);
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 8)); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "1.5 ")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
// Legacy fprint variadic — covers the non-printf surface arm (where
|
|
// formatraw isn't reached because there's no mods parser).
|
|
@test fn fprint_f64_variadic() void = {
|
|
let mem: memio.stream = memio.dynamic();
|
|
let s: io.stream = &mem.vt;
|
|
let r: (size | io.error) = fmt.fprint(s, 1.5, "x");
|
|
match (r) {
|
|
case let n: size => { assert(!(n: i32 != 5)); }; // "1.5 x"
|
|
case let eioe: io.error => abort();
|
|
};
|
|
assert(!(!streq(memio.string(&mem), "1.5 x")));
|
|
let c: (void | io.error) = io.close(s);
|
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
|
};
|
|
|
|
@test fn bsprintf_f64() void = {
|
|
let buf: [16]u8;
|
|
let r: (str | io.error) = fmt.bsprintf(buf[0:16], "x={}", 1.5);
|
|
match (r) {
|
|
case let s: str => { assert(!(!streq(s, "x=1.5"))); };
|
|
case let eioe: io.error => abort();
|
|
};
|
|
};
|
|
|
|
// Heap sink + both signs.
|
|
@test fn asprintf_f64() void = {
|
|
let r: str = fmt.asprintf("{} {}", 1.5, -2.5);
|
|
assert(!(!streq(r, "1.5 -2.5")));
|
|
os.free(r.ptr: *void, r.len: u64);
|
|
};
|