From c129c77ad8145fb53e910bb8ebd5a782955b5c50 Mon Sep 17 00:00:00 2001 From: Andrew Hundt Date: Sat, 18 Jul 2026 14:13:04 -0400 Subject: [PATCH] test(pipeline): verify incremental parity across 13 languages Incremental behavior previously lacked one table-driven oracle covering every supported parser/LSP language family. That left exact-upsert and scoped-LSP fallback routing vulnerable to language-specific drift even when individual fixtures passed. Add run_incremental_language_oracle_case in tests/test_pipeline.c. Each synthetic fixture performs a full fast index, edits source to add a callable symbol, runs incremental indexing, asserts the publication kind and fallback reason, and compares the resulting canonical graph with a fresh fast rebuild. Cover Go, C, C++, CUDA, and Python exact publication plus JavaScript, TypeScript, TSX, PHP, C#, Java, Kotlin, and Rust scoped_lsp_gap full fallback. The helper releases pipelines, configuration, and project storage through one cleanup path; ordinary passes and failures remove fixtures, while CBM_TEST_ARTIFACT_DIR retains failed fixtures inside the runner-owned cache. Verification: the ASan/UBSan test runner passed incremental_cross_lsp_language_matrix_matches_fresh_rebuild (13 cases) and eight adjacent exact, C-header fallback, Python/JavaScript scoped-LSP, delete, rename, overlay publish-failure, and overlay extraction-failure tests. git clang-format reported no changed-line edits and git diff --cached --check passed. Signed-off-by: Andrew Hundt --- tests/test_pipeline.c | 172 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/tests/test_pipeline.c b/tests/test_pipeline.c index 8ce3b3057..c2ff1b8cd 100644 --- a/tests/test_pipeline.c +++ b/tests/test_pipeline.c @@ -13795,6 +13795,177 @@ TEST(incremental_javascript_scoped_lsp_gap_reports_full_rebuild_not_cap_overflow PASS(); } +typedef struct { + const char *name; + const char *filename; + const char *initial_source; + const char *updated_source; + cbm_pipeline_publish_kind_t expected_publish_kind; +} incremental_language_oracle_case_t; + +static int run_incremental_language_oracle_case(const incremental_language_oracle_case_t *tc, + char *err, size_t err_sz) { + char root[CBM_PATH_MAX]; + const char *cache = cbm_resolve_cache_dir(); + int n = snprintf(root, sizeof(root), "%s/cbm-incr-language-%s-XXXXXX", cache, tc->name); + if (n < 0 || (size_t)n >= sizeof(root) || !cbm_mkdtemp(root)) { + snprintf(err, err_sz, "%s: fixture directory creation failed", tc->name); + return CBM_STORE_ERR; + } + + int rc = CBM_STORE_ERR; + char source_path[CBM_PATH_MAX]; + char db_path[CBM_PATH_MAX]; + char *project = NULL; + cbm_config_t *cfg = NULL; + cbm_pipeline_t *pipeline = NULL; + n = snprintf(source_path, sizeof(source_path), "%s/%s", root, tc->filename); + if (n < 0 || (size_t)n >= sizeof(source_path) || + th_write_file(source_path, tc->initial_source) != 0) { + snprintf(err, err_sz, "%s: initial source write failed", tc->name); + goto cleanup; + } + n = snprintf(db_path, sizeof(db_path), "%s/graph.db", root); + if (n < 0 || (size_t)n >= sizeof(db_path)) { + snprintf(err, err_sz, "%s: database path overflow", tc->name); + goto cleanup; + } + + cfg = incremental_test_config(root); + if (!cfg || cbm_config_set(cfg, CBM_CONFIG_INCREMENTAL_DERIVED_REFRESH, + CBM_CONFIG_INCREMENTAL_DERIVED_REFRESH_EAGER) != 0) { + snprintf(err, err_sz, "%s: incremental config setup failed", tc->name); + goto cleanup; + } + pipeline = cbm_pipeline_new(root, db_path, CBM_MODE_FAST); + if (!pipeline) { + snprintf(err, err_sz, "%s: initial pipeline allocation failed", tc->name); + goto cleanup; + } + cbm_pipeline_apply_config(pipeline, cfg); + if (cbm_pipeline_run(pipeline) != 0) { + snprintf(err, err_sz, "%s: initial full publication failed", tc->name); + goto cleanup; + } + project = cbm_strdup(cbm_pipeline_project_name(pipeline)); + cbm_pipeline_free(pipeline); + pipeline = NULL; + if (!project) { + snprintf(err, err_sz, "%s: project name allocation failed", tc->name); + goto cleanup; + } + + if (th_write_file(source_path, tc->updated_source) != 0) { + snprintf(err, err_sz, "%s: updated source write failed", tc->name); + goto cleanup; + } + pipeline = cbm_pipeline_new(root, db_path, CBM_MODE_FAST); + if (!pipeline) { + snprintf(err, err_sz, "%s: incremental pipeline allocation failed", tc->name); + goto cleanup; + } + cbm_pipeline_apply_config(pipeline, cfg); + if (cbm_pipeline_run(pipeline) != 0) { + snprintf(err, err_sz, "%s: incremental publication failed", tc->name); + goto cleanup; + } + cbm_pipeline_publish_kind_t actual_kind = cbm_pipeline_publish_kind(pipeline); + const char *actual_reason = cbm_pipeline_publish_reason(pipeline); + if (actual_kind != tc->expected_publish_kind) { + snprintf(err, err_sz, "%s: publish kind=%d reason=%s, expected=%d", tc->name, actual_kind, + actual_reason ? actual_reason : "", tc->expected_publish_kind); + goto cleanup; + } + if (actual_kind == CBM_PIPELINE_PUBLISH_FULL && + (!actual_reason || strcmp(actual_reason, "scoped_lsp_gap") != 0)) { + snprintf(err, err_sz, "%s: full fallback reason=%s, expected=scoped_lsp_gap", tc->name, + actual_reason ? actual_reason : ""); + goto cleanup; + } + cbm_pipeline_free(pipeline); + pipeline = NULL; + + rc = + pipeline_compare_current_db_to_fresh_fast_rebuild(root, db_path, project, cfg, err, err_sz); + if (rc != 0 && (!err || err[0] == '\0')) { + snprintf(err, err_sz, "%s: incremental graph differed from fresh rebuild", tc->name); + } + +cleanup: + cbm_pipeline_free(pipeline); + free(project); + cbm_config_close(cfg); + const char *artifact_dir = getenv("CBM_TEST_ARTIFACT_DIR"); + if (rc != 0 && artifact_dir && artifact_dir[0] != '\0') { + printf(" [incremental-language-artifact] %s\n", root); + } else { + th_rmtree(root); + } + return rc; +} + +TEST(incremental_cross_lsp_language_matrix_matches_fresh_rebuild) { + static const incremental_language_oracle_case_t cases[] = { + {"go", "sample.go", "package sample\n\nfunc Value() int { return 1 }\n", + "package sample\n\nfunc Value() int { return 2 }\nfunc Added() int { return Value() }\n", + CBM_PIPELINE_PUBLISH_INCREMENTAL_EXACT}, + {"c", "sample.c", "int matrix_value(void) { return 1; }\n", + "int matrix_value(void) { return 2; }\nint matrix_added(void) { return matrix_value(); " + "}\n", + CBM_PIPELINE_PUBLISH_INCREMENTAL_EXACT}, + {"cpp", "sample.cpp", "int matrix_value() { return 1; }\n", + "int matrix_value() { return 2; }\nint matrix_added() { return matrix_value(); }\n", + CBM_PIPELINE_PUBLISH_INCREMENTAL_EXACT}, + {"cuda", "sample.cu", "__device__ int matrix_value() { return 1; }\n", + "__device__ int matrix_value() { return 2; }\n" + "__device__ int matrix_added() { return matrix_value(); }\n", + CBM_PIPELINE_PUBLISH_INCREMENTAL_EXACT}, + {"python", "sample.py", "def matrix_value():\n return 1\n", + "def matrix_value():\n return 2\n\ndef matrix_added():\n return matrix_value()\n", + CBM_PIPELINE_PUBLISH_INCREMENTAL_EXACT}, + {"javascript", "sample.js", "export function matrixValue() { return 1; }\n", + "export function matrixValue() { return 2; }\n" + "export function matrixAdded() { return matrixValue(); }\n", + CBM_PIPELINE_PUBLISH_FULL}, + {"typescript", "sample.ts", "export function matrixValue(): number { return 1; }\n", + "export function matrixValue(): number { return 2; }\n" + "export function matrixAdded(): number { return matrixValue(); }\n", + CBM_PIPELINE_PUBLISH_FULL}, + {"tsx", "sample.tsx", "export function MatrixValue(): number { return 1; }\n", + "export function MatrixValue(): number { return 2; }\n" + "export function MatrixAdded(): number { return MatrixValue(); }\n", + CBM_PIPELINE_PUBLISH_FULL}, + {"php", "sample.php", " i32 { 1 }\n", + "pub fn matrix_value() -> i32 { 2 }\n" + "pub fn matrix_added() -> i32 { matrix_value() }\n", + CBM_PIPELINE_PUBLISH_FULL}, + }; + char err[CBM_SZ_8K]; + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + err[0] = '\0'; + int rc = run_incremental_language_oracle_case(&cases[i], err, sizeof(err)); + if (rc != 0) { + FAIL(err[0] ? err : "incremental language oracle failed"); + } + } + PASS(); +} + TEST(incremental_exact_python_receiver_type_gap_matches_full_rebuild) { enum { PIPELINE_EXACT_ONE_PATH = 1 }; if (setup_incremental_repo() != 0) { @@ -17455,6 +17626,7 @@ SUITE(pipeline) { RUN_TEST(incremental_overlay_publish_small_deltas_keeps_canonical_base_visible); RUN_TEST(incremental_exact_python_scoped_lsp_gap_matches_full_rebuild); RUN_TEST(incremental_javascript_scoped_lsp_gap_reports_full_rebuild_not_cap_overflow); + RUN_TEST(incremental_cross_lsp_language_matrix_matches_fresh_rebuild); RUN_TEST(incremental_exact_python_receiver_type_gap_matches_full_rebuild); RUN_TEST(pipeline_persisted_python_defs_feed_scoped_cross_lsp); RUN_TEST(pipeline_store_backed_lsp_cross_uses_import_scope_defs);