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.
238 lines
8.5 KiB
Plaintext
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);
|
|
};
|