package libenv_test; // Lib env/OS arranger observers, porting the retired C arrangers // test/wcc/974_getenv_run.c, 975_dirs_run.c, and // 975_dirs_toolong_run.c; every assertion preserved. ww ships no // setenv primitive (deliberately), so the env contracts of // lib/os/os_test.ww and lib/dirs/dirs_test.ww are arranged here by // constructing each row's environment explicitly (exec.command.env // is caller-built) and driving `ww test` on the suite: // // getenv_arranged — WW_TEST_GETENV/WW_TEST_EMPTY set (empty value // included), WW_TEST_NOT_SET cleared; the getenv rows must run // (`ok`, not SKIP). The bare leg strips the vars and asserts // the rows SKIP loudly instead of failing. // dirs_arranged — the four XDG cohorts over a scratch HOME; // rows must run; the exact auto-created directory chains are // asserted present (stronger than the carrier's rmdir-chain // encoding). Bare leg as above. // dirs_toolong — F14 #69: an over-long composed path must abort // loudly and create no stray directory; the short-HOME control // still exits 0. Runs on both driver stages (stdlib behavior // parity; the Make target declares both toolchains). // stat_selfarranged — lib/os/stat_test.ww self-arranges its // scratch tree (976_stat_run.c's env contract is gone); this // row is the suite's make-owned runner and pins that no row // skips or needs env. import os; import os.exec; import strings; import testenv; import time; fn tmo() time.duration = { return (180i64 * (time.second: i64)): time.duration; }; fn envname(entry: str) str = { let i: i32 = 0; for (i < entry.len) { if (entry[i] == '=') { break; }; i += 1; }; let n: str; n.ptr = entry.ptr; n.len = i; return n; }; fn dropsname(name: str, drop: []str, add: []str) bool = { let i: i32 = 0; for (i < drop.len) { if (testenv.same(name, drop[i])) { return true; }; i += 1; }; i = 0; for (i < add.len) { if (testenv.same(name, envname(add[i]))) { return true; }; i += 1; }; return false; }; // The row's environment: the inherited env minus `drop` minus any // name overridden in `add`, plus the `add` entries ("NAME=VALUE"). fn mkenv(drop: []str, add: []str) []str = { let base: []str = os.getenvs(); let out: []str = alloc([], (base.len + add.len + 1): u64)!; let i: i32 = 0; for (i < base.len) { if (!dropsname(envname(base[i]), drop, add)) { append(out, base[i]); }; i += 1; }; i = 0; for (i < add.len) { append(out, add[i]); i += 1; }; return out; }; fn mkworkdir(w: str) void = { let r: i32 = os.mkdir(w, 448i32); assert(r == 0 || r == -17); // EEXIST: shared across legs }; fn wwtest(td: str, name: str, w: str, file: str, pattern: str, env: []str, out: *testenv.commandout) void = { mkworkdir(w); let av: []str = alloc([], 8u64)!; append(av, testenv.driver("ww")); append(av, "test"); append(av, "-w"); append(av, w); append(av, file); if (pattern.len != 0) { append(av, pattern); }; testenv.runcommandenv(td, td, name, av, env, tmo(), out); }; fn expectok(co: *testenv.commandout, label: str) void = { if (co.termination != exec.termination.EXIT || co.code != 0) { let m: str = strings.concat("libenv FAIL: ", label, " did not exit 0\n"); os.write(2, m.ptr, m.len: u64); assert(false); }; }; @test fn getenv_arranged() void = { let td: str = testenv.fresh(); let w: str = strings.concat(td, "/osw"); let file: str = strings.concat(testenv.repo(), "/lib/os/os_test.ww"); let dropv: []str = ["WW_TEST_NOT_SET"]; let addv: []str = ["WW_TEST_GETENV=hello-world", "WW_TEST_EMPTY="]; let env: []str = mkenv(dropv, addv); let co: testenv.commandout; wwtest(td, "getenv_env", w, file, "", env, &co); expectok(&co, "os_test arranged"); assert(testenv.has(co.stdout, "test_getenv_set ... ok")); assert(testenv.has(co.stdout, "test_getenv_empty ... ok")); assert(testenv.has(co.stdout, "test_getenv_unset ... ok")); assert(testenv.has(co.stdout, "test_getenv_prefix_no_match ... ok")); assert(testenv.has(co.stdout, "test_getenvs_entries ... ok")); // bare leg: with the vars absent the rows SKIP loudly, never fail let barens: []str = ["WW_TEST_GETENV", "WW_TEST_EMPTY", "WW_TEST_NOT_SET"]; let none: []str; let bare: []str = mkenv(barens, none); wwtest(td, "getenv_bare", w, file, "test_getenv_*", bare, &co); expectok(&co, "os_test bare getenv rows"); assert(testenv.has(co.stdout, "test_getenv_set ... SKIP:")); assert(testenv.has(co.stdout, "test_getenv_empty ... SKIP:")); assert(testenv.has(co.stdout, "test_getenv_unset ... ok")); testenv.clean(td); }; @test fn dirs_arranged() void = { let td: str = testenv.fresh(); let w: str = strings.concat(td, "/dw"); let file: str = strings.concat(testenv.repo(), "/lib/dirs/dirs_test.ww"); let dropv: []str = ["XDG_DATA_HOME", "XDG_STATE_HOME"]; let addv: []str = [strings.concat("HOME=", td), strings.concat("XDG_CONFIG_HOME=", td, "/cfg"), "XDG_CACHE_HOME=relative/path", strings.concat("WW_TEST_TMPDIR=", td)]; let env: []str = mkenv(dropv, addv); let co: testenv.commandout; wwtest(td, "dirs_env", w, file, "", env, &co); expectok(&co, "dirs_test arranged"); assert(testenv.has(co.stdout, "test_config_xdg_set ... ok")); assert(testenv.has(co.stdout, "test_cache_xdg_relative_fallback ... ok")); assert(testenv.has(co.stdout, "test_data_unset_fallback ... ok")); assert(testenv.has(co.stdout, "test_state_unset_fallback ... ok")); // dirs.* auto-creates exactly these chains under the scratch HOME assert(testenv.isdir(strings.concat(td, "/cfg/myapp"))); assert(testenv.isdir(strings.concat(td, "/.cache/myapp"))); assert(testenv.isdir(strings.concat(td, "/.local/share/myapp"))); assert(testenv.isdir(strings.concat(td, "/.local/state/myapp"))); // bare leg: without WW_TEST_TMPDIR every row SKIPs loudly let barens: []str = ["WW_TEST_TMPDIR", "XDG_CONFIG_HOME", "XDG_CACHE_HOME", "XDG_DATA_HOME", "XDG_STATE_HOME"]; let none: []str; let bare: []str = mkenv(barens, none); wwtest(td, "dirs_bare", w, file, "", bare, &co); expectok(&co, "dirs_test bare"); assert(testenv.has(co.stdout, "test_config_xdg_set ... SKIP:")); assert(testenv.has(co.stdout, "test_state_unset_fallback ... SKIP:")); testenv.clean(td); }; fn toolongprog() str = { return strings.concat( "package main;\n", "import dirs;\n", "import fmt;\n", "export fn main() int = {\n", " let d = dirs.config(\"prog\");\n", " fmt.println(d.len: i64);\n", " return 0;\n", "};\n"); }; // ~260-byte HOME: overflows lib/dirs' 256B pathbuf composition. fn longhome(td: str, drv: str) str = { let b: []u8 = alloc([], 261u64)!; let i: i32 = 0; for (i < 260) { append(b, 97u8); i += 1; }; // 'a' return strings.concat(td, "/long_", drv, "_", strings.frombytes(b)); }; fn toolongstage(td: str, src: str, drv: str) void = { let bin: str = strings.concat(td, "/dtl_bin_", drv); let av: []str = [testenv.driver(drv), "build", "-o", bin, src]; let co: testenv.commandout; testenv.runcommand(td, td, strings.concat("dtl_build_", drv), av, tmo(), &co); expectok(&co, strings.concat("dtl build ", drv)); let dropv: []str = ["HOME", "XDG_CONFIG_HOME"]; // short HOME — the normal path, must still exit 0 let home: str = strings.concat(td, "/home.", drv); let addshort: []str = [strings.concat("HOME=", home)]; let runav: []str = [bin]; testenv.runcommandenv(td, td, strings.concat("dtl_short_", drv), runav, mkenv(dropv, addshort), tmo(), &co); expectok(&co, strings.concat("dtl short HOME ", drv)); assert(testenv.isdir(strings.concat(home, "/.config/prog"))); // long HOME — must abort loudly AND create no stray directory let lh: str = longhome(td, drv); let addlong: []str = [strings.concat("HOME=", lh)]; testenv.runcommandenv(td, td, strings.concat("dtl_long_", drv), runav, mkenv(dropv, addlong), tmo(), &co); assert(!(co.termination == exec.termination.EXIT && co.code == 0)); assert(!testenv.exists(lh)); }; @test fn dirs_toolong() void = { let td: str = testenv.fresh(); let src: str = strings.concat(td, "/dtl.ww"); testenv.writefile(src, toolongprog()); toolongstage(td, src, "ww"); toolongstage(td, src, "ww_ww"); testenv.clean(td); }; @test fn stat_selfarranged() void = { let td: str = testenv.fresh(); let w: str = strings.concat(td, "/sw"); let file: str = strings.concat(testenv.repo(), "/lib/os/stat_test.ww"); let none: []str; let env: []str = mkenv(none, none); let co: testenv.commandout; wwtest(td, "stat", w, file, "", env, &co); expectok(&co, "stat_test self-arranged"); assert(testenv.has(co.stdout, "test_stat_regfile ... ok")); assert(testenv.has(co.stdout, "test_lstat_symlink_nofollow ... ok")); assert(!testenv.has(co.stdout, "SKIP")); testenv.clean(td); };