test(cli): neutralize ambient client home overrides in the C runner

The CLI resolves each client's config home from its own environment
variable before falling back to $HOME — cbm_codex_config_dir() honours
$CODEX_HOME and ignores the home_dir it is handed. A test that redirects
only HOME therefore does not isolate those clients: it resolves, reads
and WRITES the developer's real configuration.

On a machine with $CODEX_HOME exported (any Codex or Orca user) 27 of
269 cli tests failed for that reason alone. Every one of them asserted a
successful rc while the CLI was reporting genuine failures against the
ambient config: an unbalanced managed marker left in the real
config.toml, an AGENTS.md that is a symlink and is refused by the
O_NOFOLLOW writer, and user-modified agent profiles that are correctly
preserved. The behaviour under test passed in all of them; only the rc
was poisoned. Worse than the red: the suite mutated that real config,
installing and removing SKILL.md under the user's Codex home.

The individual tests that already neutralize CODEX_HOME are exactly the
ones that pass, and the file carries a comment admitting one suite was
"green only via env leaked from earlier suites". Rather than extend those
ad-hoc per-test lists again, neutralize the whole inventory once in the
runner, and reuse the inventory the shell fixtures are already required
to satisfy.

tests/test_smoke_fixture_contract.sh grows one check so the two cannot
drift: a client isolated in the smoke scripts but forgotten in the C
runner now fails the contract.

Verified on macOS arm64 with $CODEX_HOME and $OPENCODE_CONFIG_DIR
exported: cli went from 241 passed / 28 failed to 268 passed / 0 failed,
with no change outside tests/.

Signed-off-by: Gabriel Gonzalez Pineda <64bits@gmail.com>
This commit is contained in:
Gabriel Gonzalez Pineda
2026-08-13 16:36:27 -04:00
parent bb2b990338
commit 6d09c19e11
2 changed files with 77 additions and 29 deletions
+38 -4
View File
@@ -54,8 +54,7 @@ static bool tf_invoked_as_windows_git_module(void) {
* Inspect the actual loaded module so the copied git.exe probe cannot fall
* through into the ordinary test runner when argv[0] is merely "git". */
wchar_t image[32768];
DWORD image_length =
GetModuleFileNameW(NULL, image, (DWORD)(sizeof(image) / sizeof(image[0])));
DWORD image_length = GetModuleFileNameW(NULL, image, (DWORD)(sizeof(image) / sizeof(image[0])));
if (image_length == 0 || image_length >= (DWORD)(sizeof(image) / sizeof(image[0]))) {
return false;
}
@@ -86,8 +85,7 @@ static bool tf_invoked_as_blocking_git(const char *argv0) {
base = cursor + 1;
}
}
return strcmp(base, "git") == 0 || strcmp(base, "git.exe") == 0 ||
strcmp(base, "GIT.EXE") == 0;
return strcmp(base, "git") == 0 || strcmp(base, "git.exe") == 0 || strcmp(base, "GIT.EXE") == 0;
#endif
}
@@ -165,6 +163,38 @@ static void tf_cleanup_cache_sentinel(void) {
}
}
/* Client home overrides the CLI honours BEFORE $HOME, so redirecting HOME alone
* does not isolate them: cbm_codex_config_dir() and its siblings return the
* ambient path and the suite resolves against the developer's real config —
* reading its state and writing to it. Same inventory the shell fixtures are
* already required to neutralize (tests/test_smoke_fixture_contract.sh), kept
* in one place so a new client cannot be isolated in the smoke scripts and
* forgotten here. A test that exercises one of these sets it after setup. */
static const char *const tf_client_home_overrides[] = {
"CLAUDE_CONFIG_DIR",
"CODEX_HOME",
"KIRO_HOME",
"HERMES_HOME",
"QWEN_HOME",
"CLINE_DATA_DIR",
"OPENCLAW_HOME",
"OPENCLAW_STATE_DIR",
"OPENCLAW_PROFILE",
"OPENCLAW_CONFIG_PATH",
"OPENCLAW_WORKSPACE_DIR",
"OPENCODE_CONFIG",
"OPENCODE_CONFIG_DIR",
"COPILOT_HOME",
"CRUSH_GLOBAL_CONFIG",
"VIBE_HOME",
"GLAB_CONFIG_DIR",
"KIMI_CODE_HOME",
"CBM_CONTINUE_CONFIG_PATH",
"CBM_TRAE_CONFIG_PATH",
"CBM_ROO_CONFIG_PATH",
"CBM_CODY_CONFIG_PATH",
};
static bool tf_setup_cache_sentinel(void) {
snprintf(tf_home_sentinel, sizeof(tf_home_sentinel), "/tmp/cbm-test-home-XXXXXX");
if (!cbm_mkdtemp(tf_home_sentinel)) {
@@ -175,6 +205,10 @@ static bool tf_setup_cache_sentinel(void) {
* override keeps both conventions pointed at the same isolated tree. */
cbm_setenv("HOME", tf_home_sentinel, 1);
cbm_unsetenv("CBM_CACHE_DIR");
for (size_t i = 0U; i < sizeof(tf_client_home_overrides) / sizeof(tf_client_home_overrides[0]);
i++) {
cbm_unsetenv(tf_client_home_overrides[i]);
}
atexit(tf_cleanup_cache_sentinel);
return true;
}
+39 -25
View File
@@ -69,6 +69,44 @@ require(
"smoke-local.sh must not reserve/release a port or launch a separate http.server",
)
# Every environment variable the CLI honours ahead of $HOME. A fixture that
# redirects only HOME still resolves these to the developer's real config, so
# both the shell fixtures and the C runner must neutralize the whole set.
CLIENT_HOME_OVERRIDES = (
"CLAUDE_CONFIG_DIR",
"CODEX_HOME",
"KIRO_HOME",
"HERMES_HOME",
"QWEN_HOME",
"CLINE_DATA_DIR",
"OPENCLAW_HOME",
"OPENCLAW_STATE_DIR",
"OPENCLAW_PROFILE",
"OPENCLAW_CONFIG_PATH",
"OPENCLAW_WORKSPACE_DIR",
"OPENCODE_CONFIG",
"OPENCODE_CONFIG_DIR",
"COPILOT_HOME",
"CRUSH_GLOBAL_CONFIG",
"VIBE_HOME",
"GLAB_CONFIG_DIR",
"KIMI_CODE_HOME",
"CBM_CONTINUE_CONFIG_PATH",
"CBM_TRAE_CONFIG_PATH",
"CBM_ROO_CONFIG_PATH",
"CBM_CODY_CONFIG_PATH",
)
# The C suite exercises the same install/uninstall paths as the shell fixtures,
# so it needs the same neutralization — otherwise a green run on a developer
# machine only proves the ambient config happened to be writable.
test_main = read("tests/test_main.c")
for variable in CLIENT_HOME_OVERRIDES:
require(
f'"{variable}"' in test_main,
f"tests/test_main.c must neutralize ambient {variable}",
)
for relative, source in (
("scripts/smoke-local.sh", smoke_local),
("test-infrastructure/vm/vm-smoke.sh", vm_smoke),
@@ -89,31 +127,7 @@ for relative, source in (
'wait "$SERVER_PID"' in source,
f"{relative} cleanup must reap the fixture-server process",
)
for variable in (
"CLAUDE_CONFIG_DIR",
"CODEX_HOME",
"KIRO_HOME",
"HERMES_HOME",
"QWEN_HOME",
"CLINE_DATA_DIR",
"OPENCLAW_HOME",
"OPENCLAW_STATE_DIR",
"OPENCLAW_PROFILE",
"OPENCLAW_CONFIG_PATH",
"OPENCLAW_WORKSPACE_DIR",
"OPENCODE_CONFIG",
"OPENCODE_CONFIG_DIR",
"COPILOT_HOME",
"CRUSH_GLOBAL_CONFIG",
"VIBE_HOME",
"GLAB_CONFIG_DIR",
"KIMI_CODE_HOME",
"CBM_CONTINUE_CONFIG_PATH",
"CBM_TRAE_CONFIG_PATH",
"CBM_ROO_CONFIG_PATH",
"CBM_CODY_CONFIG_PATH",
"CBM_TEST_WINDOWS_USER_PATH_RUN_ID",
):
for variable in CLIENT_HOME_OVERRIDES + ("CBM_TEST_WINDOWS_USER_PATH_RUN_ID",):
require(
f"-u {variable}" in source,
f"{relative} must neutralize ambient {variable}",