test(pipeline): cover repeated overlay publish

Add a focused pipeline canary for repeated opt-in overlay publishes of the same changed file. The test proves active overlay reads stay idempotent from the caller perspective: canonical base remains unchanged, the overlay view exposes one function, dirty_files remains a single overlay_ready row, and no reserved canonical generation is leaked.

This is intentionally test-only. It does not claim overlay generation retention or duplicate-publish suppression is solved; those remain separate compaction/cleanup design items.

Validation: 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.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
Andrew Hundt
2026-07-04 02:12:05 -04:00
parent 290627f5e7
commit fcce029177
+79 -14
View File
@@ -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);