mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-03 08:03:38 +08:00
test(pipeline): cover overlay publish failure
Add a CBM_TEST_FAIL_INCREMENTAL_PHASE value for the overlay producer boundary and share the existing phase-failure helper between incremental and delta code. When injected after overlay generation reservation, the producer marks the overlay generation failed and returns an error. Add a focused pipeline canary proving opt-in overlay publish failure falls back to canonical exact publish: the changed symbol is present canonically, the failed overlay generation is recorded, no ready overlay generation or reserved canonical generation remains, and dirty rows are cleared after canonical repair. Validation: initial build failed because the helper included the wrong declaration header; corrected to foundation/platform.h. Final gates: make -f Makefile.cbm build/c/test-runner -j8; CBM_ONLY_SUITE=pipeline build/c/test-runner; bash scripts/check-source-safety.sh; git diff --check; make -f Makefile.cbm cbm -j8. Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
@@ -1489,6 +1489,11 @@ int cbm_pipeline_publish_overlay_file_delta_batch(cbm_store_t *store,
|
||||
if (rc != CBM_STORE_OK) {
|
||||
return rc;
|
||||
}
|
||||
if (cbm_pipeline_test_fail_phase_enabled(CBM_TEST_FAIL_INCREMENTAL_OVERLAY_PUBLISH)) {
|
||||
(void)cbm_store_set_overlay_generation_status(store, project, overlay_generation,
|
||||
CBM_STORE_OVERLAY_STATUS_FAILED);
|
||||
return CBM_STORE_ERR;
|
||||
}
|
||||
|
||||
const cbm_store_file_delta_t **store_deltas =
|
||||
malloc((size_t)delta_count * sizeof(*store_deltas));
|
||||
|
||||
@@ -38,8 +38,6 @@ enum { INCR_RING_BUF = 4, INCR_RING_MASK = 3, INCR_TS_BUF = 24 };
|
||||
|
||||
/* ── Timing helper (same as pipeline.c) ──────────────────────────── */
|
||||
|
||||
static const char cbm_incr_test_env_disabled[] = "0";
|
||||
|
||||
/* Fork renames this elapsed_ms_incr (vs pipeline.c's elapsed_ms) to avoid an
|
||||
* ODR/duplicate-symbol collision when both TUs are linked into the same binary. */
|
||||
static double elapsed_ms_incr(struct timespec start) {
|
||||
@@ -64,14 +62,6 @@ static const char *itoa_buf_incr(int v) {
|
||||
static void free_mode_skipped(cbm_file_hash_t *ms, int count);
|
||||
static void free_deleted_paths(char **deleted, int count);
|
||||
|
||||
static bool incr_test_fail_phase_enabled(const char *phase) {
|
||||
char buf[CBM_SZ_64];
|
||||
const char *val =
|
||||
cbm_safe_getenv(CBM_TEST_FAIL_INCREMENTAL_PHASE, buf, sizeof(buf), NULL);
|
||||
return val && val[0] != '\0' && strcmp(val, cbm_incr_test_env_disabled) != 0 &&
|
||||
phase && strcmp(val, phase) == 0;
|
||||
}
|
||||
|
||||
static bool incr_is_c_family_header(CBMLanguage lang, const char *rel_path) {
|
||||
if (!rel_path) {
|
||||
return false;
|
||||
@@ -270,7 +260,7 @@ static int find_deleted_files(const char *repo_path, cbm_file_info_t *files, int
|
||||
}
|
||||
|
||||
int rc = CBM_STORE_OK;
|
||||
if (incr_test_fail_phase_enabled(CBM_TEST_FAIL_INCREMENTAL_CLASSIFY_DELETED)) {
|
||||
if (cbm_pipeline_test_fail_phase_enabled(CBM_TEST_FAIL_INCREMENTAL_CLASSIFY_DELETED)) {
|
||||
cbm_log_error("incremental.err", "phase", CBM_TEST_FAIL_INCREMENTAL_CLASSIFY_DELETED,
|
||||
"rc", itoa_buf_incr(CBM_STORE_ERR));
|
||||
rc = CBM_STORE_ERR;
|
||||
@@ -720,7 +710,7 @@ static int persist_hashes(cbm_store_t *store, const char *project, cbm_file_info
|
||||
int current_failed = 0;
|
||||
int ms_failed = 0;
|
||||
|
||||
if (incr_test_fail_phase_enabled(CBM_TEST_FAIL_INCREMENTAL_HASH_PERSIST)) {
|
||||
if (cbm_pipeline_test_fail_phase_enabled(CBM_TEST_FAIL_INCREMENTAL_HASH_PERSIST)) {
|
||||
cbm_log_error("incremental.err", "phase", CBM_TEST_FAIL_INCREMENTAL_HASH_PERSIST, "rc",
|
||||
itoa_buf_incr(CBM_STORE_ERR));
|
||||
return CBM_STORE_ERR;
|
||||
@@ -840,7 +830,7 @@ static int run_extract_resolve(cbm_pipeline_ctx_t *ctx, cbm_file_info_t *changed
|
||||
CBMFileResult **cache = (CBMFileResult **)calloc(ci, sizeof(CBMFileResult *));
|
||||
if (cache) {
|
||||
int rc = 0;
|
||||
if (incr_test_fail_phase_enabled(CBM_TEST_FAIL_INCREMENTAL_EXTRACT)) {
|
||||
if (cbm_pipeline_test_fail_phase_enabled(CBM_TEST_FAIL_INCREMENTAL_EXTRACT)) {
|
||||
cbm_log_error("incremental.err", "phase", CBM_TEST_FAIL_INCREMENTAL_EXTRACT, "rc",
|
||||
itoa_buf_incr(CBM_NOT_FOUND));
|
||||
incr_free_result_cache(cache, ci);
|
||||
@@ -857,7 +847,7 @@ static int run_extract_resolve(cbm_pipeline_ctx_t *ctx, cbm_file_info_t *changed
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (incr_test_fail_phase_enabled(CBM_TEST_FAIL_INCREMENTAL_REGISTRY)) {
|
||||
if (cbm_pipeline_test_fail_phase_enabled(CBM_TEST_FAIL_INCREMENTAL_REGISTRY)) {
|
||||
cbm_log_error("incremental.err", "phase", CBM_TEST_FAIL_INCREMENTAL_REGISTRY, "rc",
|
||||
itoa_buf_incr(CBM_NOT_FOUND));
|
||||
incr_free_result_cache(cache, ci);
|
||||
@@ -885,7 +875,7 @@ static int run_extract_resolve(cbm_pipeline_ctx_t *ctx, cbm_file_info_t *changed
|
||||
* still fires; cross-file resolution is deferred to the
|
||||
* next full re-index. Pass NULL/0/NULL to make the fused
|
||||
* step in resolve_worker a no-op. */
|
||||
if (incr_test_fail_phase_enabled(CBM_TEST_FAIL_INCREMENTAL_RESOLVE)) {
|
||||
if (cbm_pipeline_test_fail_phase_enabled(CBM_TEST_FAIL_INCREMENTAL_RESOLVE)) {
|
||||
cbm_log_error("incremental.err", "phase", CBM_TEST_FAIL_INCREMENTAL_RESOLVE, "rc",
|
||||
itoa_buf_incr(CBM_NOT_FOUND));
|
||||
incr_free_result_cache(cache, ci);
|
||||
@@ -2162,7 +2152,7 @@ int cbm_pipeline_run_incremental(cbm_pipeline_t *p, const char *db_path, cbm_fil
|
||||
}
|
||||
}
|
||||
if (pipeline_rc == 0) {
|
||||
if (incr_test_fail_phase_enabled(CBM_TEST_FAIL_INCREMENTAL_POSTPASS)) {
|
||||
if (cbm_pipeline_test_fail_phase_enabled(CBM_TEST_FAIL_INCREMENTAL_POSTPASS)) {
|
||||
cbm_log_error("incremental.err", "phase", CBM_TEST_FAIL_INCREMENTAL_POSTPASS, "rc",
|
||||
itoa_buf_incr(CBM_NOT_FOUND));
|
||||
pipeline_rc = CBM_NOT_FOUND;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "discover/discover.h"
|
||||
#include "foundation/hash_table.h"
|
||||
#include "foundation/str_util.h"
|
||||
#include "foundation/platform.h"
|
||||
#include "cbm.h"
|
||||
#include "service_patterns.h"
|
||||
#include "lsp/go_lsp.h" /* CBMLSPDef for cbm_parallel_resolve cross-LSP inputs */
|
||||
@@ -189,12 +190,23 @@ static inline void cbm_pipeline_close_call_edge_props(char *props, size_t props_
|
||||
/* Test-only incremental fault injection. Values name internal phases and are
|
||||
* intentionally not user configuration. */
|
||||
#define CBM_TEST_FAIL_INCREMENTAL_PHASE "CBM_TEST_FAIL_INCREMENTAL_PHASE"
|
||||
#define CBM_TEST_FAIL_INCREMENTAL_DISABLED "0"
|
||||
#define CBM_TEST_FAIL_INCREMENTAL_EXTRACT "incr_extract"
|
||||
#define CBM_TEST_FAIL_INCREMENTAL_REGISTRY "incr_registry"
|
||||
#define CBM_TEST_FAIL_INCREMENTAL_RESOLVE "incr_resolve"
|
||||
#define CBM_TEST_FAIL_INCREMENTAL_CLASSIFY_DELETED "classify_deleted"
|
||||
#define CBM_TEST_FAIL_INCREMENTAL_POSTPASS "postpass"
|
||||
#define CBM_TEST_FAIL_INCREMENTAL_HASH_PERSIST "hash_persist"
|
||||
#define CBM_TEST_FAIL_INCREMENTAL_OVERLAY_PUBLISH "overlay_publish"
|
||||
|
||||
static inline bool cbm_pipeline_test_fail_phase_enabled(const char *phase) {
|
||||
char buf[CBM_SZ_64];
|
||||
const char *val =
|
||||
cbm_safe_getenv(CBM_TEST_FAIL_INCREMENTAL_PHASE, buf, sizeof(buf), NULL);
|
||||
return val && val[0] != '\0' &&
|
||||
strcmp(val, CBM_TEST_FAIL_INCREMENTAL_DISABLED) != 0 && phase &&
|
||||
strcmp(val, phase) == 0;
|
||||
}
|
||||
|
||||
/* ── Pipeline context (internal) ─────────────────────────────────── */
|
||||
|
||||
|
||||
@@ -8568,6 +8568,21 @@ static int pipeline_store_completed_generation_count(const char *db_path, const
|
||||
CBM_STORE_INDEX_STATUS_COMPLETE);
|
||||
}
|
||||
|
||||
static int pipeline_store_overlay_generation_status_count(const char *db_path,
|
||||
const char *project,
|
||||
const char *status) {
|
||||
cbm_store_t *s = cbm_store_open_path_query(db_path);
|
||||
if (!s) {
|
||||
return CBM_STORE_ERR;
|
||||
}
|
||||
int count = CBM_STORE_ERR;
|
||||
if (cbm_store_count_overlay_generations(s, project, status, &count) != CBM_STORE_OK) {
|
||||
count = CBM_STORE_ERR;
|
||||
}
|
||||
cbm_store_close(s);
|
||||
return count;
|
||||
}
|
||||
|
||||
static int pipeline_store_count_file_rows_sql(const char *db_path, const char *project,
|
||||
const char *rel_path, const char *sql,
|
||||
int *out_count) {
|
||||
@@ -10814,6 +10829,71 @@ TEST(incremental_overlay_publish_repeated_update_keeps_active_view_idempotent) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(incremental_overlay_publish_failure_falls_back_to_canonical_exact) {
|
||||
pipeline_env_snapshot_t fail_env = pipeline_env_save(CBM_TEST_FAIL_INCREMENTAL_PHASE);
|
||||
|
||||
if (setup_incremental_repo() != 0) {
|
||||
FAIL("setup failed");
|
||||
}
|
||||
|
||||
cbm_config_t *cfg = incremental_test_config(g_incr_tmpdir);
|
||||
ASSERT_NOT_NULL(cfg);
|
||||
ASSERT_EQ(cbm_config_set(cfg, CBM_CONFIG_OVERLAY_PUBLISH,
|
||||
CBM_CONFIG_OVERLAY_PUBLISH_SMALL_DELTAS),
|
||||
0);
|
||||
cbm_pipeline_t *p = cbm_pipeline_new(g_incr_tmpdir, g_incr_dbpath, CBM_MODE_FAST);
|
||||
ASSERT_NOT_NULL(p);
|
||||
cbm_pipeline_apply_config(p, cfg);
|
||||
ASSERT_EQ(cbm_pipeline_run(p), 0);
|
||||
char *project = cbm_strdup(cbm_pipeline_project_name(p));
|
||||
cbm_pipeline_free(p);
|
||||
ASSERT_NOT_NULL(project);
|
||||
ASSERT(!pipeline_store_has_function_name(g_incr_dbpath, project, "OverlayFailureOnly"));
|
||||
|
||||
char path[CBM_PATH_MAX];
|
||||
int n = snprintf(path, sizeof(path), "%s/leaf.go", g_incr_tmpdir);
|
||||
ASSERT(n >= 0 && (size_t)n < sizeof(path));
|
||||
ASSERT_EQ(th_write_file(path,
|
||||
"package main\n\n"
|
||||
"func Leaf() int {\n\treturn 2\n}\n\n"
|
||||
"func OverlayFailureOnly() int {\n\treturn 88\n}\n"),
|
||||
0);
|
||||
|
||||
p = cbm_pipeline_new(g_incr_tmpdir, g_incr_dbpath, CBM_MODE_FAST);
|
||||
ASSERT_NOT_NULL(p);
|
||||
cbm_pipeline_apply_config(p, cfg);
|
||||
cbm_setenv(CBM_TEST_FAIL_INCREMENTAL_PHASE, CBM_TEST_FAIL_INCREMENTAL_OVERLAY_PUBLISH, 1);
|
||||
int run_rc = cbm_pipeline_run(p);
|
||||
pipeline_env_restore(&fail_env);
|
||||
ASSERT_EQ(run_rc, 0);
|
||||
ASSERT_EQ(cbm_pipeline_publish_kind(p), CBM_PIPELINE_PUBLISH_INCREMENTAL_EXACT);
|
||||
ASSERT(cbm_pipeline_graph_changed(p));
|
||||
cbm_pipeline_free(p);
|
||||
|
||||
ASSERT_EQ(pipeline_store_overlay_generation_status_count(
|
||||
g_incr_dbpath, project, CBM_STORE_OVERLAY_STATUS_FAILED),
|
||||
1);
|
||||
ASSERT_EQ(pipeline_store_overlay_generation_status_count(
|
||||
g_incr_dbpath, project, CBM_STORE_OVERLAY_STATUS_READY),
|
||||
0);
|
||||
ASSERT_EQ(pipeline_store_generation_status_count(g_incr_dbpath, project,
|
||||
CBM_STORE_INDEX_STATUS_RESERVED),
|
||||
0);
|
||||
ASSERT(pipeline_store_has_function_name(g_incr_dbpath, project, "OverlayFailureOnly"));
|
||||
int dirty_pending = -1;
|
||||
int dirty_overlay_ready = -1;
|
||||
ASSERT_EQ(pipeline_store_dirty_counts(g_incr_dbpath, project, &dirty_pending,
|
||||
&dirty_overlay_ready),
|
||||
CBM_STORE_OK);
|
||||
ASSERT_EQ(dirty_pending, 0);
|
||||
ASSERT_EQ(dirty_overlay_ready, 0);
|
||||
|
||||
free(project);
|
||||
cbm_config_close(cfg);
|
||||
cleanup_incremental_repo();
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(incremental_full_mode_keeps_exact_upsert_disabled) {
|
||||
if (setup_incremental_repo() != 0) {
|
||||
FAIL("setup failed");
|
||||
@@ -12980,6 +13060,7 @@ SUITE(pipeline) {
|
||||
RUN_TEST(incremental_overlay_publish_small_deltas_keeps_canonical_base_visible);
|
||||
RUN_TEST(incremental_overlay_publish_delete_keeps_canonical_base_visible);
|
||||
RUN_TEST(incremental_overlay_publish_repeated_update_keeps_active_view_idempotent);
|
||||
RUN_TEST(incremental_overlay_publish_failure_falls_back_to_canonical_exact);
|
||||
RUN_TEST(incremental_full_mode_keeps_exact_upsert_disabled);
|
||||
RUN_TEST(incremental_detects_same_size_rewrite_with_preserved_mtime);
|
||||
RUN_TEST(incremental_missing_file_state_keeps_legacy_metadata_path);
|
||||
|
||||
Reference in New Issue
Block a user