mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
test(769): end-to-end sibling regression + index-format rebuild boundary Refs #769
Signed-off-by: LA-10 <leenshuhail1@gmail.com>
This commit is contained in:
1 parent
010569fa6c
commit
4d9da40b36
9 files changed
+405
-1
No files matched your search
+1
-1
@@ -559,7 +559,7 @@ TEST_DISCOVER_SRCS = \
|
||||
|
||||
TEST_GRAPH_BUFFER_SRCS = tests/test_graph_buffer.c
|
||||
|
||||
TEST_PIPELINE_SRCS = tests/test_registry.c tests/test_pipeline.c tests/test_cross_repo.c tests/test_fqn.c tests/test_route_canon.c tests/test_path_alias.c tests/test_configlink.c tests/test_infrascan.c tests/test_worker_pool.c tests/test_parallel.c tests/test_index_resilience.c tests/test_call_reference_contract.c tests/repro/repro_call_scope_usages.c tests/repro/repro_call_argument_usages.c tests/repro/repro_reference_precision.c tests/repro/repro_lexical_binding_precision.c tests/repro/repro_call_argument_matrix_a.c tests/repro/repro_call_argument_matrix_b.c tests/repro/repro_call_node_behaviors.c tests/repro/repro_language_registry.c tests/repro/repro_call_node_manifest.c tests/repro/repro_lsp_ordered_signatures.c tests/repro/repro_lsp_ordered_local.c tests/repro/repro_ts_overload_return_chains.c tests/repro/repro_harness_cleanup.c tests/repro/repro_runner_filter.c
|
||||
TEST_PIPELINE_SRCS = tests/test_registry.c tests/test_pipeline.c tests/test_cross_repo.c tests/test_fqn.c tests/test_route_canon.c tests/test_path_alias.c tests/test_configlink.c tests/test_infrascan.c tests/test_worker_pool.c tests/test_parallel.c tests/test_index_resilience.c tests/test_index_format.c tests/test_call_reference_contract.c tests/repro/repro_call_scope_usages.c tests/repro/repro_call_argument_usages.c tests/repro/repro_reference_precision.c tests/repro/repro_lexical_binding_precision.c tests/repro/repro_call_argument_matrix_a.c tests/repro/repro_call_argument_matrix_b.c tests/repro/repro_call_node_behaviors.c tests/repro/repro_language_registry.c tests/repro/repro_call_node_manifest.c tests/repro/repro_lsp_ordered_signatures.c tests/repro/repro_lsp_ordered_local.c tests/repro/repro_ts_overload_return_chains.c tests/repro/repro_harness_cleanup.c tests/repro/repro_runner_filter.c
|
||||
|
||||
TEST_WATCHER_SRCS = tests/test_watcher.c
|
||||
|
||||
|
||||
@@ -8343,6 +8343,9 @@ static char *handle_index_repository(cbm_mcp_server_t *srv, const char *args) {
|
||||
srv, doc, root, project_name, repo_path, persistence, p, excluded_dirs, excluded_count,
|
||||
file_errors, file_error_count, has_logfile ? logfile_path : NULL);
|
||||
yyjson_mut_obj_add_str(doc, root, "status", degraded ? "degraded" : "indexed");
|
||||
if (cbm_pipeline_had_format_migration(p)) {
|
||||
yyjson_mut_obj_add_bool(doc, root, "format_migration", true);
|
||||
}
|
||||
} else {
|
||||
yyjson_mut_obj_add_str(doc, root, "status", "error");
|
||||
yyjson_mut_obj_add_str(doc, root, "hint",
|
||||
|
||||
@@ -190,6 +190,10 @@ struct cbm_pipeline {
|
||||
int committed_nodes;
|
||||
int committed_edges;
|
||||
|
||||
/* #769: set when a stale-format index was routed through the one-time
|
||||
* full rebuild, so the MCP response can surface the migration. */
|
||||
bool format_migration;
|
||||
|
||||
/* ADR (project_summaries) captured before a full-reindex DB delete, so it
|
||||
* can be restored after the rebuild. NULL when no ADR existed. Issue #516. */
|
||||
char *saved_adr;
|
||||
@@ -224,6 +228,10 @@ void cbm_pipeline_set_pkgmap(CBMHashTable *map) {
|
||||
g_pkgmap = map;
|
||||
}
|
||||
|
||||
bool cbm_pipeline_had_format_migration(const cbm_pipeline_t *p) {
|
||||
return p && p->format_migration;
|
||||
}
|
||||
|
||||
/* ── Timing helper ──────────────────────────────────────────────── */
|
||||
|
||||
static double elapsed_ms(struct timespec start) {
|
||||
@@ -1393,6 +1401,24 @@ static int try_incremental_or_delete_db(cbm_pipeline_t *p, cbm_file_info_t *file
|
||||
free(db_path);
|
||||
return CBM_PIPELINE_FORCE_FULL_REINDEX;
|
||||
}
|
||||
|
||||
cbm_store_t *fmt_store = cbm_store_open_path_query(db_path);
|
||||
int fmt = 0;
|
||||
if (fmt_store) {
|
||||
cbm_store_get_format_version(fmt_store, &fmt);
|
||||
cbm_store_close(fmt_store);
|
||||
}
|
||||
if (fmt != CBM_INDEX_FORMAT_VERSION) {
|
||||
cbm_log_info("pipeline.route", "path", "format_change_reindex", "stored_format",
|
||||
itoa_buf(fmt));
|
||||
p->format_migration = true;
|
||||
int adr_rc = capture_existing_adr(p, db_path);
|
||||
(void)cbm_unlink(db_path);
|
||||
(void)cbm_remove_db_sidecars(db_path);
|
||||
free(db_path);
|
||||
return adr_rc != 0 ? adr_rc : CBM_PIPELINE_FORCE_FULL_REINDEX;
|
||||
}
|
||||
|
||||
cbm_log_info("pipeline.route", "path", "incremental_manifest");
|
||||
int rc = cbm_pipeline_run_incremental(p, db_path, files, file_count, baseline_manifest,
|
||||
baseline_count, force_full_on_mismatch);
|
||||
@@ -1747,6 +1773,11 @@ int cbm_pipeline_publish_staged(char *stage_path, const cbm_pipeline_generation_
|
||||
ok = cbm_store_adr_store(store, generation->project, generation->adr_content) ==
|
||||
CBM_STORE_OK;
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
ok = cbm_store_set_format_version(store, CBM_INDEX_FORMAT_VERSION) == CBM_STORE_OK;
|
||||
}
|
||||
|
||||
cbm_log_info("publish.timing", "block", "writes", "elapsed_ms",
|
||||
itoa_buf((int)elapsed_ms(t_pub)));
|
||||
cbm_clock_gettime(CLOCK_MONOTONIC, &t_pub);
|
||||
|
||||
@@ -93,6 +93,8 @@ int cbm_pipeline_get_mode(const cbm_pipeline_t *p);
|
||||
* to NULL/0 when p is NULL or nothing was excluded. Do not free. */
|
||||
void cbm_pipeline_get_excluded(const cbm_pipeline_t *p, char ***out, int *count);
|
||||
|
||||
bool cbm_pipeline_had_format_migration(const cbm_pipeline_t *p);
|
||||
|
||||
/* Committed node/edge counts captured at dump time (-1 when dump did not run).
|
||||
* Nodes are the #334 plausibility-gate axis; edges are informational only. */
|
||||
void cbm_pipeline_get_committed_counts(const cbm_pipeline_t *p, int *nodes, int *edges);
|
||||
|
||||
@@ -3512,6 +3512,36 @@ int cbm_store_list_files(cbm_store_t *s, const char *project, char ***out, int *
|
||||
return CBM_STORE_OK;
|
||||
}
|
||||
|
||||
/* ── Index format version (PRAGMA user_version) ─────────────────── */
|
||||
|
||||
int cbm_store_get_format_version(cbm_store_t *s, int *out) {
|
||||
if (!s || !s->db || !out) {
|
||||
return CBM_STORE_ERR;
|
||||
}
|
||||
*out = 0;
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
if (sqlite3_prepare_v2(s->db, "PRAGMA user_version;", CBM_NOT_FOUND, &stmt, NULL) !=
|
||||
SQLITE_OK) {
|
||||
return CBM_STORE_ERR;
|
||||
}
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
*out = sqlite3_column_int(stmt, 0);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
return CBM_STORE_OK;
|
||||
}
|
||||
|
||||
/* PRAGMA values cannot be bound; format the integer constant in. Writes, so
|
||||
* only call on a read-write connection (never from configure_pragmas). */
|
||||
int cbm_store_set_format_version(cbm_store_t *s, int version) {
|
||||
if (!s || !s->db) {
|
||||
return CBM_STORE_ERR;
|
||||
}
|
||||
char sql[ST_BUF_64];
|
||||
snprintf(sql, sizeof(sql), "PRAGMA user_version = %d;", version);
|
||||
return exec_sql(s, sql);
|
||||
}
|
||||
|
||||
/* ── Node neighbor names ──────────────────────────────────────── */
|
||||
|
||||
static int query_neighbor_names(sqlite3 *db, const char *sql, int64_t node_id, int limit,
|
||||
|
||||
@@ -23,6 +23,7 @@ typedef struct cbm_store cbm_store_t;
|
||||
#define CBM_STORE_OK 0
|
||||
#define CBM_STORE_ERR (-1)
|
||||
#define CBM_STORE_NOT_FOUND (-2)
|
||||
#define CBM_INDEX_FORMAT_VERSION 1
|
||||
|
||||
/* ── Data structures ────────────────────────────────────────────── */
|
||||
|
||||
@@ -92,6 +93,13 @@ void cbm_store_node_degree(cbm_store_t *s, int64_t node_id, int *in_deg, int *ou
|
||||
* Returns CBM_STORE_OK or CBM_STORE_ERR. */
|
||||
int cbm_store_list_files(cbm_store_t *s, const char *project, char ***out, int *count);
|
||||
|
||||
/* Persisted index-format identity. Bump when a change alters the QN scheme
|
||||
* or node identity of an already-written graph, so an old DB is routed
|
||||
* through the full-reindex path instead of producing a mixed graph.
|
||||
* 1 = File QNs keep the file extension (#769). */
|
||||
int cbm_store_get_format_version(cbm_store_t *s, int *out);
|
||||
int cbm_store_set_format_version(cbm_store_t *s, int version);
|
||||
|
||||
/* Get caller/callee names for a node (CALLS/HTTP_CALLS/ASYNC_CALLS edges).
|
||||
* Returns 0 on success. Caller must free each out_callers[i]/out_callees[i]
|
||||
* and the arrays themselves. */
|
||||
|
||||
@@ -89,6 +89,77 @@ TEST(fqn_compute_basic_rs) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(fqn_compute_file_sibling_distinct) {
|
||||
char *ts = cbm_pipeline_fqn_compute("proj", "app/badge/badge.component.ts", "__file__");
|
||||
char *html = cbm_pipeline_fqn_compute("proj", "app/badge/badge.component.html", "__file__");
|
||||
char *scss = cbm_pipeline_fqn_compute("proj", "app/badge/badge.component.scss", "__file__");
|
||||
ASSERT_NOT_NULL(ts);
|
||||
ASSERT_NOT_NULL(html);
|
||||
ASSERT_NOT_NULL(scss);
|
||||
ASSERT_STR_NEQ(ts, html);
|
||||
ASSERT_STR_NEQ(ts, scss);
|
||||
ASSERT_STR_NEQ(html, scss);
|
||||
free(ts);
|
||||
free(html);
|
||||
free(scss);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(fqn_compute_symbol_still_strips) {
|
||||
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "app/badge/badge.component.ts", "BadgeComponent"),
|
||||
"proj.app.badge.badge.component.BadgeComponent");
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(fqn_module_siblings_still_share) {
|
||||
ASSERT_FQN(cbm_pipeline_fqn_module("proj", "app/badge/badge.component.ts"),
|
||||
"proj.app.badge.badge.component");
|
||||
ASSERT_FQN(cbm_pipeline_fqn_module("proj", "app/badge/badge.component.html"),
|
||||
"proj.app.badge.badge.component");
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #769: exact __file__ QNs for component siblings. */
|
||||
TEST(fqn_compute_file_sibling_exact) {
|
||||
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "app/badge/badge.component.ts", "__file__"),
|
||||
"proj.app.badge.badge.component.ts.__file__");
|
||||
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "app/badge/badge.component.html", "__file__"),
|
||||
"proj.app.badge.badge.component.html.__file__");
|
||||
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "app/badge/badge.component.scss", "__file__"),
|
||||
"proj.app.badge.badge.component.scss.__file__");
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(fqn_compute_file_multi_extension) {
|
||||
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "app/badge/badge.spec.ts", "__file__"),
|
||||
"proj.app.badge.badge.spec.ts.__file__");
|
||||
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "archive.tar.gz", "__file__"),
|
||||
"proj.archive.tar.gz.__file__");
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(fqn_compute_file_no_extension) {
|
||||
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "Makefile", "__file__"),
|
||||
"proj.Makefile.__file__");
|
||||
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "LICENSE", "__file__"),
|
||||
"proj.LICENSE.__file__");
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(fqn_compute_file_dotfile) {
|
||||
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", ".gitignore", "__file__"),
|
||||
"proj..gitignore.__file__");
|
||||
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "config/.env", "__file__"),
|
||||
"proj.config..env.__file__");
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(fqn_compute_file_index_ts) {
|
||||
ASSERT_FQN(cbm_pipeline_fqn_compute("proj", "app/index.ts", "__file__"),
|
||||
"proj.app.index.ts.__file__");
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ── Nested paths ─────────────────────────────────────────────── */
|
||||
|
||||
TEST(fqn_compute_nested_two_levels) {
|
||||
@@ -595,6 +666,14 @@ SUITE(fqn) {
|
||||
RUN_TEST(fqn_file_qn_distinguishes_same_stem_header_source_issue964);
|
||||
RUN_TEST(fqn_module_qn_still_strips_extension);
|
||||
RUN_TEST(fqn_compute_basic_rs);
|
||||
RUN_TEST(fqn_compute_file_sibling_distinct);
|
||||
RUN_TEST(fqn_compute_symbol_still_strips);
|
||||
RUN_TEST(fqn_module_siblings_still_share);
|
||||
RUN_TEST(fqn_compute_file_sibling_exact);
|
||||
RUN_TEST(fqn_compute_file_multi_extension);
|
||||
RUN_TEST(fqn_compute_file_no_extension);
|
||||
RUN_TEST(fqn_compute_file_dotfile);
|
||||
RUN_TEST(fqn_compute_file_index_ts);
|
||||
|
||||
/* fqn_compute: nested paths */
|
||||
RUN_TEST(fqn_compute_nested_two_levels);
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* test_index_format.c — Guard for the persisted index-format boundary (#769).
|
||||
*
|
||||
* #1108 changed File-node QNs to keep the file extension, so an index written
|
||||
* before it holds collided File identities: badge.component.ts/.html/.scss all
|
||||
* stripped to the same stem and only one File node survived per component.
|
||||
* Refreshing such an index incrementally would mint new-format QNs only for the
|
||||
* files that happened to change, leaving the old collided node behind — a mixed
|
||||
* graph with duplicate nodes and stale edges.
|
||||
*
|
||||
* A stale-format index must therefore be routed through the existing
|
||||
* full-reindex path exactly once (which preserves ADR/project metadata per
|
||||
* #516), and the rebuilt index must not force a second rebuild on the next
|
||||
* unchanged run.
|
||||
*/
|
||||
#include "test_framework.h"
|
||||
#include "repro_harness.h" /* RProj, rh_index_files, rh_count_label, rh_cleanup */
|
||||
#include "foundation/log.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* ── Fixture: one component's siblings + an uncolliding control ─────── */
|
||||
|
||||
static const char k_badge_ts[] = "export class BadgeComponent {\n"
|
||||
" isHighlighted() { return true; } /* repro-marker */\n"
|
||||
"}\n";
|
||||
|
||||
static const char k_badge_html[] = "<div class=\"badge\">repro-marker</div>\n";
|
||||
|
||||
static const char k_badge_scss[] = ".badge { color: red; /* repro-marker */ }\n";
|
||||
|
||||
/* help.html shares no stem with anything, so it is searchable both before and
|
||||
* after the fix — a control that isolates the collision as the cause. */
|
||||
static const char k_help_html[] = "<p>repro-marker</p>\n";
|
||||
|
||||
static const RFile k_files[] = {
|
||||
{"badge/badge.component.ts", k_badge_ts},
|
||||
{"badge/badge.component.html", k_badge_html},
|
||||
{"badge/badge.component.scss", k_badge_scss},
|
||||
{"standalone/help.html", k_help_html},
|
||||
};
|
||||
static const int k_nfiles = (int)(sizeof(k_files) / sizeof(k_files[0]));
|
||||
|
||||
/* ── Log capture: the routing decision is only visible in the log ───── */
|
||||
|
||||
enum { IF_LOG_BUF = 8192 };
|
||||
static char g_log_buf[IF_LOG_BUF];
|
||||
static size_t g_log_len;
|
||||
|
||||
static void capture_sink(const char *line) {
|
||||
size_t n = strlen(line);
|
||||
if (g_log_len + n + 2 < sizeof(g_log_buf)) {
|
||||
memcpy(g_log_buf + g_log_len, line, n);
|
||||
g_log_len += n;
|
||||
g_log_buf[g_log_len++] = '\n';
|
||||
g_log_buf[g_log_len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
static void capture_reset(void) {
|
||||
g_log_len = 0;
|
||||
g_log_buf[0] = '\0';
|
||||
}
|
||||
|
||||
/* Run index_repository through the production MCP flow, capturing the log. */
|
||||
static char *index_capture(RProj *lp) {
|
||||
char args[700];
|
||||
snprintf(args, sizeof(args), "{\"repo_path\":\"%s\"}", lp->tmpdir);
|
||||
char *prior = getenv("CBM_CACHE_DIR");
|
||||
char *saved = prior ? strdup(prior) : NULL;
|
||||
cbm_setenv("CBM_CACHE_DIR", lp->cachedir, 1);
|
||||
capture_reset();
|
||||
cbm_log_set_sink_ex(capture_sink, CBM_LOG_SINK_TEE);
|
||||
char *resp = cbm_mcp_handle_tool(lp->srv, "index_repository", args);
|
||||
cbm_log_set_sink(NULL);
|
||||
if (saved) {
|
||||
cbm_setenv("CBM_CACHE_DIR", saved, 1);
|
||||
free(saved);
|
||||
} else {
|
||||
cbm_unsetenv("CBM_CACHE_DIR");
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
/* ── Test 1: one File node per file, and every file reaches search ──── */
|
||||
|
||||
TEST(index_format_siblings_distinct_and_searchable) {
|
||||
RProj lp;
|
||||
char args[700];
|
||||
cbm_store_t *store = rh_index_files(&lp, k_files, k_nfiles);
|
||||
ASSERT_NOT_NULL(store);
|
||||
|
||||
/* One File node per file on disk — not one per component stem. */
|
||||
ASSERT_EQ(rh_count_label(store, lp.project, "File"), k_nfiles);
|
||||
|
||||
/* cbm_store_list_files is the set search_code scopes grep to: a file with
|
||||
* no node carrying its exact path is never opened, and the miss is silent. */
|
||||
char **listed = NULL;
|
||||
int nlisted = 0;
|
||||
ASSERT_EQ(cbm_store_list_files(store, lp.project, &listed, &nlisted), CBM_STORE_OK);
|
||||
for (int i = 0; i < k_nfiles; i++) {
|
||||
bool found = false;
|
||||
for (int j = 0; j < nlisted; j++) {
|
||||
if (listed[j] && strcmp(listed[j], k_files[i].name) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
FAIL("indexed file missing from cbm_store_list_files");
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < nlisted; j++) {
|
||||
free(listed[j]);
|
||||
}
|
||||
free(listed);
|
||||
|
||||
/* Every sibling's marker is reachable. .scss/.html have no def nodes, so a
|
||||
* hit lands in raw_matches rather than a containing node — mode="files"
|
||||
* merges both, so assert against the file list. */
|
||||
snprintf(args, sizeof(args),
|
||||
"{\"project\":\"%s\",\"pattern\":\"repro-marker\",\"mode\":\"files\"}", lp.project);
|
||||
char *saved_dup = getenv("CBM_CACHE_DIR") ? strdup(getenv("CBM_CACHE_DIR")) : NULL;
|
||||
cbm_setenv("CBM_CACHE_DIR", lp.cachedir, 1);
|
||||
char *resp = cbm_mcp_handle_tool(lp.srv, "search_code", args);
|
||||
if (saved_dup) {
|
||||
cbm_setenv("CBM_CACHE_DIR", saved_dup, 1);
|
||||
free(saved_dup);
|
||||
} else {
|
||||
cbm_unsetenv("CBM_CACHE_DIR");
|
||||
}
|
||||
ASSERT_NOT_NULL(resp);
|
||||
for (int i = 0; i < k_nfiles; i++) {
|
||||
if (!strstr(resp, k_files[i].name)) {
|
||||
free(resp);
|
||||
FAIL("search_code did not reach an indexed sibling");
|
||||
}
|
||||
}
|
||||
free(resp);
|
||||
|
||||
rh_cleanup(&lp, store);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ── Test 2: a legacy index rebuilds once, repairs, and settles ─────── */
|
||||
|
||||
/* Rewrite the graph into the pre-#1108 shape: the three siblings collapsed onto
|
||||
* a single File node keyed by the extension-stripped QN, which is what such an
|
||||
* index actually holds on disk. */
|
||||
static int make_legacy_file_graph(cbm_store_t *store, const char *project, const char *legacy_qn) {
|
||||
if (cbm_store_delete_nodes_by_label(store, project, "File") != CBM_STORE_OK) {
|
||||
return -1;
|
||||
}
|
||||
cbm_node_t legacy = {
|
||||
.project = project,
|
||||
.label = "File",
|
||||
.name = "badge.component.html",
|
||||
.qualified_name = legacy_qn,
|
||||
.file_path = "badge/badge.component.html",
|
||||
.properties_json = "{\"extension\":\".html\"}",
|
||||
};
|
||||
return cbm_store_upsert_node(store, &legacy) > 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
TEST(index_format_legacy_index_rebuilds_and_repairs) {
|
||||
char legacy_qn[512];
|
||||
RProj lp;
|
||||
|
||||
/* Phase 1: index, then discard the query handle. */
|
||||
cbm_store_t *q0 = rh_index_files(&lp, k_files, k_nfiles);
|
||||
ASSERT_NOT_NULL(q0);
|
||||
cbm_store_close(q0);
|
||||
|
||||
/* Phase 2: fabricate a legacy graph through a writable handle. */
|
||||
cbm_store_t *w = cbm_store_open_path(lp.dbpath);
|
||||
ASSERT_NOT_NULL(w);
|
||||
ASSERT_EQ(cbm_store_adr_store(w, lp.project, "index-format-adr"), CBM_STORE_OK);
|
||||
snprintf(legacy_qn, sizeof(legacy_qn), "%s.badge.badge.component.__file__", lp.project);
|
||||
ASSERT_EQ(make_legacy_file_graph(w, lp.project, legacy_qn), 0);
|
||||
ASSERT_EQ(cbm_store_set_format_version(w, 0), CBM_STORE_OK);
|
||||
cbm_store_close(w);
|
||||
|
||||
cbm_store_t *probe = cbm_store_open_path_query(lp.dbpath);
|
||||
if (probe)
|
||||
cbm_store_close(probe);
|
||||
|
||||
/* Run 1: stale format forces the full-reindex path. */
|
||||
char *resp = index_capture(&lp);
|
||||
ASSERT_NOT_NULL(resp);
|
||||
if (!strstr(g_log_buf, "format_change_reindex")) {
|
||||
free(resp);
|
||||
FAIL("a stale-format index was not routed through the full-reindex path");
|
||||
}
|
||||
if (!strstr(resp, "\"format_migration\":true")) {
|
||||
free(resp);
|
||||
FAIL("index_repository response did not surface format_migration");
|
||||
}
|
||||
free(resp);
|
||||
|
||||
/* Phase 3: verify the rebuild through a fresh read handle. */
|
||||
cbm_store_t *r1 = cbm_store_open_path(lp.dbpath);
|
||||
ASSERT_NOT_NULL(r1);
|
||||
cbm_node_t stale = {0};
|
||||
ASSERT_EQ(cbm_store_find_node_by_qn(r1, lp.project, legacy_qn, &stale), CBM_STORE_NOT_FOUND);
|
||||
cbm_node_free_fields(&stale);
|
||||
static const char *k_exts[] = {"ts", "html", "scss"};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
char qn[512];
|
||||
snprintf(qn, sizeof(qn), "%s.badge.badge.component.%s.__file__", lp.project, k_exts[i]);
|
||||
cbm_node_t n = {0};
|
||||
ASSERT_EQ(cbm_store_find_node_by_qn(r1, lp.project, qn, &n), CBM_STORE_OK);
|
||||
cbm_node_free_fields(&n);
|
||||
}
|
||||
ASSERT_EQ(rh_count_label(r1, lp.project, "File"), k_nfiles);
|
||||
cbm_adr_t adr = {0};
|
||||
ASSERT_EQ(cbm_store_adr_get(r1, lp.project, &adr), CBM_STORE_OK);
|
||||
ASSERT_NOT_NULL(adr.content);
|
||||
ASSERT(strstr(adr.content, "index-format-adr") != NULL);
|
||||
cbm_store_adr_free(&adr);
|
||||
int fmt = -1;
|
||||
ASSERT_EQ(cbm_store_get_format_version(r1, &fmt), CBM_STORE_OK);
|
||||
ASSERT_EQ(fmt, CBM_INDEX_FORMAT_VERSION);
|
||||
cbm_store_close(r1);
|
||||
|
||||
/* Run 2: unchanged, current format — no second rebuild. */
|
||||
resp = index_capture(&lp);
|
||||
ASSERT_NOT_NULL(resp);
|
||||
if (strstr(g_log_buf, "format_change_reindex")) {
|
||||
free(resp);
|
||||
FAIL("a current-format index was rebuilt again");
|
||||
}
|
||||
if (strstr(resp, "format_migration")) {
|
||||
free(resp);
|
||||
FAIL("format_migration surfaced on an unchanged run");
|
||||
}
|
||||
free(resp);
|
||||
|
||||
/* Phase 4: final check, then clean up (rh_cleanup closes r2). */
|
||||
cbm_store_t *r2 = cbm_store_open_path(lp.dbpath);
|
||||
ASSERT_NOT_NULL(r2);
|
||||
ASSERT_EQ(rh_count_label(r2, lp.project, "File"), k_nfiles);
|
||||
rh_cleanup(&lp, r2);
|
||||
PASS();
|
||||
}
|
||||
|
||||
SUITE(index_format) {
|
||||
RUN_TEST(index_format_siblings_distinct_and_searchable);
|
||||
RUN_TEST(index_format_legacy_index_rebuilds_and_repairs);
|
||||
}
|
||||
@@ -719,6 +719,7 @@ extern void suite_pipeline(void);
|
||||
extern void suite_pipeline_semantic_manifest_repro(void);
|
||||
extern void suite_cross_repo(void);
|
||||
extern void suite_index_resilience(void);
|
||||
extern void suite_index_format(void);
|
||||
extern void suite_fqn(void);
|
||||
extern void suite_route_canon(void);
|
||||
extern void suite_path_alias(void);
|
||||
@@ -984,6 +985,7 @@ int main(int argc, char **argv) {
|
||||
/* Pipeline (M8) */
|
||||
RUN_SELECTED_SUITE(registry);
|
||||
RUN_SELECTED_SUITE(pipeline);
|
||||
RUN_SELECTED_SUITE(index_format);
|
||||
RUN_SELECTED_SUITE(pipeline_semantic_manifest_repro);
|
||||
RUN_SELECTED_SUITE(call_reference_contract);
|
||||
RUN_SELECTED_SUITE(call_reference_language_complex_contract);
|
||||
|
||||
Reference in new issue
Block a user