Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/pipeline/pipeline.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@ const char *cbm_pipeline_project_name(const cbm_pipeline_t *p) {
return p ? p->project_name : NULL;
}

bool cbm_pipeline_persistence(const cbm_pipeline_t *p) {
return p && p->persistence;
}

const char *cbm_pipeline_repo_path(const cbm_pipeline_t *p) {
return p ? p->repo_path : NULL;
}
Expand Down
3 changes: 3 additions & 0 deletions src/pipeline/pipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ void cbm_pipeline_bind_cancel_flag(cbm_pipeline_t *p, atomic_int *cancelled);
* owned by the pipeline. Valid until cbm_pipeline_free(). */
const char *cbm_pipeline_project_name(const cbm_pipeline_t *p);

/* Return whether persistent artifact export is enabled for this pipeline. */
bool cbm_pipeline_persistence(const cbm_pipeline_t *p);

/* Override the derived project name with a sanitized user-provided label. */
bool cbm_pipeline_set_project_name(cbm_pipeline_t *p, const char *name);

Expand Down
34 changes: 30 additions & 4 deletions src/pipeline/pipeline_incremental.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

enum { INCR_RING_BUF = 4, INCR_RING_MASK = 3, INCR_TS_BUF = 24 };
#include "pipeline/pipeline.h"
#include "pipeline/artifact.h"
#include <stdio.h>
#include <time.h>
#include "pipeline/pipeline_internal.h"
Expand Down Expand Up @@ -640,8 +641,8 @@ static void run_postpasses(cbm_pipeline_ctx_t *ctx, cbm_file_info_t *changed_fil
static int dump_and_persist(cbm_gbuf_t *gbuf, const char *db_path, const char *project,
cbm_file_info_t *files, int file_count,
const cbm_file_hash_t *mode_skipped, int mode_skipped_count,
const cbm_coverage_row_t *cov, int cov_count,
const cbm_coverage_meta_t *meta_template) {
const char *repo_path, bool persistence, const cbm_coverage_row_t *cov,
int cov_count, const cbm_coverage_meta_t *meta_template) {
struct timespec t;
cbm_clock_gettime(CLOCK_MONOTONIC, &t);

Expand Down Expand Up @@ -701,6 +702,20 @@ static int dump_and_persist(cbm_gbuf_t *gbuf, const char *db_path, const char *p
}
}
cbm_store_close(hash_store);

/* Create a requested artifact, or cheaply refresh one that already exists.
* Runs after the store is closed so the export reads a settled DB file. */
bool artifact_exists = repo_path && cbm_artifact_exists(repo_path);
if (repo_path && (persistence || artifact_exists)) {
int arc = cbm_artifact_export(db_path, repo_path, project,
artifact_exists ? CBM_ARTIFACT_FAST : CBM_ARTIFACT_BEST);
if (arc != 0) {
const char *err = cbm_artifact_export_last_error();
cbm_log_error("pipeline.err", "phase", "artifact_export", "err", err ? err : "unknown");
return arc;
}
}

return rc;
}

Expand Down Expand Up @@ -749,12 +764,22 @@ int cbm_pipeline_run_incremental(cbm_pipeline_t *p, const char *db_path, cbm_fil
* that were already preserved by an earlier run) remain intact. */
if (n_changed == 0 && deleted_count == 0) {
cbm_log_info("incremental.noop", "reason", "no_changes");
int arc = 0;
const char *repo_path = cbm_pipeline_repo_path(p);
if (cbm_pipeline_persistence(p) && repo_path && !cbm_artifact_exists(repo_path)) {
arc = cbm_artifact_export(db_path, repo_path, project, CBM_ARTIFACT_BEST);
if (arc != 0) {
const char *err = cbm_artifact_export_last_error();
cbm_log_error("pipeline.err", "phase", "artifact_export", "err",
err ? err : "unknown");
}
}
free(is_changed);
free(deleted);
free_mode_skipped(mode_skipped, mode_skipped_count);
cbm_store_free_file_hashes(stored, stored_count);
cbm_store_close(store);
return 0;
return arc;
}

cbm_store_free_file_hashes(stored, stored_count);
Expand Down Expand Up @@ -1007,7 +1032,8 @@ int cbm_pipeline_run_incremental(cbm_pipeline_t *p, const char *db_path, cbm_fil
.coverage_version = 1,
};
int persist_rc = dump_and_persist(existing, db_path, project, files, file_count, mode_skipped,
mode_skipped_count, cov, cov_n, &coverage_meta);
mode_skipped_count, cbm_pipeline_repo_path(p),
cbm_pipeline_persistence(p), cov, cov_n, &coverage_meta);
free(cov);
cbm_store_free_coverage(old_cov, old_cov_count);
free_mode_skipped(mode_skipped, mode_skipped_count);
Expand Down
59 changes: 59 additions & 0 deletions tests/test_pipeline.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "foundation/mem.h" // cbm_mem_init/budget (back-pressure futile-nap test)
#include "pipeline/pipeline.h"
#include "pipeline/pipeline_internal.h"
#include "pipeline/artifact.h"
#include "store/store.h"
#include "git/git_context.h"
#include "foundation/dump_verify.h"
Expand Down Expand Up @@ -5761,6 +5762,62 @@ TEST(incremental_full_then_noop) {
PASS();
}

TEST(incremental_persistence_creates_first_artifact) {
/* Populate the local DB without persistence, then change a file and
* enable persistence on the incremental path. The first artifact must be
* created here rather than waiting for a later reindex. */
if (setup_incremental_repo() != 0) {
FAIL("setup failed");
}

cbm_pipeline_t *p = cbm_pipeline_new(g_incr_tmpdir, g_incr_dbpath, CBM_MODE_FULL);
ASSERT_NOT_NULL(p);
ASSERT_EQ(cbm_pipeline_run(p), 0);
cbm_pipeline_free(p);

ASSERT_FALSE(cbm_artifact_exists(g_incr_tmpdir));

char path[512];
snprintf(path, sizeof(path), "%s/helper.go", g_incr_tmpdir);
ASSERT_EQ(th_append_file(path, "\n// persistence regression marker\n"), 0);

p = cbm_pipeline_new(g_incr_tmpdir, g_incr_dbpath, CBM_MODE_FULL);
ASSERT_NOT_NULL(p);
cbm_pipeline_set_persistence(p, true);
ASSERT_EQ(cbm_pipeline_run(p), 0);
ASSERT_TRUE(cbm_artifact_exists(g_incr_tmpdir));
cbm_pipeline_free(p);

cleanup_incremental_repo();
PASS();
}

TEST(incremental_noop_persistence_creates_first_artifact) {
/* Populate the local DB without persistence, then request persistence
* without changing any files. The incremental no-op path must still
* create the missing artifact. */
if (setup_incremental_repo() != 0) {
FAIL("setup failed");
}

cbm_pipeline_t *p = cbm_pipeline_new(g_incr_tmpdir, g_incr_dbpath, CBM_MODE_FULL);
ASSERT_NOT_NULL(p);
ASSERT_EQ(cbm_pipeline_run(p), 0);
cbm_pipeline_free(p);

ASSERT_FALSE(cbm_artifact_exists(g_incr_tmpdir));

p = cbm_pipeline_new(g_incr_tmpdir, g_incr_dbpath, CBM_MODE_FULL);
ASSERT_NOT_NULL(p);
cbm_pipeline_set_persistence(p, true);
ASSERT_EQ(cbm_pipeline_run(p), 0);
ASSERT_TRUE(cbm_artifact_exists(g_incr_tmpdir));
cbm_pipeline_free(p);

cleanup_incremental_repo();
PASS();
}

TEST(incremental_detects_changed_file) {
/* Full index, modify one file, re-index → changed file re-parsed */
if (setup_incremental_repo() != 0) {
Expand Down Expand Up @@ -7506,6 +7563,8 @@ SUITE(pipeline) {
RUN_TEST(pipeline_fastapi_depends_edges);
/* Incremental */
RUN_TEST(incremental_full_then_noop);
RUN_TEST(incremental_persistence_creates_first_artifact);
RUN_TEST(incremental_noop_persistence_creates_first_artifact);
RUN_TEST(incremental_detects_changed_file);
RUN_TEST(incremental_aborts_when_previous_coverage_is_unreadable);
RUN_TEST(incremental_detects_deleted_file);
Expand Down
Loading