Files
ww/test/libenv/libenv_test.ww
Hojun-Cho b4a6bab112 test: libenv arranger observers; retire the 974/975 env carriers
test/libenv/libenv_test.ww owns the env-arranged legs of the lib/os
getenv and lib/dirs XDG suites (rows asserted ok, not SKIP, plus the
created-chain layout), the dirs too-long abort on both stages, and the
make-owned runner for the self-arranged stat suite. Wired beside the
sep observers under test-compiler.
2026-08-08 15:40:19 +09:00

238 lines
8.5 KiB
Plaintext

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);
};