// 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)); };