// 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). package memio; import bytes; import io; import memio; import os; // signalled — bumped by main before each test so a failing exit code // pinpoints the offending case. let signalled: i32 = 0; fn fail() void = { os.exit(signalled + 10); }; 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 mem: memio.state; let s: io.stream; memio.fixed(&mem, &s, arr[0:8]); 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: (i32 | io.eof | io.closed) = io.read(&s, out[0:req[i]]); match (r) { case let n: i32 => { if (weof[i] != 0) { fail(); }; if (n != wantn[i]) { fail(); }; if (n > 0 && out[0] != wantf[i]) { fail(); }; if (n > 0 && out[n - 1] != wantl[i]) { fail(); }; }; case io.eof => { if (weof[i] == 0) { fail(); }; }; case io.closed => fail(); }; i += 1; }; // closenoop on a fixed stream returns void. let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; // ---- fixedwritecases: full-fit / exact-fill / partial / overflow ------------- @test fn fixedwritecases() void = { let dst: [16]u8; let mem: memio.state; let s: io.stream; memio.fixed(&mem, &s, dst[0:16]); // 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) — wantn diverges from inlen on the // partial and overflow rows. let off: [4]i32; let ln: [4]i32; let wantn: [4]i32; off[0]=0; ln[0]=6; wantn[0]=6; // full fit off[1]=6; ln[1]=7; wantn[1]=7; // exact-fills cap (pos=13) off[2]=13; ln[2]=7; wantn[2]=3; // partial: 3 free off[3]=20; ln[3]=1; wantn[3]=0; // overflow: 0 free → 0 let i: i32 = 0; for (i < 4) { let lo: i32 = off[i]; let hi: i32 = lo + ln[i]; let r: (i32 | io.closed) = io.write(&s, src[lo:hi]); match (r) { case let n: i32 => { if (n != wantn[i]) { fail(); }; }; case io.closed => fail(); }; i += 1; }; let want: [16]u8; let _: i32 = putstr("hello world!!XXX", want[0:16], 0); if (!bytes.equal(dst[0:16], want[0:16])) { fail(); }; // 0-byte write is always 0 with no side effects. let r0: (i32 | io.closed) = io.write(&s, src[0:0]); match (r0) { case let n: i32 => { if (n != 0) { fail(); }; }; case io.closed => fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; // ---- dynamicgrow: every cap doubling exercised -------------------------- // Drive grow 0 → 8 → 16 → 32 by writing sized chunks. Verify // accumulated `pos` after each step. @test fn dynamicgrow() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); 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: (i32 | io.closed) = io.write(&s, src[lo:hi]); match (r) { case let n: i32 => { if (n != chunk[i]) { fail(); }; }; case io.closed => fail(); }; if (memio.buffer(&mem).len != total[i]) { fail(); }; off += chunk[i]; i += 1; }; if (!bytes.equal(memio.buffer(&mem), src[0:26])) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; // ---- 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 mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); 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(&mem); off = 0; } else { let lo: i32 = off; let hi: i32 = lo + ln[i]; let r: (i32 | io.closed) = io.write(&s, src[lo:hi]); match (r) { case let n: i32 => { if (n != ln[i]) { fail(); }; }; case io.closed => fail(); }; off += ln[i]; }; if (memio.buffer(&mem).len != want[i]) { fail(); }; i += 1; }; if (!bytes.equal(memio.buffer(&mem), src[0:8])) { fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; // ---- 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 mem: memio.state; let s: io.stream; memio.fixed(&mem, &s, 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(&mem, amt[i]); match (r) { case let v: []u8 => { if (weof[i] != 0) { fail(); }; if (v.len != amt[i]) { fail(); }; if (v.len > 0 && v[0] != wf[i]) { fail(); }; if (v.len > 0 && v[v.len - 1] != wl[i]) { fail(); }; }; case io.eof => { if (weof[i] == 0) { fail(); }; }; }; i += 1; }; }; // ---- stringview: len + endpoints track pos across appends --------------- @test fn stringview() void = { let mem: memio.state; let s: io.stream; memio.dynamic(&mem, &s); if (memio.string(&mem).len != 0) { fail(); }; 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: (i32 | io.closed) = io.write(&s, src[lo:hi]); match (r) { case let n: i32 => {}; case io.closed => fail(); }; let v: str = memio.string(&mem); if (v.len != want[i]) { fail(); }; if (v[0] != wf[i]) { fail(); }; if (v[v.len - 1] != wl[i]) { fail(); }; i += 1; }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; // ---- 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 `m.cap = buf.cap` // fix in lib/memio/memio.ww. let seed: []u8; seed.ptr = nil; seed.len = 0; seed.cap = 0; append(seed, 100u8, 101u8, 102u8, 103u8); let mem: memio.state; let s: io.stream; memio.dynamicfrom(&mem, &s, seed); // (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: (i32 | io.eof | io.closed) = io.read(&s, out[0:rn[i]]); match (r) { case let n: i32 => { if (weof[i] != 0) { fail(); }; if (n != rn[i]) { fail(); }; if (out[0] != want[i]) { fail(); }; }; case io.eof => { if (weof[i] == 0) { fail(); }; }; case io.closed => fail(); }; 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: (i32 | io.closed) = io.write(&s, extra[0:4]); match (w) { case let n: i32 => { if (n != 4) { fail(); }; }; case io.closed => fail(); }; let c: (void | io.closed) = io.close(&s); match (c) { case void => {}; case io.closed => fail(); }; }; export fn main() i32 = { signalled = 1; fixedread(); signalled = 2; fixedwritecases(); signalled = 3; dynamicgrow(); signalled = 4; dynamicreset(); signalled = 5; borrowedreadcases(); signalled = 6; stringview(); signalled = 7; dynamicfromseed(); return 0; };