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/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 $*"
|
||||
|
||||
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