From feb5cd8a6f02e5dc7b16967cb5b10a9571977d7b Mon Sep 17 00:00:00 2001 From: tmonestudio Date: Sun, 12 Jul 2026 20:28:33 -0300 Subject: [PATCH] fix(tests): isolate integration caches from user state Run the test process under a private temporary HOME and clear inherited CBM_CACHE_DIR so production index paths and legacy fixtures resolve to the same isolated cache. Make lang-contract fixtures honor an explicit cache override and remove the sentinel tree at process exit, including after early assertion returns. Validated on Windows with the full runner: 5828 passed, 22 skipped; a 500-project user cache had zero files added or removed. Co-Authored-By: Claude Opus 4.8 Signed-off-by: tmonestudio --- tests/test_lang_contract.c | 17 +++++++++++------ tests/test_main.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/tests/test_lang_contract.c b/tests/test_lang_contract.c index b71a39279..9903d2c11 100644 --- a/tests/test_lang_contract.c +++ b/tests/test_lang_contract.c @@ -71,13 +71,18 @@ static cbm_store_t *lang_open_indexed(LangProj *lp) { if (!lp->project) { return NULL; } - const char *home = getenv("HOME"); - if (!home) { - home = "/tmp"; - } char cache_dir[512]; - snprintf(cache_dir, sizeof(cache_dir), "%s/.cache/codebase-memory-mcp", home); - cbm_mkdir(cache_dir); + const char *configured_cache = getenv("CBM_CACHE_DIR"); + if (configured_cache && configured_cache[0]) { + snprintf(cache_dir, sizeof(cache_dir), "%s", configured_cache); + } else { + const char *home = getenv("HOME"); + if (!home) { + home = "/tmp"; + } + snprintf(cache_dir, sizeof(cache_dir), "%s/.cache/codebase-memory-mcp", home); + } + cbm_mkdir_p(cache_dir, 0755); snprintf(lp->dbpath, sizeof(lp->dbpath), "%s/%s.db", cache_dir, lp->project); unlink(lp->dbpath); lp->srv = cbm_mcp_server_new(NULL); diff --git a/tests/test_main.c b/tests/test_main.c index ce40f799c..69d061201 100644 --- a/tests/test_main.c +++ b/tests/test_main.c @@ -9,6 +9,7 @@ int tf_fail_count = 0; int tf_skip_count = 0; #include "test_framework.h" +#include "test_helpers.h" #include "foundation/compat.h" /* cbm_setenv — #845 supervisor kill switch */ #include "foundation/compat_fs.h" /* cbm_fopen — worker response file */ #include "foundation/mem.h" /* cbm_mem_init — worker budget */ @@ -24,6 +25,32 @@ int tf_skip_count = 0; #include /* #798 follow-up: socket-isolation re-exec probe */ #endif +/* Test handlers that exercise the production index_repository flow must never + * inherit the user's real cache. Individual tests may temporarily override + * this sentinel and restore it; atexit removes anything left behind by an + * assertion that returns before fixture cleanup. */ +static char tf_home_sentinel[512]; + +static void tf_cleanup_cache_sentinel(void) { + if (tf_home_sentinel[0]) { + th_rmtree(tf_home_sentinel); + } +} + +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)) { + return false; + } + /* Legacy integration fixtures derive DB paths from HOME, while production + * cache_dir() prefers CBM_CACHE_DIR. A private HOME plus no inherited cache + * override keeps both conventions pointed at the same isolated tree. */ + cbm_setenv("HOME", tf_home_sentinel, 1); + cbm_unsetenv("CBM_CACHE_DIR"); + atexit(tf_cleanup_cache_sentinel); + return true; +} + /* #832 guard support: when the index supervisor spawns THIS binary as * ` cli --index-worker index_repository --response-out ` * (exactly the argv cbm_index_spawn_worker builds), act as a faithful in-process @@ -267,6 +294,10 @@ int main(int argc, char **argv) { * binary as ` cli --index-worker …` and recursively re-run suites. * A test that exercises the supervisor must explicitly re-enable it. */ cbm_setenv("CBM_INDEX_SUPERVISOR", "0", 1); + if (!tf_setup_cache_sentinel()) { + fprintf(stderr, "failed to create isolated test cache\n"); + return 2; + } g_suite_argc = argc; g_suite_argv = argv;