diff --git a/tests/test_pipeline.c b/tests/test_pipeline.c index cd7260fe3..4447568bc 100644 --- a/tests/test_pipeline.c +++ b/tests/test_pipeline.c @@ -8509,28 +8509,34 @@ static int pipeline_store_dirty_counts(const char *db_path, const char *project, return rc; } -static int pipeline_store_overlay_file_has_function(const char *db_path, const char *project, - const char *rel_path, const char *name) { +static int pipeline_store_overlay_file_function_count(const char *db_path, const char *project, + const char *rel_path, const char *name) { cbm_store_t *s = cbm_store_open_path_query(db_path); if (!s) { - return 0; + return CBM_STORE_ERR; } cbm_node_t *nodes = NULL; int count = 0; - int found = 0; - if (cbm_store_find_nodes_by_file_overlay_view(s, project, rel_path, &nodes, &count) == + int matches = 0; + if (cbm_store_find_nodes_by_file_overlay_view(s, project, rel_path, &nodes, &count) != CBM_STORE_OK) { - for (int i = 0; i < count; i++) { - if (nodes[i].label && strcmp(nodes[i].label, "Function") == 0 && nodes[i].name && - strcmp(nodes[i].name, name) == 0) { - found = 1; - break; - } - } - cbm_store_free_nodes(nodes, count); + cbm_store_close(s); + return CBM_STORE_ERR; } + for (int i = 0; i < count; i++) { + if (nodes[i].label && strcmp(nodes[i].label, "Function") == 0 && nodes[i].name && + strcmp(nodes[i].name, name) == 0) { + matches++; + } + } + cbm_store_free_nodes(nodes, count); cbm_store_close(s); - return found; + return matches; +} + +static int pipeline_store_overlay_file_has_function(const char *db_path, const char *project, + const char *rel_path, const char *name) { + return pipeline_store_overlay_file_function_count(db_path, project, rel_path, name) > 0; } static int pipeline_store_generation_status_count(const char *db_path, const char *project, @@ -10750,6 +10756,64 @@ TEST(incremental_overlay_publish_delete_keeps_canonical_base_visible) { PASS(); } +TEST(incremental_overlay_publish_repeated_update_keeps_active_view_idempotent) { + 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); + + 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 OverlayRetryOnly() int {\n\treturn 77\n}\n"), + 0); + + for (int i = 0; i < 2; i++) { + 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); + ASSERT_EQ(cbm_pipeline_publish_kind(p), CBM_PIPELINE_PUBLISH_INCREMENTAL_OVERLAY); + ASSERT(!cbm_pipeline_graph_changed(p)); + cbm_pipeline_free(p); + } + + 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, "OverlayRetryOnly")); + ASSERT_EQ(pipeline_store_overlay_file_function_count(g_incr_dbpath, project, "leaf.go", + "OverlayRetryOnly"), + 1); + 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, 1); + + 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"); @@ -12915,6 +12979,7 @@ SUITE(pipeline) { RUN_TEST(incremental_overlay_producer_marks_dirty_ready_without_canonical_mutation); 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_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);