From 6a29a19c236ecd1faa46fe6d1664ab7ae1d858b1 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Mon, 5 Oct 2026 23:55:25 +0200 Subject: [PATCH] fix(test-impact): build and run on Linux GCC, git before 2.44, LeakSanitizer PR CI on the first push of these commits found what no local leg had built yet: - test_impact_result_validate.c: initialize the count GCC's -Wmaybe-uninitialized flags (-Werror on every Linux build: smoke, shadow, analyze). - tests: split one-line `if` chains GCC reports as misleading indentation, and drop a redundant `= {0}` it reads as missing braces (the GCC test runner did not compile). - tests/test_test_impact_engine.c: realpath() into a PATH_MAX buffer; glibc's _FORTIFY_SOURCE aborts on anything smaller, as test_index_resilience.c already notes. - test_impact_git.c: pass --no-lazy-fetch only when the git executable takes it (git 2.44+, probed once per session with the option on --version). Older git rejected it and every engine command failed; Ubuntu 24.04 ships git 2.43. `-c protocol.allow=never` already turns a lazy fetch into an error there, which is what the option asks for. - test_impact_engine.c: the selection and publish job threads release their thread-local node-type bitset cache before they end, as the parallel workers and the MCP index thread do. LeakSanitizer reported it (488 bytes in 13 allocations, test-diag); the Linux arm64 ASan leg reproduced exactly that and is clean with the fix. Signed-off-by: Martin Vogel --- src/mcp/test_impact_engine.c | 6 ++++++ src/mcp/test_impact_git.c | 23 +++++++++++++++++++++-- src/mcp/test_impact_result_validate.c | 2 +- tests/test_pipeline_frozen.c | 1 - tests/test_store_graph_digest.c | 7 +++++-- tests/test_test_impact_engine.c | 9 ++++++++- tests/test_test_impact_git.c | 7 +++++-- 7 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/mcp/test_impact_engine.c b/src/mcp/test_impact_engine.c index 391a6d6d7..93a16f226 100644 --- a/src/mcp/test_impact_engine.c +++ b/src/mcp/test_impact_engine.c @@ -13,6 +13,7 @@ #include "foundation/sha256.h" #include "store/store_graph_digest.h" #include "store/store_impact.h" +#include "helpers.h" /* cbm_kind_in_set_free_cache — the job thread's cache teardown */ #include #include @@ -621,6 +622,10 @@ static void *te_job_main(void *arg) { status = CBM_TEST_IMPACT_CANCELLED; } te_free(&c); + /* The snapshot build extracted on THIS thread, which ends here: its + * thread-local node-type bitset cache goes with it (as the MCP index + * thread and the parallel workers release theirs). */ + cbm_kind_in_set_free_cache(); job->status = status; return NULL; } @@ -678,6 +683,7 @@ static void *te_publish_main(void *arg) { bool ok = te_publish(&c, p); job->status = c.oom ? CBM_TEST_IMPACT_OOM : ok ? CBM_TEST_IMPACT_OK : CBM_TEST_IMPACT_INVALID; te_free(&c); + cbm_kind_in_set_free_cache(); /* this thread's extraction cache, as in te_job_main */ return NULL; } diff --git a/src/mcp/test_impact_git.c b/src/mcp/test_impact_git.c index 299ae03f2..b30a933d7 100644 --- a/src/mcp/test_impact_git.c +++ b/src/mcp/test_impact_git.c @@ -60,6 +60,7 @@ struct cbm_git_facts { cbm_git_facts_error_t stopped; gf_batch_budget_t *batch_budget; /* borrowed stack state during one serialized call */ gf_inventory_cache_t inventory_cache[2]; + bool no_lazy_fetch; /* the executable takes --no-lazy-fetch (gf_probe_no_lazy_fetch) */ }; typedef struct { @@ -486,7 +487,6 @@ static bool gf_run_input(cbm_git_facts_t *facts, const char *const *arguments, i "--no-replace-objects", "--no-pager", "--literal-pathspecs", - "--no-lazy-fetch", "-C", facts->options.root, "-c", @@ -496,11 +496,13 @@ static bool gf_run_input(cbm_git_facts_t *facts, const char *const *arguments, i size_t prefix_count = sizeof(prefix) / sizeof(prefix[0]); size_t directory_count = facts->command_git_dir ? 4 : 0; if (count < 1 || count >= GF_ARG_CAP || - (size_t)count + prefix_count + directory_count + (size_t)facts->git_pin_count + 1 > + (size_t)count + prefix_count + directory_count + (size_t)facts->git_pin_count + 2 > GF_ARG_CAP) return gf_error(error, CBM_GIT_FACTS_INVALID, "Invalid internal Git command"); for (size_t i = 0; i < prefix_count; i++) argv[argc++] = prefix[i]; + if (facts->no_lazy_fetch) + argv[argc++] = "--no-lazy-fetch"; if (facts->command_git_dir) { argv[argc++] = "--git-dir"; argv[argc++] = facts->command_git_dir; @@ -907,6 +909,22 @@ static bool gf_open_identity(cbm_git_facts_t *facts, cbm_git_facts_error_t *erro return gf_gate(facts, error); } +/* --no-lazy-fetch (git 2.44) keeps an object missing from a partial clone + * from being fetched. Older git rejects the option, which failed every engine + * command (Ubuntu 24.04 ships git 2.43). There `-c protocol.allow=never` + * already turns such a fetch into an error, which is what the option asks + * for, so it is passed only when the executable accepts it: one probe per + * facts session, outside the command budget, output discarded. */ +static bool gf_probe_no_lazy_fetch(const char *git) { + const char *argv[] = {git, "--no-lazy-fetch", "--version", NULL}; + cbm_proc_opts_t options = {.bin = git, + .argv = argv, + .cancel_grace_ms = CBM_SUBPROCESS_DEFAULT_CANCEL_GRACE_MS, + .strip_git_repo_env = true}; + cbm_proc_result_t result = {0}; + return cbm_subprocess_run(&options, &result) == 0 && result.outcome == CBM_PROC_CLEAN; +} + cbm_git_facts_t *cbm_git_facts_open(const cbm_git_facts_options_t *options, cbm_git_facts_error_t *error) { cbm_git_facts_error_t local; @@ -959,6 +977,7 @@ cbm_git_facts_t *cbm_git_facts_open(const cbm_git_facts_options_t *options, cbm_git_facts_free(facts); return NULL; } + facts->no_lazy_fetch = gf_probe_no_lazy_fetch(facts->options.git_executable); if (!gf_open_identity(facts, error)) { cbm_git_facts_free(facts); return NULL; diff --git a/src/mcp/test_impact_result_validate.c b/src/mcp/test_impact_result_validate.c index ac5200741..132598cf6 100644 --- a/src/mcp/test_impact_result_validate.c +++ b/src/mcp/test_impact_result_validate.c @@ -27,7 +27,7 @@ static bool tir_label(tir_context *c, const char *s, tir_bytes *out) { static bool tir_string_array(tir_context *c, const char *const *strings, int count, tir_bytes **saved) { - size_t n; + size_t n = 0; if (!tir_count(c, strings, count, &n)) return false; tir_bytes *copy = saved ? tir_alloc(c, n, sizeof(*copy)) : NULL; diff --git a/tests/test_pipeline_frozen.c b/tests/test_pipeline_frozen.c index c4fe336d1..bb6b120e5 100644 --- a/tests/test_pipeline_frozen.c +++ b/tests/test_pipeline_frozen.c @@ -258,7 +258,6 @@ static cbm_pipeline_frozen_inputs_t pf_inputs(pf_fixture_t *f, const char *confi .config_bytes = config, .config_len = config ? strlen(config) : 0, .pinned_git = git, - .resource_policy = {0}, .cancelled = cancelled}; } static cbm_pipeline_frozen_status_t pf_create(pf_fixture_t *f, const char *config, diff --git a/tests/test_store_graph_digest.c b/tests/test_store_graph_digest.c index 6e69fa9b8..abb32ba7c 100644 --- a/tests/test_store_graph_digest.c +++ b/tests/test_store_graph_digest.c @@ -420,9 +420,12 @@ TEST(store_graph_digest_invalid_arguments_missing_project_and_initial_latches) { int opened=s && collation?cbm_store_read_scope_open(s,NULL,NULL,&scope):CBM_STORE_ERR; cbm_store_graph_digest_limits_t lim=gd_room;cbm_store_graph_digest_t out;memset(&out,0xa5,sizeof(out)); const unsigned char *p=(const unsigned char *)"p";size_t len=1; - if(which==2)p=NULL;if(which==3)len=0;if(which==4)len=(size_t)INT_MAX+1; + if(which==2)p=NULL; + if(which==3)len=0; + if(which==4)len=(size_t)INT_MAX+1; if(which==5){p=(const unsigned char *)"p\0q";len=3;} - if(which==7)lim.max_rows=0;if(which==8)lim.max_framed_bytes=0; + if(which==7)lim.max_rows=0; + if(which==8)lim.max_framed_bytes=0; if(which==9)lim.max_framed_bytes=UINT64_MAX/8+1; if(which==10)p=(const unsigned char *)"P"; int rc=opened==CBM_STORE_OK?cbm_store_graph_digest(which==0?NULL:scope,p,len,which==6?NULL:&lim,which==1?NULL:&out):-1; diff --git a/tests/test_test_impact_engine.c b/tests/test_test_impact_engine.c index 7bae6388b..8d735340b 100644 --- a/tests/test_test_impact_engine.c +++ b/tests/test_test_impact_engine.c @@ -16,11 +16,18 @@ #include "foundation/subprocess.h" #include "mcp/test_impact_engine.h" +#include #include #include #include #include +/* glibc's _FORTIFY_SOURCE realpath() aborts unless the output buffer is at + * least PATH_MAX bytes, independent of the actual path length. */ +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + typedef struct { char git[1024]; char home[1024]; @@ -126,7 +133,7 @@ static bool tie_open_with(tie_fixture_t *fx, const char *extra_path, const char memset(fx, 0, sizeof(*fx)); const char *git = cbm_find_cli("git", cbm_get_home_dir()); const char *home = th_mktempdir("cbm-ti-engine"); - char real[1024]; + char real[PATH_MAX]; if (!git || !home || !realpath(home, real) || strlen(git) >= sizeof(fx->git)) { return false; } diff --git a/tests/test_test_impact_git.c b/tests/test_test_impact_git.c index 46c4ced08..ed8483de5 100644 --- a/tests/test_test_impact_git.c +++ b/tests/test_test_impact_git.c @@ -2002,7 +2002,9 @@ TEST(test_git_blob_batch_validates_full_selection_and_empty_requests) { gfb_error(facts,&request,NULL,CBM_GIT_FACTS_INVALID) && ok; for(int i=0;control && i<3;i++) { cbm_git_blob_batch_limits_t zero=limits; - if(i==0)zero.max_entries=0;if(i==1)zero.max_input_bytes=0;if(i==2)zero.max_arena_bytes=0; + if(i==0)zero.max_entries=0; + if(i==1)zero.max_input_bytes=0; + if(i==2)zero.max_arena_bytes=0; ok=gfb_error(facts,&request,&zero,CBM_GIT_FACTS_INVALID) && ok; } bool no_out=control && !cbm_git_facts_read_blob_batch(facts,&request,&limits,NULL,&error) && @@ -2265,7 +2267,8 @@ static cbm_git_bytes_t gfb_concat(CBMArena *arena,cbm_git_bytes_t a,cbm_git_byte if(a.length>SIZE_MAX-b.length)return (cbm_git_bytes_t){NULL,0}; size_t size=a.length+b.length;unsigned char *bytes=cbm_arena_alloc(arena,size?size:1); if(!bytes)return (cbm_git_bytes_t){NULL,0}; - if(a.length)memcpy(bytes,a.data,a.length);if(b.length)memcpy(bytes+a.length,b.data,b.length); + if(a.length)memcpy(bytes,a.data,a.length); + if(b.length)memcpy(bytes+a.length,b.data,b.length); return (cbm_git_bytes_t){bytes,size}; }