From b4a6bab1129eb5bfb22b9123939072a0eb0f6d5c Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 14:35:46 +0900 Subject: [PATCH] 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. --- Makefile | 20 ++- test/libenv/libenv_test.ww | 237 ++++++++++++++++++++++++++++++++ test/wcc/974_getenv_run.c | 65 --------- test/wcc/975_dirs_run.c | 107 -------------- test/wcc/975_dirs_toolong_run.c | 157 --------------------- 5 files changed, 253 insertions(+), 333 deletions(-) create mode 100644 test/libenv/libenv_test.ww delete mode 100644 test/wcc/974_getenv_run.c delete mode 100644 test/wcc/975_dirs_run.c delete mode 100644 test/wcc/975_dirs_toolong_run.c diff --git a/Makefile b/Makefile index f67fa328..e7814e77 100644 --- a/Makefile +++ b/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/septest_test.ww test/sep/m3sep_test.ww 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 # test/xmod/ on the test/testenv helper package, porting the residual # 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 # 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) \ $(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) @set -e; for t in $(COMPILER_WRAPPER_BINS); do \ 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, # assemble, link, run), so both full per-stage toolchains are -# prerequisites. -$(SEP_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ +# prerequisites. The libenv arrangers share that shape: the dirs +# 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 \ $(BIN)/w6l_ww @echo "ww test $*" diff --git a/test/libenv/libenv_test.ww b/test/libenv/libenv_test.ww new file mode 100644 index 00000000..2e8b2bcd --- /dev/null +++ b/test/libenv/libenv_test.ww @@ -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); +}; diff --git a/test/wcc/974_getenv_run.c b/test/wcc/974_getenv_run.c deleted file mode 100644 index fb576708..00000000 --- a/test/wcc/974_getenv_run.c +++ /dev/null @@ -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 -#include -#include -#include - -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; -} diff --git a/test/wcc/975_dirs_run.c b/test/wcc/975_dirs_run.c deleted file mode 100644 index 7d4be32f..00000000 --- a/test/wcc/975_dirs_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -} diff --git a/test/wcc/975_dirs_toolong_run.c b/test/wcc/975_dirs_toolong_run.c deleted file mode 100644 index 69947fdc..00000000 --- a/test/wcc/975_dirs_toolong_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -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; -}