Files
ww/lib/errors/errnotest.ww
Hojun-Cho fc9b486fb4 lib: port errors.errno + os errno/strerror sys-layer (#6)
Hare-faithful port of errors::errno (ref/hare/errors/{rt,common,opaque}.ha): the 13 named common error conditions, opaque_data/opaque_ (the type-erased tail whose strerror fn-ptr defers to os.strerror), and errno(os.errno) error mapping the ~12 mapped errnos to named conditions and wrapping the unmapped tail in opaque_. The raw errno type (!i32, kernel-int width, distinct from oserror's !i64 negative raw return), the E* constants, and the strerror message table live in lib/os: ww folds Hare's sys role into os, so os is the import floor that lib/io and lib/errors build on -- documented in lib/CLAUDE.md (os never imports io or errors). errors.error is explicitly enumerated, matching Hare; the ...errors::error spread is only io.error's (blocked by #199b). Prereq for post-eFinal #5's faithful io error mapping; retires the nomem-collapse interim. Adds errnotest (mapping / opaque-tail / strerror) + test/wcc/902_errno_run. Landing required two wwstage cgen fixes (#9 struct-variant-large-union return, #11 deref-store alias narrow). Divergences cited at-site: bare-type-name return -> let+return; switch fall-through vs Hare's exhaustiveness-only default; opaque_ const dropped.
2026-05-30 05:58:06 +09:00

124 lines
3.6 KiB
Plaintext

// errnotest — exercises [[errors.errno]] and the [[errors.opaque_]]
// tail. Run with `out/bin/ww run lib/errors/errnotest.ww`.
//
// Parallel `[N]T` arrays of (errno, expected-tag) rather than a
// `[N]struct{...}` table — the cstage cgen's chained `arr[i].field`
// store gap (task #6). [[errtag]] collapses each named-void variant to
// a small int so the row body is a single integer compare.
package errors;
import errors;
import os;
@symbol("rt_syscall") fn syscall1ww(num: i64, a: i64) i64;
fn doexit(code: i32) void = {
syscall1ww(60i64, code: i64);
};
// signalled — bumped by main before each test so a failing exit code
// pinpoints the offending case.
let signalled: i32 = 0;
fn fail() void = { doexit(signalled + 10); };
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;
};
// errtag — the union discriminant as a stable int. opaque_ is 99 so a
// misrouted named condition can't masquerade as the unmapped tail.
fn errtag(e: errors.error) int = {
match (e) {
case errors.busy => return 1;
case errors.exists => return 2;
case errors.invalid => return 3;
case errors.noaccess => return 4;
case errors.noentry => return 5;
case errors.overflow => return 6;
case errors.unsupported => return 7;
case errors.timeout => return 8;
case errors.cancelled => return 9;
case errors.refused => return 10;
case errors.interrupted => return 11;
case errors.again => return 12;
case errors.netunreachable => return 13;
case let o: errors.opaque_ => return 99;
};
};
// ---- errno → named condition --------------------------------------
@test fn mapping() void = {
let ins: [12]os.errno;
ins[0] = os.ECONNREFUSED;
ins[1] = os.ECANCELED;
ins[2] = os.EOVERFLOW;
ins[3] = os.EACCES;
ins[4] = os.EINVAL;
ins[5] = os.EEXIST;
ins[6] = os.ENOENT;
ins[7] = os.ETIMEDOUT;
ins[8] = os.EBUSY;
ins[9] = os.EINTR;
ins[10] = os.EAGAIN;
ins[11] = os.ENETUNREACH;
let exp: [12]int;
exp[0] = 10; // refused
exp[1] = 9; // cancelled
exp[2] = 6; // overflow
exp[3] = 4; // noaccess
exp[4] = 3; // invalid
exp[5] = 2; // exists
exp[6] = 5; // noentry
exp[7] = 8; // timeout
exp[8] = 1; // busy
exp[9] = 11; // interrupted
exp[10] = 12; // again
exp[11] = 13; // netunreachable
let i: i32 = 0;
for (i < 12) {
// store-to-local, not inline errtag(errors.errno(ins[i])): the
// inline form hits #222 (large >4-eightbyte union sret-arg
// clobber, cs==ww). store-then-consume is the idiomatic shape
// regardless — it is how errno is actually used (cf
// lib/io/stream.ww:53, and Hare's errors/rt.ha callers).
let er: errors.error = errors.errno(ins[i]);
if (errtag(er) != exp[i]) { fail(); };
i += 1;
};
};
// ---- unmapped errno → opaque_ tail --------------------------------
@test fn opaquetail() void = {
// EIO (5) is outside the mapped set, so it wraps opaque_.
let e: errors.error = errors.errno(5);
if (errtag(e) != 99) { fail(); };
match (e) {
case let o: errors.opaque_ =>
if (!streq((*o.strerror)(&o.data), "Unknown error")) { fail(); };
case =>
fail();
};
};
// ---- os.strerror mapped path --------------------------------------
@test fn strerrortext() void = {
if (!streq(os.strerror(os.ENOENT), "No such file or directory")) { fail(); };
if (!streq(os.strerror(os.EINVAL), "Invalid argument")) { fail(); };
if (!streq(os.strerror(5), "Unknown error")) { fail(); };
};
export fn main() i32 = {
signalled = 1; mapping();
signalled = 2; opaquetail();
signalled = 3; strerrortext();
return 0;
};