Files
ww/lib/memio/memio_test.ww
Hojun-Cho fa4b9a134b lib: migrate every test file to the canonical *_test.ww name
25 renames (git mv, content untouched). _test.ww is what the package
coordinator's test detection and the sep loader's canonical exclusion
key on; the old *test.ww spellings survived only through the
line-leading-@test compatibility scan. Consumers updated in place:
LIBRARY_TESTS, the libbyteid roster, the 901/974/975/976 carriers that
copy or invoke these files, and the check.c/check.ww + path/ftos
comments that cite them. Closes the open-driver-work migration bullet.
2026-08-08 04:29:31 +09:00

570 lines
16 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// memiotest — exercises lib/memio. Run with `out/bin/ww run lib/memio/memiotest.ww`.
//
// Every @test enumerates parallel `[N]T` arrays of inputs and
// expectations, then iterates one body across them. Parallel arrays
// (rather than `[N]struct{...}`) sidestep the cstage cgen's chained
// `arr[i].field` store gap (task #6). #94 fold-eFinal: the unified
// value-return surface (`let st = memio.fixed(buf); &st.vt` into the
// io dispatchers; io.read/write/close return size/io.error).
package memio_test;
import bytes;
import errors;
import io;
import memio;
fn putstr(s: str, into: []u8, off: i32) i32 = {
let i: i32 = 0;
for (i < s.len) {
into[off + i] = s[i];
i += 1;
};
return off + s.len;
};
// ---- fixedread: read sizes drive partial / full / eof outcomes ----------
@test fn fixedread() void = {
let arr: [8]u8;
arr[0] = 1u8; arr[1] = 2u8; arr[2] = 3u8; arr[3] = 4u8;
arr[4] = 5u8; arr[5] = 6u8; arr[6] = 7u8; arr[7] = 8u8;
// (request, wantn, wantfirst, wantlast, weof)
// 5 rows: under-remaining, full slot, last-byte-only, post-end, 0-len-at-end.
let req: [5]i32;
let wantn: [5]i32;
let wantf: [5]u8;
let wantl: [5]u8;
let weof: [5]i32;
req[0]=3; wantn[0]=3; wantf[0]=1u8; wantl[0]=3u8; weof[0]=0;
req[1]=4; wantn[1]=4; wantf[1]=4u8; wantl[1]=7u8; weof[1]=0;
req[2]=5; wantn[2]=1; wantf[2]=8u8; wantl[2]=8u8; weof[2]=0;
req[3]=1; wantn[3]=0; wantf[3]=0u8; wantl[3]=0u8; weof[3]=1;
req[4]=0; wantn[4]=0; wantf[4]=0u8; wantl[4]=0u8; weof[4]=1;
let st: memio.stream = memio.fixed(arr[0:8]);
let s: io.stream = &st.vt;
let out: [8]u8;
let i: i32 = 0;
for (i < 5) {
// Clear out so spot checks read real data, not stale bytes.
let k: i32 = 0;
for (k < 8) { out[k] = 0u8; k += 1; };
let r: (size | io.eof | io.error) = io.read(s, out[0:req[i]]);
match (r) {
case let nz: size => {
let n: i32 = nz: i32;
assert(!(weof[i] != 0));
assert(!(n != wantn[i]));
assert(!(n > 0 && out[0] != wantf[i]));
assert(!(n > 0 && out[n - 1] != wantl[i]));
};
case io.eof => { assert(!(weof[i] == 0)); };
case let e: io.error => abort();
};
i += 1;
};
// fixed stream has no closer → io.close returns void.
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let e: io.error => abort(); };
};
// ---- fixedwritecases: full-fit / exact-fill / partial / overflow -------------
@test fn fixedwritecases() void = {
let dst: [16]u8;
let st: memio.stream = memio.fixed(dst[0:16]);
let s: io.stream = &st.vt;
// One flat source; rows index into it via (off, len). Sequence:
// "hello " (6), "world!!" (7), "XXXXXXX" (7), "Y" (1).
let src: [32]u8;
let _: i32 = putstr("hello world!!XXXXXXXY", src[0:32], 0);
// (srcoff, inlen, wantn, wantnomem) — wantn diverges from inlen on
// the partial row; the overflow row writes into a now-full sink and
// returns nomem (ref/hare/memio/stream.ha:161).
let off: [4]i32;
let ln: [4]i32;
let wantn: [4]i32;
let wantnomem: [4]bool;
off[0]=0; ln[0]=6; wantn[0]=6; wantnomem[0]=false; // full fit
off[1]=6; ln[1]=7; wantn[1]=7; wantnomem[1]=false; // exact-fills cap (pos=13)
off[2]=13; ln[2]=7; wantn[2]=3; wantnomem[2]=false; // partial: 3 free
off[3]=20; ln[3]=1; wantn[3]=0; wantnomem[3]=true; // overflow: full sink → nomem
let i: i32 = 0;
for (i < 4) {
let lo: i32 = off[i];
let hi: i32 = lo + ln[i];
let r: (size | io.error) = io.write(s, src[lo:hi]);
match (r) {
case let n: size => {
assert(!wantnomem[i]);
assert(!(n: i32 != wantn[i]));
};
case let e: io.error => {
assert(wantnomem[i]);
assert(e is nomem);
};
};
i += 1;
};
let want: [16]u8;
let _: i32 = putstr("hello world!!XXX", want[0:16], 0);
assert(!(!bytes.equal(dst[0:16], want[0:16])));
// 0-byte write is always 0 with no side effects.
let r0: (size | io.error) = io.write(s, src[0:0]);
match (r0) {
case let n: size => { assert(!(n != 0: size)); };
case let e: io.error => abort();
};
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let e: io.error => abort(); };
};
// ---- fixedwritefull: the full-sink nomem contract (memio.ww:190) -------
// A write that exactly fills the sink succeeds; any further non-empty
// write into the full sink returns nomem, not a 0-byte success
// (ref/hare/memio/stream.ha:161). A zero-length sink is full from the
// start, so its first non-empty write is already nomem.
@test fn fixedwritefull() void = {
let buf: [4]u8;
let st: memio.stream = memio.fixed(buf[0:4]);
let s: io.stream = &st.vt;
let src: [4]u8;
let _: i32 = putstr("abcd", src[0:4], 0);
let r4: (size | io.error) = io.write(s, src[0:4]); // exact fill
match (r4) {
case let n: size => { assert(!(n != 4: size)); };
case let e: io.error => abort();
};
let r1: (size | io.error) = io.write(s, src[0:1]); // full sink
match (r1) {
case let n: size => abort();
case let e: io.error => { assert(e is nomem); };
};
let ze: memio.stream = memio.fixed(buf[0:0]);
let zs: io.stream = &ze.vt;
let rz: (size | io.error) = io.write(zs, src[0:1]); // zero-len sink
match (rz) {
case let n: size => abort();
case let e: io.error => { assert(e is nomem); };
};
};
// ---- dynamicgrowcases: every cap doubling exercised ---------------------
// Drive grow 0 → 8 → 16 → 32 by writing sized chunks. Verify
// accumulated `pos` after each step.
@test fn dynamicgrowcases() void = {
let st: memio.stream = memio.dynamic();
let s: io.stream = &st.vt;
let src: [32]u8;
let _: i32 = putstr("abcdefghijklmnopqrstuvwxyz012345", src[0:32], 0);
// (chunkn, totaln) — cap transitions: 0→8 at row 0; 8→16 at row 2;
// 16→32 at row 4.
let chunk: [5]i32;
let total: [5]i32;
chunk[0]=3; total[0]=3;
chunk[1]=5; total[1]=8;
chunk[2]=1; total[2]=9;
chunk[3]=7; total[3]=16;
chunk[4]=10; total[4]=26;
let off: i32 = 0;
let i: i32 = 0;
for (i < 5) {
let lo: i32 = off;
let hi: i32 = lo + chunk[i];
let r: (size | io.error) = io.write(s, src[lo:hi]);
match (r) {
case let n: size => { assert(!(n: i32 != chunk[i])); };
case let e: io.error => abort();
};
assert(!(memio.buffer(&st).len != total[i]));
off += chunk[i];
i += 1;
};
assert(!(!bytes.equal(memio.buffer(&st), src[0:26])));
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let e: io.error => abort(); };
};
// ---- dynamicreset: write / reset / write cycles -------------------------
// op=0 writes `ln` bytes from a rolling source; op=1 resets and ignores
// ln. After each row the accumulated len must equal `want`.
@test fn dynamicreset() void = {
let st: memio.stream = memio.dynamic();
let s: io.stream = &st.vt;
let src: [16]u8;
src[0]=10u8; src[1]=20u8; src[2]=30u8; src[3]=40u8;
src[4]=50u8; src[5]=60u8; src[6]=70u8; src[7]=80u8;
let op: [6]i32;
let ln: [6]i32;
let want: [6]i32;
op[0]=0; ln[0]=5; want[0]=5; // initial write
op[1]=1; ln[1]=0; want[1]=0; // reset (after writes)
op[2]=0; ln[2]=3; want[2]=3; // refill from 0
op[3]=0; ln[3]=2; want[3]=5; // continue
op[4]=1; ln[4]=0; want[4]=0; // reset (after partial)
op[5]=0; ln[5]=8; want[5]=8; // larger refill
let off: i32 = 0;
let i: i32 = 0;
for (i < 6) {
if (op[i] == 1) {
memio.reset(&st);
off = 0;
} else {
let lo: i32 = off;
let hi: i32 = lo + ln[i];
let r: (size | io.error) = io.write(s, src[lo:hi]);
match (r) {
case let n: size => { assert(!(n: i32 != ln[i])); };
case let e: io.error => abort();
};
off += ln[i];
};
assert(!(memio.buffer(&st).len != want[i]));
i += 1;
};
assert(!(!bytes.equal(memio.buffer(&st), src[0:8])));
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let e: io.error => abort(); };
};
// ---- borrowedread: under / exact / over / 0-byte ------------------------
@test fn borrowedreadcases() void = {
let arr: [6]u8;
arr[0]=0u8; arr[1]=1u8; arr[2]=2u8; arr[3]=3u8; arr[4]=4u8; arr[5]=5u8;
let st: memio.stream = memio.fixed(arr[0:6]);
// (amt, wantfirst, wantlast, weof)
let amt: [4]i32;
let wf: [4]u8;
let wl: [4]u8;
let weof: [4]i32;
amt[0]=4; wf[0]=0u8; wl[0]=3u8; weof[0]=0; // under remaining
amt[1]=2; wf[1]=4u8; wl[1]=5u8; weof[1]=0; // exact remaining
amt[2]=1; wf[2]=0u8; wl[2]=0u8; weof[2]=1; // past end → eof
amt[3]=0; wf[3]=0u8; wl[3]=0u8; weof[3]=0; // 0-byte view always ok
let i: i32 = 0;
for (i < 4) {
let r: ([]u8 | io.eof) = memio.borrowedread(&st, amt[i]);
match (r) {
case let v: []u8 => {
assert(!(weof[i] != 0));
assert(!(v.len != amt[i]));
assert(!(v.len > 0 && v[0] != wf[i]));
assert(!(v.len > 0 && v[v.len - 1] != wl[i]));
};
case io.eof => { assert(!(weof[i] == 0)); };
};
i += 1;
};
};
// ---- stringview: len + endpoints track pos across appends ---------------
@test fn stringview() void = {
let st: memio.stream = memio.dynamic();
let s: io.stream = &st.vt;
assert(!(memio.string(&st).len != 0));
let src: [32]u8;
let _: i32 = putstr("hello world!!", src[0:32], 0);
// (srcoff, inlen, wantacc, wantfirst, wantlast)
let off: [3]i32;
let ln: [3]i32;
let want: [3]i32;
let wf: [3]u8;
let wl: [3]u8;
off[0]=0; ln[0]=5; want[0]=5; wf[0]=104u8; wl[0]=111u8; // "hello" h..o
off[1]=5; ln[1]=6; want[1]=11; wf[1]=104u8; wl[1]=100u8; // +" world" h..d
off[2]=11; ln[2]=2; want[2]=13; wf[2]=104u8; wl[2]=33u8; // +"!!" h..!
let i: i32 = 0;
for (i < 3) {
let lo: i32 = off[i];
let hi: i32 = lo + ln[i];
let r: (size | io.error) = io.write(s, src[lo:hi]);
match (r) { case let n: size => {}; case let e: io.error => abort(); };
let v: str = memio.string(&st);
assert(!(v.len != want[i]));
assert(!(v[0] != wf[i]));
assert(!(v[v.len - 1] != wl[i]));
i += 1;
};
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let e: io.error => abort(); };
};
// ---- dynamicfromseed: ownership-transferred seed, read then write -------
@test fn dynamicfromseed() void = {
// Build a heap-allocated seed via append (rt_ensure path), then
// hand ownership to memio. dynamicclose then frees `seed.cap`
// bytes — the regression that motivated the `cap = buf.cap` fix.
let seed: []u8;
seed.ptr = nil; seed.len = 0; seed.cap = 0;
append(seed, 100u8, 101u8, 102u8, 103u8);
let st: memio.stream = memio.dynamicfrom(seed);
let s: io.stream = &st.vt;
// (readn, wantbyte, weof)
let rn: [5]i32;
let want: [5]u8;
let weof: [5]i32;
rn[0]=1; want[0]=100u8; weof[0]=0;
rn[1]=1; want[1]=101u8; weof[1]=0;
rn[2]=1; want[2]=102u8; weof[2]=0;
rn[3]=1; want[3]=103u8; weof[3]=0;
rn[4]=1; want[4]=0u8; weof[4]=1; // past seed end → eof
let out: [4]u8;
let i: i32 = 0;
for (i < 5) {
let r: (size | io.eof | io.error) = io.read(s, out[0:rn[i]]);
match (r) {
case let nz: size => {
let n: i32 = nz: i32;
assert(!(weof[i] != 0));
assert(!(n != rn[i]));
assert(!(out[0] != want[i]));
};
case io.eof => { assert(!(weof[i] == 0)); };
case let e: io.error => abort();
};
i += 1;
};
// Now write extends past the seed; grow path runs and close
// must free the (post-grow) cap, not the seed cap.
let extra: [4]u8;
extra[0]=200u8; extra[1]=201u8; extra[2]=202u8; extra[3]=203u8;
let w: (size | io.error) = io.write(s, extra[0:4]);
match (w) {
case let n: size => { assert(!(n: i32 != 4)); };
case let e: io.error => abort();
};
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let e: io.error => abort(); };
};
// ---- seekcases: SET/CUR/END × in-bounds/OOB over one fixed stream --------
// Sequential rows over one 8-byte fixed stream; CUR rows depend on the
// cursor left by earlier rows. want = resulting pos for valid rows,
// -1 = errors.invalid expected. After EVERY row an io.tell readback
// checks the cursor against a shadow `pos` — invalid rows must leave
// it untouched (ref/hare/memio/stream.ha:122-140 returns before the
// pos store).
@test fn seekcases() void = {
let arr: [8]u8;
arr[0]=10u8; arr[1]=11u8; arr[2]=12u8; arr[3]=13u8;
arr[4]=14u8; arr[5]=15u8; arr[6]=16u8; arr[7]=17u8;
// (whence, off, want)
let wh: [12]i32; // 0 SET / 1 CUR / 2 END
let off: [12]i64;
let want: [12]i64;
wh[0] =0; off[0] =3; want[0] =3; // SET forward
wh[1] =1; off[1] =2; want[1] =5; // CUR forward
wh[2] =1; off[2] =-5; want[2] =0; // CUR back to start
wh[3] =2; off[3] =0; want[3] =8; // END exact end
wh[4] =2; off[4] =-8; want[4] =0; // END back to start
wh[5] =0; off[5] =9; want[5] =-1; // SET past end
wh[6] =1; off[6] =-1; want[6] =-1; // CUR before start
wh[7] =2; off[7] =1; want[7] =-1; // END past end
wh[8] =2; off[8] =-9; want[8] =-1; // END before start
wh[9] =0; off[9] =8; want[9] =8; // SET end exact
wh[10]=1; off[10]=0; want[10]=8; // CUR no-op at end
wh[11]=0; off[11]=0; want[11]=0; // SET rewind
let st: memio.stream = memio.fixed(arr[0:8]);
let s: io.stream = &st.vt;
let pos: i64 = 0;
let i: i32 = 0;
for (i < 12) {
let w: io.whence = wh[i]: io.whence;
let r: (io.off | io.error) = io.seek(s, off[i], w);
match (r) {
case let o: io.off => {
assert(!(want[i] < 0));
assert(!((o: i64) != want[i]));
pos = o: i64;
};
case let e: io.error => {
assert(!(want[i] >= 0));
match (e) {
case errors.invalid => {};
case => abort();
};
};
};
let t: (io.off | io.error) = io.tell(s);
match (t) {
case let o: io.off => { assert(!((o: i64) != pos)); };
case let e: io.error => abort();
};
i += 1;
};
// Data readback through the repositioned cursor: SET 5 then read
// drains bytes 5..7, then eof.
let r: (io.off | io.error) = io.seek(s, 5, io.whence.SET);
match (r) {
case let o: io.off => { assert(!((o: i64) != 5)); };
case let e: io.error => abort();
};
let out: [8]u8;
let rd: (size | io.eof | io.error) = io.read(s, out[0:8]);
match (rd) {
case let nz: size => {
assert(!(nz: i32 != 3));
assert(!(out[0] != 15u8));
assert(!(out[2] != 17u8));
};
case io.eof => abort();
case let e: io.error => abort();
};
let rd2: (size | io.eof | io.error) = io.read(s, out[0:8]);
match (rd2) {
case io.eof => {};
case let nz: size => abort();
case let e: io.error => abort();
};
};
// ---- emptyseek: every whence over a 0-length buffer ----------------------
@test fn emptyseek() void = {
let arr: [1]u8;
arr[0] = 0u8;
let st: memio.stream = memio.fixed(arr[0:0]);
let s: io.stream = &st.vt;
let wh: [5]i32;
let off: [5]i64;
let want: [5]i64;
wh[0]=0; off[0]=0; want[0]=0; // SET 0 — only valid target
wh[1]=2; off[1]=0; want[1]=0; // END 0 == start
wh[2]=0; off[2]=1; want[2]=-1;
wh[3]=1; off[3]=-1; want[3]=-1;
wh[4]=2; off[4]=-1; want[4]=-1;
let i: i32 = 0;
for (i < 5) {
let w: io.whence = wh[i]: io.whence;
let r: (io.off | io.error) = io.seek(s, off[i], w);
match (r) {
case let o: io.off => {
assert(!(want[i] < 0));
assert(!((o: i64) != want[i]));
};
case let e: io.error => {
assert(!(want[i] >= 0));
match (e) {
case errors.invalid => {};
case => abort();
};
};
};
i += 1;
};
};
// ---- dynamicseek: rewind-and-reread over written data --------------------
// The dynamic vtable wires the same seekfn; bounds run against the
// LOGICAL length (m.len), not capacity (Hare: len(s.buf),
// ref/hare/memio/stream.ha:131,136).
@test fn dynamicseek() void = {
let st: memio.stream = memio.dynamic();
let s: io.stream = &st.vt;
let src: [5]u8;
src[0]=30u8; src[1]=31u8; src[2]=32u8; src[3]=33u8; src[4]=34u8;
let wr: (size | io.error) = io.write(s, src[0:5]);
match (wr) {
case let n: size => { assert(!(n: i32 != 5)); };
case let e: io.error => abort();
};
// END 0 → 5 (logical length); cap is 8 post-grow, so END 1 must
// still be invalid even though capacity would fit it.
let r: (io.off | io.error) = io.seek(s, 0, io.whence.END);
match (r) {
case let o: io.off => { assert(!((o: i64) != 5)); };
case let e: io.error => abort();
};
let r1: (io.off | io.error) = io.seek(s, 1, io.whence.END);
match (r1) {
case let o: io.off => abort();
case let e: io.error => {
match (e) {
case errors.invalid => {};
case => abort();
};
};
};
// CUR -2 → 3; read drains 33,34 then eof.
let r2: (io.off | io.error) = io.seek(s, -2, io.whence.CUR);
match (r2) {
case let o: io.off => { assert(!((o: i64) != 3)); };
case let e: io.error => abort();
};
let out: [4]u8;
let rd: (size | io.eof | io.error) = io.read(s, out[0:4]);
match (rd) {
case let nz: size => {
assert(!(nz: i32 != 2));
assert(!(out[0] != 33u8));
assert(!(out[1] != 34u8));
};
case io.eof => abort();
case let e: io.error => abort();
};
let rd2: (size | io.eof | io.error) = io.read(s, out[0:4]);
match (rd2) {
case io.eof => {};
case let nz: size => abort();
case let e: io.error => abort();
};
let c: (void | io.error) = io.close(s);
match (c) { case void => {}; case let e: io.error => abort(); };
};