// 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_test; import errors; 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 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; };