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.
This commit is contained in:
20
Makefile
20
Makefile
@@ -366,6 +366,14 @@ SEP_WW_TESTS = test/sep/sepbuild_test.ww test/sep/sepimport_test.ww \
|
|||||||
test/sep/seplink_test.ww test/sep/sepscratch_test.ww \
|
test/sep/seplink_test.ww test/sep/sepscratch_test.ww \
|
||||||
test/sep/septest_test.ww test/sep/m3sep_test.ww
|
test/sep/septest_test.ww test/sep/m3sep_test.ww
|
||||||
SEP_WW_TARGETS = $(SEP_WW_TESTS:%=wwtest/%)
|
SEP_WW_TARGETS = $(SEP_WW_TESTS:%=wwtest/%)
|
||||||
|
# Ww-native lib env/OS arranger observers: the env contracts of the
|
||||||
|
# lib/os and lib/dirs suites (getenv cohorts, XDG cohorts, the dirs
|
||||||
|
# too-long abort) need per-row env construction ww cannot self-arrange
|
||||||
|
# (no setenv, deliberately). These replaced the 974/975-era C
|
||||||
|
# arrangers; the suites themselves pass bare (skip or self-arrange).
|
||||||
|
LIBENV_WW_TESTS = test/libenv/libenv_test.ww
|
||||||
|
LIBENV_WW_TARGETS = $(LIBENV_WW_TESTS:%=wwtest/%)
|
||||||
|
|
||||||
# Ww-native multi-module-TREE observers: single-file ww tests under
|
# Ww-native multi-module-TREE observers: single-file ww tests under
|
||||||
# test/xmod/ on the test/testenv helper package, porting the residual
|
# test/xmod/ on the test/testenv helper package, porting the residual
|
||||||
# multi-module C carriers (import-tree collisions, path-mangle and
|
# multi-module C carriers (import-tree collisions, path-mangle and
|
||||||
@@ -507,10 +515,12 @@ test-compiler-smoke: $(WWFIXTURE_BIN) $(BIN)/w6c_ww $(BIN)/w6a_ww \
|
|||||||
|
|
||||||
# Complete declarative corpus plus the residual native artifact/integration
|
# Complete declarative corpus plus the residual native artifact/integration
|
||||||
# gates that cannot be represented by a single compiler fixture, plus the
|
# gates that cannot be represented by a single compiler fixture, plus the
|
||||||
# ww-native sep observers that replaced the 989_sep* carriers.
|
# ww-native sep observers that replaced the 989_sep* carriers and the
|
||||||
|
# lib env arranger observers that replaced the 974/975-era carriers.
|
||||||
test-compiler: $(WWFIXTURE_BIN) $(WRAPPER_TOOLS) $(COMPILER_WRAPPER_BINS) \
|
test-compiler: $(WWFIXTURE_BIN) $(WRAPPER_TOOLS) $(COMPILER_WRAPPER_BINS) \
|
||||||
$(SEP_WW_TARGETS) $(XMOD_WW_TARGETS) $(ASM_WW_TARGETS) \
|
$(SEP_WW_TARGETS) $(XMOD_WW_TARGETS) $(ASM_WW_TARGETS) \
|
||||||
$(OBJECT_WW_TARGETS) $(MISC_WW_TARGETS) $(TOOL_WW_TARGETS)
|
$(OBJECT_WW_TARGETS) $(MISC_WW_TARGETS) $(TOOL_WW_TARGETS) \
|
||||||
|
$(LIBENV_WW_TARGETS)
|
||||||
@$(CURDIR)/$(WWFIXTURE_BIN) -j $(JOBS)
|
@$(CURDIR)/$(WWFIXTURE_BIN) -j $(JOBS)
|
||||||
@set -e; for t in $(COMPILER_WRAPPER_BINS); do \
|
@set -e; for t in $(COMPILER_WRAPPER_BINS); do \
|
||||||
echo "compiler artifact $$t"; BIN=$(CURDIR)/$(BIN) $(CURDIR)/$$t; \
|
echo "compiler artifact $$t"; BIN=$(CURDIR)/$(BIN) $(CURDIR)/$$t; \
|
||||||
@@ -548,8 +558,10 @@ $(BYTEID_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a \
|
|||||||
|
|
||||||
# The sep observers drive both driver stages end-to-end (compile,
|
# The sep observers drive both driver stages end-to-end (compile,
|
||||||
# assemble, link, run), so both full per-stage toolchains are
|
# assemble, link, run), so both full per-stage toolchains are
|
||||||
# prerequisites.
|
# prerequisites. The libenv arrangers share that shape: the dirs
|
||||||
$(SEP_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
# too-long row builds and runs on both stages.
|
||||||
|
$(SEP_WW_TARGETS) $(LIBENV_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c \
|
||||||
|
$(BIN)/w6a $(BIN)/w6l \
|
||||||
$(LIB)/libwwrt.a $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww \
|
$(LIB)/libwwrt.a $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww \
|
||||||
$(BIN)/w6l_ww
|
$(BIN)/w6l_ww
|
||||||
@echo "ww test $*"
|
@echo "ww test $*"
|
||||||
|
|||||||
237
test/libenv/libenv_test.ww
Normal file
237
test/libenv/libenv_test.ww
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
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);
|
||||||
|
};
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* 974_getenv_run — execute the lib/os getenv smoke fixture under the
|
|
||||||
* C-side `ww run` driver and assert exit 0.
|
|
||||||
*
|
|
||||||
* Pre-arranges the environment that lib/os/os_test.ww asserts against:
|
|
||||||
*
|
|
||||||
* WW_TEST_GETENV = "hello-world" (set, non-empty)
|
|
||||||
* WW_TEST_EMPTY = "" (set, empty value)
|
|
||||||
* WW_TEST_NOT_SET unset (unsetenv-cleared)
|
|
||||||
*
|
|
||||||
* The child `ww run` process inherits this env, so os_test.ww's
|
|
||||||
* `os.getenv` calls see exactly the state we configured here. This
|
|
||||||
* is the "C-side env arrangement" pattern (parent sets, child reads)
|
|
||||||
* — proper POSIX shape for stdlib testing without a `setenv` ww
|
|
||||||
* primitive (deferred per drew/rob).
|
|
||||||
*
|
|
||||||
* Same wrapper shape as 970_fmt_run / 971_log_run / 972_fnmatch_run
|
|
||||||
* / 973_shlex_run.
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
static int
|
|
||||||
runwait(const char *cmd)
|
|
||||||
{
|
|
||||||
int rc = system(cmd);
|
|
||||||
if (rc == -1) return -1;
|
|
||||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(void)
|
|
||||||
{
|
|
||||||
const char *bin = getenv("BIN");
|
|
||||||
if (!bin) bin = "out/bin";
|
|
||||||
char absbin[1024];
|
|
||||||
if (bin[0] != '/') {
|
|
||||||
char cwd[1024];
|
|
||||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
|
||||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
|
||||||
bin = absbin;
|
|
||||||
}
|
|
||||||
char cwd[1024];
|
|
||||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
|
||||||
|
|
||||||
/* Pre-arrange the env state os_test.ww asserts against. */
|
|
||||||
setenv("WW_TEST_GETENV", "hello-world", 1);
|
|
||||||
setenv("WW_TEST_EMPTY", "", 1);
|
|
||||||
unsetenv("WW_TEST_NOT_SET");
|
|
||||||
|
|
||||||
const char *src = "lib/os/os_test.ww";
|
|
||||||
char path[1024], cmd[2048];
|
|
||||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
|
||||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
|
||||||
int rc = runwait(cmd);
|
|
||||||
if (rc != 0) {
|
|
||||||
fprintf(stderr, "getenv_run FAIL: %s exited %d\n", src, rc);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("getenv_run: %s ok\n", src);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
/*
|
|
||||||
* 975_dirs_run — execute the lib/dirs @test fixture under the C-side
|
|
||||||
* `ww test` driver and assert exit 0.
|
|
||||||
*
|
|
||||||
* Workflow:
|
|
||||||
* 1. mkdtemp /tmp/wwdirs-XXXXXX — fresh per run, no cross-run
|
|
||||||
* stale state, no parallel-test conflicts.
|
|
||||||
* 2. setenv the four cohort env states (see lib/dirs/dirs_test.ww
|
|
||||||
* file header for the contract).
|
|
||||||
* 3. exec `ww test lib/dirs/dirs_test.ww`. The fixture reads
|
|
||||||
* WW_TEST_TMPDIR + the XDG_/HOME envs and asserts.
|
|
||||||
* 4. Remove the exact auto-created child dirs bottom-up, then the
|
|
||||||
* mkdtemp-owned root.
|
|
||||||
*
|
|
||||||
* Same wrapper shape as 970-974; cleanup runs on success and failure.
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
static int
|
|
||||||
runwait(const char *cmd)
|
|
||||||
{
|
|
||||||
int rc = system(cmd);
|
|
||||||
if (rc == -1) return -1;
|
|
||||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(void)
|
|
||||||
{
|
|
||||||
const char *bin = getenv("BIN");
|
|
||||||
if (!bin) bin = "out/bin";
|
|
||||||
char absbin[1024];
|
|
||||||
if (bin[0] != '/') {
|
|
||||||
char cwd[1024];
|
|
||||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
|
||||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
|
||||||
bin = absbin;
|
|
||||||
}
|
|
||||||
char cwd[1024];
|
|
||||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
|
||||||
|
|
||||||
/* mkdtemp the per-run scratch root. */
|
|
||||||
char tmpl[64];
|
|
||||||
strcpy(tmpl, "/tmp/wwdirs-XXXXXX");
|
|
||||||
if (mkdtemp(tmpl) == NULL) {
|
|
||||||
perror("mkdtemp");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pre-arrange env. The child `ww test` inherits these. */
|
|
||||||
char cfg[128];
|
|
||||||
snprintf(cfg, sizeof cfg, "%s/cfg", tmpl);
|
|
||||||
setenv("HOME", tmpl, 1);
|
|
||||||
setenv("XDG_CONFIG_HOME", cfg, 1);
|
|
||||||
setenv("XDG_CACHE_HOME", "relative/path", 1); /* non-abs → fallback */
|
|
||||||
unsetenv("XDG_DATA_HOME");
|
|
||||||
unsetenv("XDG_STATE_HOME");
|
|
||||||
setenv("WW_TEST_TMPDIR", tmpl, 1);
|
|
||||||
|
|
||||||
const char *src = "lib/dirs/dirs_test.ww";
|
|
||||||
char path[1024], cmd[2048];
|
|
||||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
|
||||||
snprintf(cmd, sizeof cmd, "%s/ww test %s", bin, path);
|
|
||||||
int rc = runwait(cmd);
|
|
||||||
|
|
||||||
int fail = 0;
|
|
||||||
if (rc != 0) {
|
|
||||||
fprintf(stderr, "dirs_run FAIL: %s exited %d\n", src, rc);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* dirs.* recursively creates these exact directory chains. */
|
|
||||||
char p[256];
|
|
||||||
int cleanbad = 0;
|
|
||||||
snprintf(p, sizeof p, "%s/cfg/myapp", tmpl);
|
|
||||||
if (rmdir(p) != 0 && errno != ENOENT) cleanbad = 1;
|
|
||||||
snprintf(p, sizeof p, "%s/cfg", tmpl);
|
|
||||||
if (rmdir(p) != 0 && errno != ENOENT) cleanbad = 1;
|
|
||||||
snprintf(p, sizeof p, "%s/.cache/myapp", tmpl);
|
|
||||||
if (rmdir(p) != 0 && errno != ENOENT) cleanbad = 1;
|
|
||||||
snprintf(p, sizeof p, "%s/.cache", tmpl);
|
|
||||||
if (rmdir(p) != 0 && errno != ENOENT) cleanbad = 1;
|
|
||||||
snprintf(p, sizeof p, "%s/.local/share/myapp", tmpl);
|
|
||||||
if (rmdir(p) != 0 && errno != ENOENT) cleanbad = 1;
|
|
||||||
snprintf(p, sizeof p, "%s/.local/share", tmpl);
|
|
||||||
if (rmdir(p) != 0 && errno != ENOENT) cleanbad = 1;
|
|
||||||
snprintf(p, sizeof p, "%s/.local/state/myapp", tmpl);
|
|
||||||
if (rmdir(p) != 0 && errno != ENOENT) cleanbad = 1;
|
|
||||||
snprintf(p, sizeof p, "%s/.local/state", tmpl);
|
|
||||||
if (rmdir(p) != 0 && errno != ENOENT) cleanbad = 1;
|
|
||||||
snprintf(p, sizeof p, "%s/.local", tmpl);
|
|
||||||
if (rmdir(p) != 0 && errno != ENOENT) cleanbad = 1;
|
|
||||||
if (rmdir(tmpl) != 0) cleanbad = 1;
|
|
||||||
if (cleanbad) {
|
|
||||||
fprintf(stderr, "dirs_run FAIL: temporary cleanup failed\n");
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
if (fail) return 1;
|
|
||||||
printf("dirs_run: %s ok\n", src);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
/*
|
|
||||||
* 975_dirs_toolong_run — F14 #69: dirs silently truncated an over-long
|
|
||||||
* composed path and then mkdir'd the WRONG directory, returning it rc=0.
|
|
||||||
* The fix (dirs.ww build()) precomputes the composition length and
|
|
||||||
* rt_aborts loudly when it won't fit the 256B pathbuf — mirroring Hare's
|
|
||||||
* path::push too_long → `!` abort (ref/hare/dirs/xdg.ha). Both stages
|
|
||||||
* share the lib, so this is a stdlib behavior fix, not a stage divergence;
|
|
||||||
* the test still runs both driver twins for parity.
|
|
||||||
*
|
|
||||||
* One fixture program (dirs.config("prog"), print len) built per stage,
|
|
||||||
* then run under two HOME states:
|
|
||||||
* - a short HOME → exit 0 (normal path intact, no regression);
|
|
||||||
* - a ~260-byte HOME → non-zero exit (loud abort) AND no stray
|
|
||||||
* directory created on disk.
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
static int
|
|
||||||
runwait(const char *cmd)
|
|
||||||
{
|
|
||||||
int rc = system(cmd);
|
|
||||||
if (rc == -1) return -1;
|
|
||||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *PROG =
|
|
||||||
"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";
|
|
||||||
|
|
||||||
/* run_stage — build PROG with `driver`, then exercise the two HOME states.
|
|
||||||
* Returns 0 on success, non-zero on any mismatch. */
|
|
||||||
static int
|
|
||||||
run_stage(const char *name, const char *driver)
|
|
||||||
{
|
|
||||||
char tmpdir[64] = "/tmp/dtl_XXXXXX";
|
|
||||||
if (mkdtemp(tmpdir) == NULL) {
|
|
||||||
perror("dirs_toolong: mkdtemp");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
char src[128], outbin[128], sepwork[160], rmcmd[192], cmd[2048];
|
|
||||||
char shorthome[128], shortcfg[160], shortprog[192];
|
|
||||||
snprintf(src, sizeof src, "%s/dtl_%d.ww", tmpdir, getpid());
|
|
||||||
snprintf(outbin, sizeof outbin, "%s/dtl_%d", tmpdir, getpid());
|
|
||||||
snprintf(sepwork, sizeof sepwork, "%s.sepwork", outbin);
|
|
||||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", sepwork);
|
|
||||||
snprintf(shorthome, sizeof shorthome, "%s/home", tmpdir);
|
|
||||||
snprintf(shortcfg, sizeof shortcfg, "%s/.config", shorthome);
|
|
||||||
snprintf(shortprog, sizeof shortprog, "%s/prog", shortcfg);
|
|
||||||
int fail = 0;
|
|
||||||
|
|
||||||
FILE *f = fopen(src, "wb");
|
|
||||||
if (!f) { fail = 1; goto cleanup; }
|
|
||||||
fputs(PROG, f);
|
|
||||||
fclose(f);
|
|
||||||
|
|
||||||
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
|
|
||||||
driver, outbin, src);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "dirs_toolong[%s]: build failed\n", name);
|
|
||||||
fail = 1;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* short HOME — the normal path, must still exit 0. */
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"env -u XDG_CONFIG_HOME HOME=%s %s >/dev/null 2>&1",
|
|
||||||
shorthome, outbin);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "dirs_toolong[%s]: short HOME did not exit 0\n",
|
|
||||||
name);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ~260-byte HOME — must abort loudly (non-zero) and create no dir. */
|
|
||||||
char longhome[512];
|
|
||||||
int p = snprintf(longhome, sizeof longhome, "%s/long_", tmpdir);
|
|
||||||
for (int i = 0; i < 260 && p < (int)sizeof longhome - 1; i++)
|
|
||||||
longhome[p++] = 'a';
|
|
||||||
longhome[p] = '\0';
|
|
||||||
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"env -u XDG_CONFIG_HOME HOME=%s %s >/dev/null 2>&1",
|
|
||||||
longhome, outbin);
|
|
||||||
if (runwait(cmd) == 0) {
|
|
||||||
fprintf(stderr, "dirs_toolong[%s]: long HOME exited 0, "
|
|
||||||
"expected a loud abort\n", name);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
/* the truncated wrong directory must NOT have been created. */
|
|
||||||
snprintf(cmd, sizeof cmd, "test -e %s", longhome);
|
|
||||||
if (runwait(cmd) == 0) {
|
|
||||||
fprintf(stderr, "dirs_toolong[%s]: stray directory created\n",
|
|
||||||
name);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup: {
|
|
||||||
int cleanbad = 0;
|
|
||||||
if (unlink(src) != 0 && errno != ENOENT) cleanbad = 1;
|
|
||||||
if (unlink(outbin) != 0 && errno != ENOENT) cleanbad = 1;
|
|
||||||
if (runwait(rmcmd) != 0) cleanbad = 1;
|
|
||||||
if (rmdir(shortprog) != 0 && errno != ENOENT) cleanbad = 1;
|
|
||||||
if (rmdir(shortcfg) != 0 && errno != ENOENT) cleanbad = 1;
|
|
||||||
if (rmdir(shorthome) != 0 && errno != ENOENT) cleanbad = 1;
|
|
||||||
if (rmdir(tmpdir) != 0) cleanbad = 1;
|
|
||||||
if (cleanbad) {
|
|
||||||
fprintf(stderr, "dirs_toolong[%s]: temporary cleanup failed\n",
|
|
||||||
name);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(void)
|
|
||||||
{
|
|
||||||
const char *bin = getenv("BIN");
|
|
||||||
if (!bin) bin = "out/bin";
|
|
||||||
char absbin[1024];
|
|
||||||
if (bin[0] != '/') {
|
|
||||||
char cwd[1024];
|
|
||||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
|
||||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
|
||||||
bin = absbin;
|
|
||||||
}
|
|
||||||
|
|
||||||
char cdrv[1024], wdrv[1024];
|
|
||||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
|
||||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
|
||||||
|
|
||||||
int fail = 0;
|
|
||||||
fail += run_stage("cstage", cdrv);
|
|
||||||
if (access(wdrv, X_OK) == 0)
|
|
||||||
fail += run_stage("wwstage", wdrv);
|
|
||||||
else
|
|
||||||
fprintf(stderr, "dirs_toolong: skip wwstage (no %s)\n", wdrv);
|
|
||||||
|
|
||||||
if (fail) {
|
|
||||||
fprintf(stderr, "dirs_toolong: %d stage(s) failed\n", fail);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("dirs_toolong: ok\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user