mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
perf(pipeline): prove an unchanged re-index on the live generation
Every index_repository call copied the whole existing database into a stage (sqlite3 backup) before routing, and the exact no-op then sealed that copy and renamed it back over the live file. On the Linux kernel index the copy alone was a 19 GB write per unchanged re-index. The copy is now deferred until routing needs it. The exact no-op is decided read-only on the live generation with the same checks the staged route applies to its copy (shallow integrity, format version, current coverage metadata and doc-link layer, byte-identical semantic manifest); the run then publishes nothing and only refreshes the artifact from the live database. Any other outcome copies and routes exactly as before, so every change -- including a same-size edit that keeps the original mtime -- still rebuilds from content hashes. cbm_pipeline_new no longer resolves the Git context the run start always re-resolved (seven git subprocesses per call). No-op index_repository, macOS, median of three runs of n=3: django 840.8 -> 539.0 ms, redis 450.7 -> 310.5 ms, Linux kernel 18449.5 -> 2405.0 ms. Responses and graphs are byte-identical to the baseline for the no-op and for modify/add/delete/rename edits. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
7729a8ee9f
commit
a372a1fa5e
4 files changed
+367
-40
No files matched your search
+119
-19
@@ -260,6 +260,15 @@ struct cbm_pipeline {
|
||||
* "invalid_existing_db" (#1864). */
|
||||
bool final_existed;
|
||||
bool existing_generation;
|
||||
/* The copy into the stage is deferred until routing needs it: an
|
||||
* unchanged repository is proven current on the live generation
|
||||
* (live_db_path, read-only) and never copies, seals or renames a
|
||||
* database. backup_succeeded records the copy once it ran; live_noop
|
||||
* marks a run that ended on the live proof with nothing staged. */
|
||||
bool stage_backup_pending;
|
||||
bool backup_succeeded;
|
||||
bool live_noop;
|
||||
const char *live_db_path; /* borrowed from cbm_pipeline_run */
|
||||
|
||||
/* 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. */
|
||||
@@ -848,7 +857,10 @@ cbm_pipeline_t *cbm_pipeline_new(const char *repo_path, const char *db_path,
|
||||
p->repo_path = strdup(repo_path);
|
||||
p->db_path = db_path ? strdup(db_path) : NULL;
|
||||
p->project_name = cbm_project_name_from_path(repo_path);
|
||||
(void)cbm_git_context_resolve(repo_path, &p->git_ctx);
|
||||
/* The Git context is resolved once, when the run starts
|
||||
* (pipeline_refresh_git_context): a context resolved here was always
|
||||
* replaced before any use, at the cost of seven git subprocesses per
|
||||
* index call. Until then the branch QN names the working tree. */
|
||||
p->branch_qn = cbm_git_context_branch_qn(p->project_name, &p->git_ctx);
|
||||
p->requested_mode = mode;
|
||||
p->mode = mode;
|
||||
@@ -2738,12 +2750,69 @@ static int capture_existing_adr(cbm_pipeline_t *p, const char *db_path) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int remove_stage_files(const char *stage_path);
|
||||
|
||||
/* Copy the live generation into this run's stage: every route except the
|
||||
* live no-op works on that copy. A failed copy behaves as the eager copy it
|
||||
* replaces did: the stage files go (the name and its lock stay this run's,
|
||||
* the rebuilt generation is renamed over it later) and the run has no
|
||||
* previous generation, so it rebuilds in full. */
|
||||
static void materialize_stage_backup(cbm_pipeline_t *p) {
|
||||
p->stage_backup_pending = false;
|
||||
p->backup_succeeded = cbm_store_backup_path(p->live_db_path, p->db_path) == CBM_STORE_OK;
|
||||
if (!p->backup_succeeded) {
|
||||
cbm_log_warn("pipeline.stage", "action", "backup_failed_full_rebuild", "path",
|
||||
p->live_db_path);
|
||||
(void)remove_stage_files(p->db_path);
|
||||
}
|
||||
p->existing_generation = p->backup_succeeded;
|
||||
}
|
||||
|
||||
/* The live generation is provably current: read-only, it passes the shallow
|
||||
* integrity and format checks the staged route applies to its copy, and the
|
||||
* exact semantic-manifest test the staged route would answer no-op with. A
|
||||
* copy of it would route to that no-op and publish the same database again,
|
||||
* so the run ends here with nothing copied, sealed or renamed. Any doubt
|
||||
* answers false and the run copies and routes exactly as before. */
|
||||
static bool live_generation_current(cbm_pipeline_t *p, const cbm_file_hash_t *baseline_manifest,
|
||||
int baseline_count) {
|
||||
struct stat db_st;
|
||||
if (!p->live_db_path || stat(p->live_db_path, &db_st) != 0) {
|
||||
return false;
|
||||
}
|
||||
cbm_store_t *store = cbm_store_open_path_query(p->live_db_path);
|
||||
if (!store) {
|
||||
return false;
|
||||
}
|
||||
int fmt = 0;
|
||||
bool current = cbm_store_check_integrity(store);
|
||||
if (current) {
|
||||
cbm_store_get_format_version(store, &fmt);
|
||||
current =
|
||||
fmt == CBM_INDEX_FORMAT_VERSION &&
|
||||
cbm_pipeline_incremental_generation_exact(p, store, baseline_manifest, baseline_count);
|
||||
}
|
||||
cbm_store_close(store);
|
||||
return current;
|
||||
}
|
||||
|
||||
/* Route an existing generation. Full rebuilds never delete the live DB here:
|
||||
* publication owns the eventual atomic replacement after every pass and
|
||||
* metadata write has succeeded. */
|
||||
static int try_incremental_or_delete_db(cbm_pipeline_t *p, cbm_file_info_t *files, int file_count,
|
||||
const cbm_file_hash_t *baseline_manifest,
|
||||
int baseline_count, bool force_full_on_mismatch) {
|
||||
if (p->stage_backup_pending) {
|
||||
if (live_generation_current(p, baseline_manifest, baseline_count)) {
|
||||
p->stage_backup_pending = false;
|
||||
p->live_noop = true;
|
||||
cbm_log_info("pipeline.route", "path", "incremental_manifest", "generation", "live");
|
||||
cbm_log_info("incremental.noop", "reason", "semantic_manifest_equal", "generation",
|
||||
"live");
|
||||
return 0;
|
||||
}
|
||||
materialize_stage_backup(p);
|
||||
}
|
||||
if (!p->existing_generation) {
|
||||
/* Nothing to be incremental against: a first index, or a
|
||||
* destination that could not be copied (already reported as
|
||||
@@ -2846,12 +2915,15 @@ static bool promote_mode_to_existing_coverage(cbm_pipeline_t *p) {
|
||||
if (!p || !p->project_name) {
|
||||
return false;
|
||||
}
|
||||
char *db_path = resolve_db_path(p);
|
||||
/* Until routing copies it, the stage is an empty placeholder: the
|
||||
* coverage the copy would carry is read from the live generation. */
|
||||
char *resolved = p->stage_backup_pending ? NULL : resolve_db_path(p);
|
||||
const char *db_path = p->stage_backup_pending ? p->live_db_path : resolved;
|
||||
if (!db_path) {
|
||||
return false;
|
||||
}
|
||||
cbm_store_t *store = cbm_store_open_path_query(db_path);
|
||||
free(db_path);
|
||||
free(resolved);
|
||||
if (!store) {
|
||||
return false;
|
||||
}
|
||||
@@ -4542,6 +4614,29 @@ static void sweep_orphan_stages(const char *final_path) {
|
||||
free(dir_path);
|
||||
}
|
||||
|
||||
/* End a run whose exact no-op was proven on the live generation. Nothing was
|
||||
* staged, so nothing is sealed or renamed and the live database stays the
|
||||
* current generation. The checks publication makes on the run's sources and
|
||||
* destination still decide success, and the artifact is refreshed from the
|
||||
* live database exactly as it would be from a republished one. */
|
||||
static int finish_live_noop(cbm_pipeline_t *p, const char *final_path) {
|
||||
cbm_userconfig_t *fresh_config = cbm_userconfig_load_with_source(p->repo_path);
|
||||
bool source_matches = cbm_pipeline_project_source_matches(p, fresh_config);
|
||||
cbm_userconfig_free(fresh_config);
|
||||
struct stat final_st;
|
||||
int rc = 0;
|
||||
if (!source_matches) {
|
||||
cbm_log_error("pipeline.err", "phase", "test_declarations_source_changed");
|
||||
rc = CBM_PIPELINE_ABORT_PRESERVE_DB;
|
||||
} else if (stat(final_path, &final_st) != 0) {
|
||||
/* The destination vanished during the run: someone else owns it. */
|
||||
rc = CBM_PIPELINE_ABORT_PRESERVE_DB;
|
||||
} else {
|
||||
rc = export_after_publish(p, final_path);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int cbm_pipeline_run(cbm_pipeline_t *p) {
|
||||
/* Per-index attribution: peaks and phase totals are about THIS index, not
|
||||
* the process history, so they start clean here. The first mark opens
|
||||
@@ -4571,21 +4666,12 @@ int cbm_pipeline_run(cbm_pipeline_t *p) {
|
||||
return CBM_NOT_FOUND;
|
||||
}
|
||||
|
||||
bool backup_succeeded = false;
|
||||
if (final_existed) {
|
||||
backup_succeeded = cbm_store_backup_path(final_path, staging_path) == CBM_STORE_OK;
|
||||
if (!backup_succeeded) {
|
||||
cbm_log_warn("pipeline.stage", "action", "backup_failed_full_rebuild", "path",
|
||||
final_path);
|
||||
/* The copy is gone but the NAME stays ours: the rebuilt
|
||||
* generation is renamed over it by the inner finalize and then
|
||||
* published from it below, so its lock is held to the end. */
|
||||
remove_stage_files(staging_path);
|
||||
}
|
||||
}
|
||||
|
||||
/* The existing generation is copied into the stage only when routing
|
||||
* needs a copy (materialize_stage_backup): an exact no-op is proven on
|
||||
* the live generation and copies nothing -- on a kernel-scale index the
|
||||
* copy alone was a 19 GB write per unchanged re-index. */
|
||||
p->final_existed = final_existed;
|
||||
p->existing_generation = final_existed && backup_succeeded;
|
||||
p->existing_generation = final_existed;
|
||||
char *configured_db_path = p->db_path;
|
||||
p->db_path = strdup(staging_path);
|
||||
if (!p->db_path) {
|
||||
@@ -4595,10 +4681,19 @@ int cbm_pipeline_run(cbm_pipeline_t *p) {
|
||||
free(final_path);
|
||||
return CBM_NOT_FOUND;
|
||||
}
|
||||
p->stage_backup_pending = final_existed;
|
||||
p->backup_succeeded = false;
|
||||
p->live_noop = false;
|
||||
p->live_db_path = final_path;
|
||||
cbm_log_debug("pipeline.setup", "step", "staged");
|
||||
int rc = cbm_pipeline_run_staged(p);
|
||||
free(p->db_path);
|
||||
p->db_path = configured_db_path;
|
||||
bool backup_succeeded = p->backup_succeeded;
|
||||
bool live_noop = p->live_noop;
|
||||
p->stage_backup_pending = false;
|
||||
p->live_noop = false;
|
||||
p->live_db_path = NULL;
|
||||
|
||||
/* Report WHY the run stopped. Everything below happens before the final
|
||||
* rename, so the live database is still the previous generation and an
|
||||
@@ -4612,12 +4707,17 @@ int cbm_pipeline_run(cbm_pipeline_t *p) {
|
||||
pipeline_release_test_config(p);
|
||||
return rc;
|
||||
}
|
||||
if (check_cancel(p)) {
|
||||
/* A cancelled run and a live no-op both end here without publishing:
|
||||
* the stage placeholder is discarded, and only the no-op goes on to
|
||||
* finish on the live generation. */
|
||||
bool cancelled = check_cancel(p);
|
||||
if (cancelled || live_noop) {
|
||||
cleanup_staging_db(staging_path);
|
||||
free(staging_path);
|
||||
rc = cancelled ? CBM_PIPELINE_ABORT_PRESERVE_DB : finish_live_noop(p, final_path);
|
||||
free(final_path);
|
||||
pipeline_release_test_config(p);
|
||||
return CBM_PIPELINE_ABORT_PRESERVE_DB;
|
||||
return rc;
|
||||
}
|
||||
if (seal_staging_db(staging_path) != 0) {
|
||||
cleanup_staging_db(staging_path);
|
||||
|
||||
@@ -3557,6 +3557,62 @@ static bool incr_doc_links_current(cbm_store_t *store, const char *project) {
|
||||
return current;
|
||||
}
|
||||
|
||||
/* The exact no-op test: the stored generation's metadata is current for this
|
||||
* mode, its doc-link layer is usable, and its recorded semantic manifest
|
||||
* equals the baseline byte for byte. Read-only. The two out flags report the
|
||||
* halves the staged route also needs for its own routing. */
|
||||
static bool incr_generation_exact(cbm_pipeline_t *p, cbm_store_t *store, const char *project,
|
||||
const cbm_file_hash_t *stored, int stored_count,
|
||||
const cbm_file_hash_t *baseline_manifest, int baseline_count,
|
||||
bool *metadata_current_out, bool *doc_links_current_out) {
|
||||
cbm_coverage_meta_t meta = {0};
|
||||
int meta_rc = cbm_store_coverage_meta_get(store, project, &meta);
|
||||
const char *mode_name = incr_mode_name(cbm_pipeline_get_mode(p));
|
||||
bool metadata_current =
|
||||
meta_rc == CBM_STORE_OK && meta.coverage_version == CBM_SEMANTIC_INDEX_VERSION &&
|
||||
meta.hash_records_complete && meta.index_mode && strcmp(meta.index_mode, mode_name) == 0;
|
||||
/* A generation whose doc-link layer is missing or failed is not
|
||||
* current even with identical inputs: index_status tells the user to
|
||||
* re-run, and the re-run has to rebuild. */
|
||||
bool doc_links_current = metadata_current && incr_doc_links_current(store, project);
|
||||
bool exact = doc_links_current && cbm_pipeline_semantic_manifests_equal(
|
||||
stored, stored_count, baseline_manifest, baseline_count);
|
||||
cbm_store_coverage_meta_clear(&meta);
|
||||
*metadata_current_out = metadata_current;
|
||||
*doc_links_current_out = doc_links_current;
|
||||
return exact;
|
||||
}
|
||||
|
||||
bool cbm_pipeline_incremental_generation_exact(cbm_pipeline_t *p, cbm_store_t *store,
|
||||
const cbm_file_hash_t *baseline_manifest,
|
||||
int baseline_count) {
|
||||
#if defined(CBM_INCREMENTAL_TEST_API) && CBM_INCREMENTAL_TEST_API
|
||||
/* A forced legacy-partial run must reach the staged route that takes
|
||||
* the one-shot flag; answering here would consume the run without it. */
|
||||
if (atomic_load(&g_incr_test_force_legacy_partial)) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
const char *project = cbm_pipeline_project_name(p);
|
||||
cbm_file_hash_t *stored = NULL;
|
||||
int stored_count = 0;
|
||||
if (!store ||
|
||||
cbm_store_get_file_hashes(store, project, &stored, &stored_count) != CBM_STORE_OK) {
|
||||
return false;
|
||||
}
|
||||
bool metadata_current = false;
|
||||
bool doc_links_current = false;
|
||||
bool exact = incr_generation_exact(p, store, project, stored, stored_count, baseline_manifest,
|
||||
baseline_count, &metadata_current, &doc_links_current);
|
||||
cbm_store_free_file_hashes(stored, stored_count);
|
||||
#if defined(CBM_INCREMENTAL_TEST_API) && CBM_INCREMENTAL_TEST_API
|
||||
if (exact) {
|
||||
incr_test_set_last_route(CBM_INCREMENTAL_ROUTE_NOOP);
|
||||
}
|
||||
#endif
|
||||
return exact;
|
||||
}
|
||||
|
||||
int cbm_pipeline_run_incremental(cbm_pipeline_t *p, const char *db_path, cbm_file_info_t *files,
|
||||
int file_count, const cbm_file_hash_t *baseline_manifest,
|
||||
int baseline_count, bool force_full_on_mismatch) {
|
||||
@@ -3602,21 +3658,11 @@ int cbm_pipeline_run_incremental(cbm_pipeline_t *p, const char *db_path, cbm_fil
|
||||
* rebuilds the complete graph. This avoids partial LSP/package/config
|
||||
* convergence gaps while retaining the common unchanged-repo fast path. */
|
||||
if (!force_legacy_partial) {
|
||||
cbm_coverage_meta_t meta = {0};
|
||||
int meta_rc = cbm_store_coverage_meta_get(store, project, &meta);
|
||||
const char *mode_name = incr_mode_name(cbm_pipeline_get_mode(p));
|
||||
bool metadata_current = meta_rc == CBM_STORE_OK &&
|
||||
meta.coverage_version == CBM_SEMANTIC_INDEX_VERSION &&
|
||||
meta.hash_records_complete && meta.index_mode &&
|
||||
strcmp(meta.index_mode, mode_name) == 0;
|
||||
/* A generation whose doc-link layer is missing or failed is not
|
||||
* current even with identical inputs: index_status tells the user to
|
||||
* re-run, and the re-run has to rebuild. */
|
||||
bool doc_links_current = metadata_current && incr_doc_links_current(store, project);
|
||||
bool exact = doc_links_current &&
|
||||
cbm_pipeline_semantic_manifests_equal(stored, stored_count, baseline_manifest,
|
||||
baseline_count);
|
||||
cbm_store_coverage_meta_clear(&meta);
|
||||
bool metadata_current = false;
|
||||
bool doc_links_current = false;
|
||||
bool exact =
|
||||
incr_generation_exact(p, store, project, stored, stored_count, baseline_manifest,
|
||||
baseline_count, &metadata_current, &doc_links_current);
|
||||
if (exact) {
|
||||
cbm_store_free_file_hashes(stored, stored_count);
|
||||
cbm_store_close(store);
|
||||
|
||||
@@ -1382,6 +1382,13 @@ int cbm_pipeline_run_incremental(cbm_pipeline_t *p, const char *db_path, cbm_fil
|
||||
int file_count, const cbm_file_hash_t *baseline_manifest,
|
||||
int baseline_count, bool force_full_on_mismatch);
|
||||
|
||||
/* The exact no-op test cbm_pipeline_run_incremental routes by, read-only on
|
||||
* an already opened generation: true when that generation is current for the
|
||||
* pipeline's mode and records exactly the baseline manifest. */
|
||||
bool cbm_pipeline_incremental_generation_exact(cbm_pipeline_t *p, cbm_store_t *store,
|
||||
const cbm_file_hash_t *baseline_manifest,
|
||||
int baseline_count);
|
||||
|
||||
/* Exact semantic inputs for no-op/forced-full routing. The manifest contains
|
||||
* every discovered source plus repository controls actually consumed by
|
||||
* discovery, package mapping, path aliases, and extension overrides. */
|
||||
|
||||
+180
-6
@@ -5759,6 +5759,177 @@ TEST(pipeline_concurrent_sweep_must_not_remove_inflight_stage) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
static int live_noop_run(const char *repo, const char *db_path, cbm_incremental_route_t *route) {
|
||||
cbm_pipeline_incremental_test_reset_faults();
|
||||
*route = CBM_INCREMENTAL_ROUTE_NONE;
|
||||
cbm_pipeline_t *p = cbm_pipeline_new(repo, db_path, CBM_MODE_FULL);
|
||||
if (!p) {
|
||||
return CBM_NOT_FOUND;
|
||||
}
|
||||
int rc = cbm_pipeline_run(p);
|
||||
*route = cbm_pipeline_incremental_test_last_route();
|
||||
cbm_pipeline_free(p);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* An unchanged re-index proves the exact no-op on the LIVE generation. It
|
||||
* never copies the database into a stage, seals the copy and renames it back
|
||||
* (on a kernel-scale index that copy alone was a 19 GB write per call): the
|
||||
* live file keeps its identity, size and mtime, no stage artifact remains,
|
||||
* and the graph still serves. */
|
||||
TEST(pipeline_unchanged_reindex_leaves_live_generation_untouched) {
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof(tmp), "/tmp/cbm_live_noop_XXXXXX");
|
||||
ASSERT_NOT_NULL(cbm_mkdtemp(tmp));
|
||||
write_temp_file(tmp, "generation.py", "def StableGeneration():\n return 1\n");
|
||||
char db_path[512];
|
||||
snprintf(db_path, sizeof(db_path), "%s/generation.db", tmp);
|
||||
char *project = cbm_project_name_from_path(tmp);
|
||||
ASSERT_NOT_NULL(project);
|
||||
|
||||
cbm_incremental_route_t first_route;
|
||||
int first_rc = live_noop_run(tmp, db_path, &first_route);
|
||||
cbm_path_info_t before = {0};
|
||||
struct stat before_st;
|
||||
bool before_ok =
|
||||
cbm_path_info_utf8(db_path, &before) == CBM_PATH_INFO_OK && stat(db_path, &before_st) == 0;
|
||||
|
||||
cbm_incremental_route_t noop_route;
|
||||
int noop_rc = live_noop_run(tmp, db_path, &noop_route);
|
||||
cbm_path_info_t after = {0};
|
||||
struct stat after_st;
|
||||
bool after_ok =
|
||||
cbm_path_info_utf8(db_path, &after) == CBM_PATH_INFO_OK && stat(db_path, &after_st) == 0;
|
||||
int defined_count = -1;
|
||||
int absent_count = -1;
|
||||
observe_named_generation(db_path, project, "StableGeneration", "NeverDefined", &defined_count,
|
||||
&absent_count);
|
||||
int stage_count = count_generation_stage_artifacts(tmp, "generation.db");
|
||||
cbm_pipeline_incremental_test_reset_faults();
|
||||
free(project);
|
||||
th_rmtree(tmp);
|
||||
|
||||
ASSERT_EQ(first_rc, 0);
|
||||
ASSERT_TRUE(before_ok);
|
||||
ASSERT_EQ(noop_rc, 0);
|
||||
ASSERT_EQ(noop_route, CBM_INCREMENTAL_ROUTE_NOOP);
|
||||
ASSERT_TRUE(after_ok);
|
||||
ASSERT_TRUE(after_st.st_ino == before_st.st_ino);
|
||||
ASSERT_TRUE(after.mtime_ns == before.mtime_ns);
|
||||
ASSERT_TRUE(after.size == before.size);
|
||||
ASSERT_EQ(stage_count, 0);
|
||||
ASSERT_EQ(defined_count, 1);
|
||||
ASSERT_EQ(absent_count, 0);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* The live proof answers only for an exact manifest. Every real change -- a
|
||||
* same-size edit that keeps the original mtime, an addition, a deletion and
|
||||
* a rename -- still routes past it into a rebuild that the graph shows, and
|
||||
* only the unchanged re-index after them takes the no-op. */
|
||||
TEST(pipeline_live_noop_proof_never_hides_a_change) {
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof(tmp), "/tmp/cbm_live_noop_change_XXXXXX");
|
||||
ASSERT_NOT_NULL(cbm_mkdtemp(tmp));
|
||||
write_temp_file(tmp, "alpha.py", "def AlphaDefined():\n return 1\n");
|
||||
write_temp_file(tmp, "beta.py", "def BetaDefined():\n return 2\n");
|
||||
char db_path[512];
|
||||
char alpha_path[512];
|
||||
char beta_path[512];
|
||||
char gamma_path[512];
|
||||
char delta_path[512];
|
||||
snprintf(db_path, sizeof(db_path), "%s/generation.db", tmp);
|
||||
snprintf(alpha_path, sizeof(alpha_path), "%s/alpha.py", tmp);
|
||||
snprintf(beta_path, sizeof(beta_path), "%s/beta.py", tmp);
|
||||
snprintf(gamma_path, sizeof(gamma_path), "%s/gamma.py", tmp);
|
||||
snprintf(delta_path, sizeof(delta_path), "%s/delta.py", tmp);
|
||||
char *project = cbm_project_name_from_path(tmp);
|
||||
ASSERT_NOT_NULL(project);
|
||||
|
||||
cbm_incremental_route_t initial_route;
|
||||
int initial_rc = live_noop_run(tmp, db_path, &initial_route);
|
||||
|
||||
/* Same size, original mtime restored: only the bytes differ. */
|
||||
cbm_path_info_t alpha_info = {0};
|
||||
bool alpha_stat_ok = cbm_path_info_utf8(alpha_path, &alpha_info) == CBM_PATH_INFO_OK;
|
||||
write_temp_file(tmp, "alpha.py", "def AlphbDefined():\n return 1\n");
|
||||
int mtime_rc = pipeline_test_set_mtime(alpha_path, (time_t)(alpha_info.mtime_ns / 1000000000),
|
||||
(long)(alpha_info.mtime_ns % 1000000000));
|
||||
cbm_path_info_t edited_info = {0};
|
||||
bool edited_same_stat = cbm_path_info_utf8(alpha_path, &edited_info) == CBM_PATH_INFO_OK &&
|
||||
edited_info.size == alpha_info.size &&
|
||||
edited_info.mtime_ns == alpha_info.mtime_ns;
|
||||
cbm_incremental_route_t modify_route;
|
||||
int modify_rc = live_noop_run(tmp, db_path, &modify_route);
|
||||
int old_alpha = -1;
|
||||
int new_alpha = -1;
|
||||
observe_named_generation(db_path, project, "AlphaDefined", "AlphbDefined", &old_alpha,
|
||||
&new_alpha);
|
||||
|
||||
write_temp_file(tmp, "gamma.py", "def GammaDefined():\n return 3\n");
|
||||
cbm_incremental_route_t add_route;
|
||||
int add_rc = live_noop_run(tmp, db_path, &add_route);
|
||||
int gamma_added = -1;
|
||||
int unused = -1;
|
||||
observe_named_generation(db_path, project, "GammaDefined", "NeverDefined", &gamma_added,
|
||||
&unused);
|
||||
|
||||
int unlink_rc = cbm_unlink(beta_path);
|
||||
cbm_incremental_route_t delete_route;
|
||||
int delete_rc = live_noop_run(tmp, db_path, &delete_route);
|
||||
int beta_left = -1;
|
||||
observe_named_generation(db_path, project, "BetaDefined", "NeverDefined", &beta_left, &unused);
|
||||
|
||||
int rename_rc = cbm_rename_replace(gamma_path, delta_path);
|
||||
cbm_incremental_route_t rename_route;
|
||||
int renamed_rc = live_noop_run(tmp, db_path, &rename_route);
|
||||
int gamma_renamed = -1;
|
||||
observe_named_generation(db_path, project, "GammaDefined", "NeverDefined", &gamma_renamed,
|
||||
&unused);
|
||||
int old_path_hash = CBM_STORE_OK;
|
||||
int new_path_hash = CBM_STORE_NOT_FOUND;
|
||||
cbm_store_t *store = cbm_store_open_path_query(db_path);
|
||||
if (store) {
|
||||
cbm_file_hash_t hash = {0};
|
||||
old_path_hash = cbm_store_get_file_hash(store, project, "gamma.py", &hash);
|
||||
cbm_store_clear_file_hash(&hash);
|
||||
new_path_hash = cbm_store_get_file_hash(store, project, "delta.py", &hash);
|
||||
cbm_store_clear_file_hash(&hash);
|
||||
cbm_store_close(store);
|
||||
}
|
||||
|
||||
cbm_incremental_route_t final_route;
|
||||
int final_rc = live_noop_run(tmp, db_path, &final_route);
|
||||
cbm_pipeline_incremental_test_reset_faults();
|
||||
free(project);
|
||||
th_rmtree(tmp);
|
||||
|
||||
ASSERT_EQ(initial_rc, 0);
|
||||
ASSERT_TRUE(alpha_stat_ok);
|
||||
ASSERT_EQ(mtime_rc, 0);
|
||||
ASSERT_TRUE(edited_same_stat);
|
||||
ASSERT_EQ(modify_rc, 0);
|
||||
ASSERT_TRUE(modify_route != CBM_INCREMENTAL_ROUTE_NOOP);
|
||||
ASSERT_EQ(old_alpha, 0);
|
||||
ASSERT_EQ(new_alpha, 1);
|
||||
ASSERT_EQ(add_rc, 0);
|
||||
ASSERT_TRUE(add_route != CBM_INCREMENTAL_ROUTE_NOOP);
|
||||
ASSERT_EQ(gamma_added, 1);
|
||||
ASSERT_EQ(unlink_rc, 0);
|
||||
ASSERT_EQ(delete_rc, 0);
|
||||
ASSERT_TRUE(delete_route != CBM_INCREMENTAL_ROUTE_NOOP);
|
||||
ASSERT_EQ(beta_left, 0);
|
||||
ASSERT_EQ(rename_rc, 0);
|
||||
ASSERT_EQ(renamed_rc, 0);
|
||||
ASSERT_TRUE(rename_route != CBM_INCREMENTAL_ROUTE_NOOP);
|
||||
ASSERT_EQ(gamma_renamed, 1);
|
||||
ASSERT_EQ(old_path_hash, CBM_STORE_NOT_FOUND);
|
||||
ASSERT_EQ(new_path_hash, CBM_STORE_OK);
|
||||
ASSERT_EQ(final_rc, 0);
|
||||
ASSERT_EQ(final_route, CBM_INCREMENTAL_ROUTE_NOOP);
|
||||
PASS();
|
||||
}
|
||||
|
||||
static char g_route_log_capture[8192];
|
||||
static atomic_flag g_route_log_spin = ATOMIC_FLAG_INIT;
|
||||
|
||||
@@ -6368,15 +6539,15 @@ TEST(pipeline_existing_artifact_refreshes_after_default_forced_full_reindex) {
|
||||
ASSERT_EQ(reindex_observer.rename_calls, 1);
|
||||
ASSERT_EQ(reindex_observer.exports_before_publish, 0);
|
||||
ASSERT_EQ(reindex_observer.export_count, 1);
|
||||
/* The no-ops are proven on the live generation and publish nothing; the
|
||||
* artifact is still refreshed, from that already published database. */
|
||||
ASSERT_EQ(explicit_rc, 0);
|
||||
ASSERT_EQ(explicit_route, CBM_INCREMENTAL_ROUTE_NOOP);
|
||||
ASSERT_EQ(explicit_observer.rename_calls, 1);
|
||||
ASSERT_EQ(explicit_observer.exports_before_publish, 0);
|
||||
ASSERT_EQ(explicit_observer.rename_calls, 0);
|
||||
ASSERT_EQ(explicit_observer.export_count, 1);
|
||||
ASSERT_EQ(unchanged_rc, 0);
|
||||
ASSERT_EQ(unchanged_route, CBM_INCREMENTAL_ROUTE_NOOP);
|
||||
ASSERT_EQ(observer.rename_calls, 1);
|
||||
ASSERT_EQ(observer.exports_before_publish, 0);
|
||||
ASSERT_EQ(observer.rename_calls, 0);
|
||||
ASSERT_EQ(observer.export_count, 1);
|
||||
ASSERT_TRUE(journal_ok);
|
||||
ASSERT_STR_EQ(journal_mode, "delete");
|
||||
@@ -18853,8 +19024,9 @@ TEST(incremental_downgrade_preserves_scope_and_artifact_across_change_noop_delet
|
||||
ASSERT_EQ(noop.rc, 0);
|
||||
ASSERT_EQ(noop.route, CBM_INCREMENTAL_ROUTE_NOOP);
|
||||
ASSERT_TRUE(noop.tools_excluded);
|
||||
ASSERT_EQ(noop.publish.rename_calls, 1);
|
||||
ASSERT_EQ(noop.publish.exports_before_publish, 0);
|
||||
/* Proven on the live generation: nothing is published, the artifact is
|
||||
* still refreshed from the live database. */
|
||||
ASSERT_EQ(noop.publish.rename_calls, 0);
|
||||
ASSERT_EQ(noop.publish.export_count, 1);
|
||||
|
||||
ASSERT_EQ(deleted.rc, 0);
|
||||
@@ -23352,6 +23524,8 @@ SUITE(pipeline_semantic_manifest_repro) {
|
||||
RUN_TEST(pipeline_sweep_reclaims_orphans_of_other_projects_and_keeps_their_live_stages);
|
||||
RUN_TEST(pipeline_sweep_removes_dead_writer_stage_keeps_locked_stage);
|
||||
RUN_TEST(pipeline_concurrent_sweep_must_not_remove_inflight_stage);
|
||||
RUN_TEST(pipeline_unchanged_reindex_leaves_live_generation_untouched);
|
||||
RUN_TEST(pipeline_live_noop_proof_never_hides_a_change);
|
||||
RUN_TEST(pipeline_fresh_index_never_reports_invalid_existing_db);
|
||||
RUN_TEST(pipeline_source_mutation_before_publication_preserves_previous_generation);
|
||||
RUN_TEST(pipeline_source_addition_before_publication_preserves_previous_generation);
|
||||
|
||||
Reference in new issue
Block a user