// dirstest — exercises lib/dirs against an arranged env cohort. The // XDG rows need env control ww cannot self-arrange (no setenv, // deliberately), so every row skips loudly when WW_TEST_TMPDIR is // absent and the suite passes bare; the arranged leg (env cohort // below constructed per row over a scratch HOME, rows asserted `ok`, // not SKIP) is test/libenv/libenv_test.ww. // // Arranged env cohort: // // HOME = (fresh scratch dir) // XDG_CONFIG_HOME = /cfg (absolute → XDG branch) // XDG_CACHE_HOME = "relative/path" (non-abs → fallback to HOME) // XDG_DATA_HOME unset (fallback to HOME) // XDG_STATE_HOME unset (fallback to HOME) // WW_TEST_TMPDIR = (so this fixture can compute // the expected paths) // // Cohorts (one @test fn per row, in numeric order from main): // // 1. config("myapp") — XDG-set+absolute → /cfg/myapp // 2. cache("myapp") — XDG-set+relative → /.cache/myapp (abs-check fallback) // 3. data("myapp") — XDG-unset → /.local/share/myapp (HOME fallback) // 4. state("myapp") — XDG-unset → /.local/state/myapp (HOME fallback) // // Static-buffer gotcha: dirs.config / dirs.cache / dirs.data / // dirs.state all return views into the same module-level pathbuf. // Each @test calls EXACTLY ONE dirs.* function, builds its expected // string into a separate `scratch: [256]u8` BEFORE that call, and // asserts immediately. Don't call two dirs.* fns and compare across // — the second clobbers the first's view. // // 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. package dirs_test; import dirs; import os; import test; 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; }; // scratch — local buffer for expected-path construction. Independent // of dirs.pathbuf; both views stay valid through the streq compare // because they live in different buffers. let scratch: [256]u8; fn putscratch(off: i32, s: str) i32 = { let i: i32 = 0; for (i < s.len) { if (off + i >= 255) { break; }; scratch[off + i] = s[i]; i += 1; }; return off + i; }; // expected — build "//" into scratch and return a // view. Mirrors dirs.build but writes into our scratch buffer so // the expected and got views can be compared without aliasing. fn expected(tmpl: str, sub: str, prog: str) str = { let off: i32 = 0; off = putscratch(off, tmpl); if (off < 255) { scratch[off] = 47u8; off += 1; }; // '/' off = putscratch(off, sub); if (off < 255) { scratch[off] = 47u8; off += 1; }; off = putscratch(off, prog); let r: str; r.ptr = &scratch[0]; r.len = off; return r; }; fn gettmpdir() str = { match (os.getenv("WW_TEST_TMPDIR")) { case let s: str => return s; case void => test.skip("WW_TEST_TMPDIR unset (arranged leg: test/libenv)"); }; }; @test fn test_config_xdg_set() void = { let tmpl = gettmpdir(); let exp = expected(tmpl, "cfg", "myapp"); let got = dirs.config("myapp"); assert(!(!streq(got, exp))); }; // Exercises Hare's `if (!path::abs(path)) yield;` branch — a // non-absolute XDG_*_HOME silently falls through to the HOME path. @test fn test_cache_xdg_relative_fallback() void = { let tmpl = gettmpdir(); let exp = expected(tmpl, ".cache", "myapp"); let got = dirs.cache("myapp"); assert(!(!streq(got, exp))); }; @test fn test_data_unset_fallback() void = { let tmpl = gettmpdir(); let exp = expected(tmpl, ".local/share", "myapp"); let got = dirs.data("myapp"); assert(!(!streq(got, exp))); }; @test fn test_state_unset_fallback() void = { let tmpl = gettmpdir(); let exp = expected(tmpl, ".local/state", "myapp"); let got = dirs.state("myapp"); assert(!(!streq(got, exp))); };