From eb891f4007cad75f6044e380e6cf9b3a3de12c09 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 10 Jun 2026 18:52:53 +0900 Subject: [PATCH] lib/os+dirs tests: hand-main plumbing -> assert (@test conversion B6) WW_TEST_* env arrangement logic untouched; only the fail/exit plumbing converts. --- lib/dirs/dirstest.ww | 27 ++++++-------- lib/os/ostest.ww | 40 +++++++++----------- lib/os/stattest.ww | 88 +++++++++++++++++++++----------------------- 3 files changed, 72 insertions(+), 83 deletions(-) diff --git a/lib/dirs/dirstest.ww b/lib/dirs/dirstest.ww index eb7dc4be..9afe7e9a 100644 --- a/lib/dirs/dirstest.ww +++ b/lib/dirs/dirstest.ww @@ -26,21 +26,18 @@ // asserts immediately. Don't call two dirs.* fns and compare across // — the second clobbers the first's view. // -// `signalled` global bumps before each test so a failing exit code -// pinpoints the offending row (WEXITSTATUS = signalled + 10). Same -// shape as temptest / shlextest / fnmatchtest / ostest. +// A failing row aborts via the assert/abort builtin (task #5 @test +// conversion). // // Don't `use io;` here — lib/os and lib/io collide on read/write/ -// close (task #17). We need only os.getenv + os.exit. +// close (task #17). We need only os.getenv. package dirs_test; import dirs; import os; -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; }; @@ -87,7 +84,7 @@ fn gettmpdir() str = { match (os.getenv("WW_TEST_TMPDIR")) { case let s: str => return s; case void => { - fail(); + abort(); let empty: str; return empty; }; @@ -100,7 +97,7 @@ fn gettmpdir() str = { let tmpl = gettmpdir(); let exp = expected(tmpl, "cfg", "myapp"); let got = dirs.config("myapp"); - if (!streq(got, exp)) { fail(); }; + assert(!(!streq(got, exp))); }; // ---- 2: XDG_CACHE_HOME="relative/path" → fallback to $HOME/.cache -- @@ -112,7 +109,7 @@ fn gettmpdir() str = { let tmpl = gettmpdir(); let exp = expected(tmpl, ".cache", "myapp"); let got = dirs.cache("myapp"); - if (!streq(got, exp)) { fail(); }; + assert(!(!streq(got, exp))); }; // ---- 3: XDG_DATA_HOME unset → fallback to $HOME/.local/share ------- @@ -121,7 +118,7 @@ fn gettmpdir() str = { let tmpl = gettmpdir(); let exp = expected(tmpl, ".local/share", "myapp"); let got = dirs.data("myapp"); - if (!streq(got, exp)) { fail(); }; + assert(!(!streq(got, exp))); }; // ---- 4: XDG_STATE_HOME unset → fallback to $HOME/.local/state ------ @@ -130,13 +127,13 @@ fn gettmpdir() str = { let tmpl = gettmpdir(); let exp = expected(tmpl, ".local/state", "myapp"); let got = dirs.state("myapp"); - if (!streq(got, exp)) { fail(); }; + assert(!(!streq(got, exp))); }; export fn main() i32 = { - signalled = 1; test_config_xdg_set(); - signalled = 2; test_cache_xdg_relative_fallback(); - signalled = 3; test_data_unset_fallback(); - signalled = 4; test_state_unset_fallback(); + test_config_xdg_set(); + test_cache_xdg_relative_fallback(); + test_data_unset_fallback(); + test_state_unset_fallback(); return 0; }; diff --git a/lib/os/ostest.ww b/lib/os/ostest.ww index 3bed7ce4..d277e9fd 100644 --- a/lib/os/ostest.ww +++ b/lib/os/ostest.ww @@ -5,11 +5,9 @@ // cover alloc/free; the direct row here pins the FFI shape under // lib/os itself so future bindings refactors can't quietly drift. // -// Convention follows the stdlib `_run` test fixtures: hand-rolled -// @test fns dispatched from `main()` in numeric order, with a -// `signalled` global the main fn bumps so a failing case exits with -// `WEXITSTATUS = signalled + 10` — the harness then knows which -// scenario tripped. Same shape as temptest / shlextest / fnmatchtest. +// Hand-rolled @test fns dispatched from `main()` in source order; a +// failing case aborts via the assert/abort builtin (task #5 @test +// conversion). // // Pre-arranged env state (set by 974_getenv_run.c via setenv/unsetenv // before exec'ing `ww run lib/os/ostest.ww`): @@ -20,17 +18,15 @@ // // Don't `use io;` here — lib/os and lib/io both export read/write/ // close as C symbols that collide under the driver's flat-scope -// concat (task #17). os alone is sufficient: getenv + exit are the -// only primitives needed. +// concat (task #17). os alone is sufficient: getenv + alloc/free are +// the only primitives needed. package os_test; import os; import rt; -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; }; @@ -47,9 +43,9 @@ fn streq(a: str, b: str) bool = { @test fn test_getenv_set() void = { let r = os.getenv("WW_TEST_GETENV"); match (r) { - case void => fail(); + case void => abort(); case let s: str => { - if (!streq(s, "hello-world")) { fail(); }; + assert(!(!streq(s, "hello-world"))); }; }; }; @@ -63,9 +59,9 @@ fn streq(a: str, b: str) bool = { @test fn test_getenv_empty() void = { let r = os.getenv("WW_TEST_EMPTY"); match (r) { - case void => fail(); + case void => abort(); case let s: str => { - if (s.len != 0) { fail(); }; + assert(!(s.len != 0)); }; }; }; @@ -76,7 +72,7 @@ fn streq(a: str, b: str) bool = { let r = os.getenv("WW_TEST_NOT_SET"); match (r) { case void => {}; - case let s: str => fail(); + case let s: str => abort(); }; }; @@ -91,7 +87,7 @@ fn streq(a: str, b: str) bool = { let r = os.getenv("WW_TEST_GETEN"); match (r) { case void => {}; - case let s: str => fail(); + case let s: str => abort(); }; }; @@ -106,16 +102,16 @@ fn streq(a: str, b: str) bool = { p.len = 4096; p[0] = 90u8; // 0x5a p[4095] = 165u8; // 0xa5 - if (p[0] != 90u8) { fail(); }; - if (p[4095] != 165u8) { fail(); }; + assert(!(p[0] != 90u8)); + assert(!(p[4095] != 165u8)); os.free(p.ptr: *void, 4096u64); }; export fn main() i32 = { - signalled = 1; test_getenv_set(); - signalled = 2; test_getenv_empty(); - signalled = 3; test_getenv_unset(); - signalled = 4; test_getenv_prefix_no_match(); - signalled = 5; test_alloc_free_roundtrip(); + test_getenv_set(); + test_getenv_empty(); + test_getenv_unset(); + test_getenv_prefix_no_match(); + test_alloc_free_roundtrip(); return 0; }; diff --git a/lib/os/stattest.ww b/lib/os/stattest.ww index c0e01fcb..023de329 100644 --- a/lib/os/stattest.ww +++ b/lib/os/stattest.ww @@ -12,24 +12,20 @@ // WW_TEST_STAT_SUBDIR = "/subdir" directory, 0700 // WW_TEST_STAT_NOENT = "/does-not-exist" // -// Test exit code follows the stdlib `_run` convention: a `signalled` -// global the main fn bumps before each row, so `WEXITSTATUS = signalled -// + 10` tells the harness which row tripped. Same shape as -// ostest / temptest / shlextest. +// A failing row aborts via the assert/abort builtin (task #5 @test +// conversion). package os_test; import os; -let signalled: i32 = 0; -fn fail() void = { os.exit(signalled + 10); }; // envpath — fetch an env var or abort; we want a clean signal if // 976_stat_run.c didn't prime the env state. fn envpath(name: str) str = { match (os.getenv(name)) { - case void => { fail(); return "": str; }; + case void => { abort(); return "": str; }; case let s: str => return s; }; }; @@ -59,27 +55,27 @@ fn istype(m: os.mode, t: os.mode) bool = { | os.stat_mask.ATIME | os.stat_mask.MTIME | os.stat_mask.CTIME) as u32; match (os.stat(&fi, p)) { - case let e: os.oserror => fail(); + case let e: os.oserror => abort(); case void => { - if ((fi.mask as u32) != wantmask) { fail(); }; - if (!istype(fi.mode, os.mode.REG)) { fail(); }; - if (fi.sz != 11u64) { fail(); }; - if (fi.inode == 0u64) { fail(); }; + assert(!((fi.mask as u32) != wantmask)); + assert(!(!istype(fi.mode, os.mode.REG))); + assert(!(fi.sz != 11u64)); + assert(!(fi.inode == 0u64)); // Permission-bit smoke test — 976_stat_run.c open(2)s with // mode 0644 so USER_R survives any reasonable umask. Catches // a struct-field-offset miscompile on `fi.mode` that the // type-bit istype() check could miss if perm bits aliased a // neighbouring u32 (uid/gid). if (((fi.mode as u32) & (os.mode.USER_R as u32)) == 0u32) { - fail(); + abort(); }; // atime/mtime/ctime — kernel-set at create time, all // post-epoch (>0). Three distinct kstat offsets (72/88/104) // so a fillfilestat field-copy miscompile or a filestat // time.instant offset bug surfaces here, not silently. - if (fi.atime.sec <= 0i64) { fail(); }; - if (fi.mtime.sec <= 0i64) { fail(); }; - if (fi.ctime.sec <= 0i64) { fail(); }; + assert(!(fi.atime.sec <= 0i64)); + assert(!(fi.mtime.sec <= 0i64)); + assert(!(fi.ctime.sec <= 0i64)); }; }; }; @@ -90,9 +86,9 @@ fn istype(m: os.mode, t: os.mode) bool = { let p = envpath("WW_TEST_STAT_SUBDIR"); let fi: os.filestat; match (os.stat(&fi, p)) { - case let e: os.oserror => fail(); + case let e: os.oserror => abort(); case void => { - if (!istype(fi.mode, os.mode.DIR)) { fail(); }; + assert(!(!istype(fi.mode, os.mode.DIR))); }; }; }; @@ -103,10 +99,10 @@ fn istype(m: os.mode, t: os.mode) bool = { let p = envpath("WW_TEST_STAT_NOENT"); let fi: os.filestat; match (os.stat(&fi, p)) { - case void => fail(); + case void => abort(); case let e: os.oserror => { // ENOENT = 2 → raw errno is -2. - if ((e: i64) != -2i64) { fail(); }; + assert(!((e: i64) != -2i64)); }; }; }; @@ -120,10 +116,10 @@ fn istype(m: os.mode, t: os.mode) bool = { let p = envpath("WW_TEST_STAT_SYMLINK"); let fi: os.filestat; match (os.stat(&fi, p)) { - case let e: os.oserror => fail(); + case let e: os.oserror => abort(); case void => { - if (!istype(fi.mode, os.mode.REG)) { fail(); }; - if (fi.sz != 11u64) { fail(); }; + assert(!(!istype(fi.mode, os.mode.REG))); + assert(!(fi.sz != 11u64)); }; }; }; @@ -132,9 +128,9 @@ fn istype(m: os.mode, t: os.mode) bool = { let p = envpath("WW_TEST_STAT_SYMLINK"); let fi: os.filestat; match (os.lstat(&fi, p)) { - case let e: os.oserror => fail(); + case let e: os.oserror => abort(); case void => { - if (!istype(fi.mode, os.mode.LINK)) { fail(); }; + assert(!(!istype(fi.mode, os.mode.LINK))); }; }; }; @@ -144,13 +140,13 @@ fn istype(m: os.mode, t: os.mode) bool = { @test fn test_fstat_regfile() void = { let p = envpath("WW_TEST_STAT_REGFILE"); let fd: i32 = os.open(p, os.flag.RDONLY, 0i32); - if (fd < 0) { fail(); }; + assert(!(fd < 0)); let fi: os.filestat; match (os.fstat(&fi, fd)) { - case let e: os.oserror => { os.close(fd); fail(); }; + case let e: os.oserror => { os.close(fd); abort(); }; case void => { - if (!istype(fi.mode, os.mode.REG)) { os.close(fd); fail(); }; - if (fi.sz != 11u64) { os.close(fd); fail(); }; + if (!istype(fi.mode, os.mode.REG)) { os.close(fd); abort(); }; + if (fi.sz != 11u64) { os.close(fd); abort(); }; }; }; os.close(fd); @@ -160,17 +156,17 @@ fn istype(m: os.mode, t: os.mode) bool = { @test fn test_exists_regfile() void = { let p = envpath("WW_TEST_STAT_REGFILE"); - if (!os.exists(p)) { fail(); }; + assert(!(!os.exists(p))); }; @test fn test_exists_subdir() void = { let p = envpath("WW_TEST_STAT_SUBDIR"); - if (!os.exists(p)) { fail(); }; + assert(!(!os.exists(p))); }; @test fn test_exists_noent() void = { let p = envpath("WW_TEST_STAT_NOENT"); - if (os.exists(p)) { fail(); }; + assert(!(os.exists(p))); }; // ---- ENAMETOOLONG: kpath rejects paths >= PATH_MAX ------------------- @@ -197,29 +193,29 @@ fn makebig(n: i32) str = { let bp = makebig(os.PATH_MAX); let fi: os.filestat; match (os.stat(&fi, bp)) { - case void => fail(); + case void => abort(); case let e: os.oserror => { - if ((e: i64) != -36i64) { fail(); }; // ENAMETOOLONG + assert(!((e: i64) != -36i64)); // ENAMETOOLONG }; }; }; @test fn test_exists_toolong() void = { let bp = makebig(os.PATH_MAX); - if (os.exists(bp)) { fail(); }; + assert(!(os.exists(bp))); }; export fn main() i32 = { - signalled = 1; test_stat_regfile(); - signalled = 2; test_stat_subdir(); - signalled = 3; test_stat_noent(); - signalled = 4; test_stat_symlink_follow(); - signalled = 5; test_lstat_symlink_nofollow(); - signalled = 6; test_fstat_regfile(); - signalled = 7; test_exists_regfile(); - signalled = 8; test_exists_subdir(); - signalled = 9; test_exists_noent(); - signalled = 10; test_stat_toolong(); - signalled = 11; test_exists_toolong(); + test_stat_regfile(); + test_stat_subdir(); + test_stat_noent(); + test_stat_symlink_follow(); + test_lstat_symlink_nofollow(); + test_fstat_regfile(); + test_exists_regfile(); + test_exists_subdir(); + test_exists_noent(); + test_stat_toolong(); + test_exists_toolong(); return 0; };