Files
ww/lib/temp/temp_test.ww
Hojun-Cho aadc6618f0 lib: banner purge + WHY-only comment sweep (rule 8)
Every // ---- section banner dies (132 -> 0): names carry the WHAT.
Narration deleted (filename restatements, run-with lines, what-the-
next-line-does); every ref/hare cite, task cite, divergence, ABI/
layout contract, and ownership qualifier kept (borrowed-view lines
restored where the sweep over-cut). Comment-only proven: all 442
walk-workdir .s and 32 import-probe .s byte-identical before/after;
libbyteid 56-roster all-ID.
2026-08-08 21:10:18 +09:00

231 lines
6.4 KiB
Plaintext

// 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).
//
// Cleanup is the tests' responsibility — temp ships no defer hook
// (deliberate divergence from Hare; see lib/temp/temp.ww header).
// Each test [[os.close]]s every fd it opens and [[os.remove]] /
// [[os.rmdir]]s every path it returns, so a regression here would
// leave detritus under /tmp. `ls /tmp` before/after each run should
// match.
package temp_test;
import os;
import temp;
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;
};
@test fn namedroundtrip() void = {
// (payload size, fill byte). Row 0 covers the zero-byte edge.
let sz: [4]i32;
let fill: [4]u8;
sz[0]=0; fill[0]=0u8;
sz[1]=1; fill[1]=33u8; // '!'
sz[2]=4; fill[2]=65u8; // 'A'
sz[3]=64; fill[3]=90u8; // 'Z'
let i: i32 = 0;
for (i < 4) {
let fd: i32 = 0;
let p: str;
match (temp.named(&fd, &p, "/tmp", temp.mode.RDWR, 384i32)) { // 0o600
case void => {};
case let e: os.oserror => abort();
};
assert(!(fd < 0));
assert(!(p.len < 10)); // at minimum "/tmp/temp.<1 hex>"
// Path bytes must start with "/tmp/temp." and live in temp's
// static buffer (NUL-terminated for syscall handoff).
assert(!(p[0] != 47u8)); // '/'
assert(!(!streq(strslice(p, 0, 10), "/tmp/temp.")));
assert(!(p.ptr[p.len] != 0u8));
let wbuf: [64]u8;
let k: i32 = 0;
for (k < sz[i]) { wbuf[k] = fill[i]; k += 1; };
if (sz[i] > 0) {
let wr: i64 = os.write(fd, &wbuf[0], sz[i]: u64);
assert(!(wr != sz[i]: i64));
};
let r: i64 = os.lseek(fd, 0i64, os.whence.SET);
assert(!(r != 0i64));
let rbuf: [64]u8;
let z: i32 = 0;
for (z < 64) { rbuf[z] = 0u8; z += 1; };
let rd: i64 = 0i64;
if (sz[i] > 0) {
rd = os.read(fd, &rbuf[0], sz[i]: u64);
assert(!(rd != sz[i]: i64));
};
let k2: i32 = 0;
for (k2 < sz[i]) {
assert(!(rbuf[k2] != fill[i]));
k2 += 1;
};
assert(!(os.access(p, 0i32) != 0));
assert(!(os.close(fd) != 0));
assert(!(os.remove(p) != 0));
assert(!(os.access(p, 0i32) == 0));
i += 1;
};
};
// strslice — borrow `p[lo:hi]`. Inline because lib/strings.sub
// returns an allocated copy in some shapes; here we want a view.
fn strslice(p: str, lo: i32, hi: i32) str = {
let r: str;
r.ptr = p.ptr + (lo: u64);
r.len = hi - lo;
return r;
};
// Hare docs: "The name is statically allocated, and will be
// overwritten on subsequent calls." Match that contract — the second
// named() call lands in the same buffer, so p1.ptr == p2.ptr.
@test fn namedoverwrite() void = {
let fd1: i32 = 0;
let p1: str;
match (temp.named(&fd1, &p1, "/tmp", temp.mode.WRITE, 384i32)) {
case void => {};
case let e: os.oserror => abort();
};
// Snapshot p1's bytes BEFORE the second call clobbers the buffer,
// so we can compare p2 against the original p1 content and
// remove() the first file after closing it.
let snap: [128]u8;
let snaplen: i32 = p1.len;
let i: i32 = 0;
for (i < p1.len) { snap[i] = p1[i]; i += 1; };
snap[p1.len] = 0u8;
let psnap: str;
psnap.ptr = &snap[0];
psnap.len = snaplen;
let fd2: i32 = 0;
let p2: str;
match (temp.named(&fd2, &p2, "/tmp", temp.mode.WRITE, 384i32)) {
case void => {};
case let e: os.oserror => abort();
};
// Same buffer (Hare docs: "overwritten on subsequent calls"),
// distinct path bytes (random suffix differs).
assert(!(p1.ptr != p2.ptr));
assert(!(streq(strslice(p2, 0, p2.len), psnap)));
assert(!(fd1 == fd2));
assert(!(os.close(fd2) != 0));
assert(!(os.remove(p2) != 0));
assert(!(os.close(fd1) != 0));
assert(!(os.remove(psnap) != 0));
};
// NOTE: temp.file() has no test of its own. The function is a thin
// wrapper around temp.named() that DROPS the returned path, and the
// on-disk entry would leak until external cleanup (no O_TMPFILE in
// lib/os yet — see lib/temp/temp.ww header). Hare's +freebsd.ha
// has the same leak; the Hare +linux.ha fallback path does too.
// Re-add a file() test once O_TMPFILE lands and the leak goes away.
// Coverage for the underlying open+create+EXCL path lives in
// [[namedroundtrip]] / [[namedoverwrite]].
@test fn dirlifecycle() void = {
// (child count). Row 0: empty dir. Row 1: dir + one file.
let childn: [2]i32;
childn[0] = 0;
childn[1] = 1;
let i: i32 = 0;
for (i < 2) {
let d: str = temp.dir();
assert(!(d.len < 6));
assert(!(!streq(strslice(d, 0, 5), "/tmp/")));
assert(!(d.ptr[d.len] != 0u8));
assert(!(os.access(d, 0i32) != 0));
// Snapshot the dir path into a local NUL-terminated buffer:
// the os.* path entrypoints now copy through lib/os.pathbuf
// (kpath), so d's view into temp.pathbuf is safe; the
// snapshot still buys robustness against future helpers
// that might share temp's buffer.
let dsnap: [128]u8;
let dlen: i32 = d.len;
let s: i32 = 0;
for (s < d.len) { dsnap[s] = d[s]; s += 1; };
dsnap[d.len] = 0u8;
let dview: str;
dview.ptr = &dsnap[0]; dview.len = dlen;
if (childn[i] > 0) {
let cbuf: [144]u8;
let off: i32 = 0;
let j: i32 = 0;
for (j < dlen) { cbuf[off] = dsnap[j]; off += 1; j += 1; };
cbuf[off] = 47u8; off += 1; // '/'
cbuf[off] = 120u8; off += 1; // 'x'
cbuf[off] = 0u8;
let cview: str;
cview.ptr = &cbuf[0]; cview.len = off;
let cflags: os.flag = os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL;
let fd: i32 = os.open(cview, cflags, 384i32);
assert(!(fd < 0));
let payload: [3]u8;
payload[0] = 88u8; payload[1] = 89u8; payload[2] = 90u8; // "XYZ"
let wr: i64 = os.write(fd, &payload[0], 3u64);
assert(!(wr != 3i64));
assert(!(os.close(fd) != 0));
assert(!(os.remove(cview) != 0));
};
// Rmdir uses dview (more robust if the static buffer were
// touched between dir() and here).
assert(!(os.rmdir(dview) != 0));
assert(!(os.access(dview, 0i32) == 0));
i += 1;
};
};
@test fn diruniqueness() void = {
let d1: str = temp.dir();
let snap: [128]u8;
let snaplen: i32 = d1.len;
let i: i32 = 0;
for (i < d1.len) { snap[i] = d1[i]; i += 1; };
snap[d1.len] = 0u8;
let dsnap: str;
dsnap.ptr = &snap[0];
dsnap.len = snaplen;
let d2: str = temp.dir();
assert(!(d1.ptr != d2.ptr)); // same static buffer
assert(!(streq(strslice(d2, 0, d2.len), dsnap)));
// Cleanup both, using snap for d1's old contents.
assert(!(os.rmdir(d2) != 0));
assert(!(os.rmdir(dsnap) != 0));
};