mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
One request pins the change (merge base .. HEAD), builds the graph and the runnable test registry of exactly the HEAD snapshot, seeds the graph from the change, walks what depends on it and answers which tests to run (cbm.test_impact). Missing or doubtful evidence never narrows: it becomes a run-all answer that names why. Snapshot (classified ledger -> frozen graph -> registry): - one shared language classifier for discovery and pinned blobs (raw bytes on every platform); the classified ledger feeds the frozen build, which indexes exactly the listed files and records partial parses as per-file PARSE_GAP instead of refusing the graph; - the pinned tree skips symlinks and submodule links (counted), as discovery never indexes them; - the test model scopes uncertainty to the suites of its file (they run whole), reads line splices where they occur, treats a mid-file `#ifndef X` + `#define X` as an ordinary condition, and no longer reads a plain identifier named like a macro as an invocation. On this repository the registry is now narrowable and matches a text census exactly (8,365 cases, 141 runner suites). Seeding and reach (port of the measured replay, rule B): - hunk -> overlapping definitions, comment-only hunks dropped, pure deletions -> enclosing definition, no-definition hunks -> whole file, deleted names found by text, header changes propagated through the include closure and the headers' macros (pastes included), file-level escalation for unclassifiable header lines; - inbound fixpoint walk over CALLS, ASYNC_CALLS, USAGE, CALL_REFERENCE, READS (+ routes); cases, suites and the runner's entry points are sinks; a reached runner main runs everything (RUNNER_REACHED); - a parse gap whose text names a reached definition reaches the definition around it (or its file); - macro-registered tests run their suites whole; cases and suites map to graph nodes by their start line. The line patterns reproduce the reference regexes exactly on 1,383,850 lines of this repository and 37,167,926 kernel lines; on 24 replayed changes the engine selects at least what the reference selected (13 identical, the rest only conservative supersets on the same graph). Also: - configured test declarations that cannot be mapped degrade their file (no configured roles, a test_declarations diagnostic) instead of aborting the whole index; - the selector keeps other runners' suites out of the selection, and the result codec judges the model by narrowability like the selector; - a change to .codebase-memory.json runs everything (built-in rule); - .codebase-memory.json with the c-cbm preset and this repository's rules. Gate fixes (suites, lint-ci, Windows cross-compile): - the request runs on its own thread with a default-size stack (daemon tool calls run on 256 KiB threads, where the in-process build died); - a request-supplied config_path is no longer followed by an unlink of an uninitialized path buffer; - the inventory's frame-local reader no longer stays reachable from the caller's source after the filter call; - FILE_ID_INFO needs _WIN32_WINNT >= 0x0602; the pinned-tree header pins it before any system header (toolchains defaulting lower failed); - memory-core ratchet: the win_utf8 converters gain core-backed `_in(cls, ...)` forms over one implementation (the legacy names keep the C library for unmigrated callers), the Git facts module uses them, and the Windows spawn path releases everything through one failure exit; pipeline, extraction and config allocations that this work touched moved to the core in matched pairs; baselines tightened; - the branch's C files formatted with the project's clang-format. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
638 lines
36 KiB
C
638 lines
36 KiB
C
#include <foundation/arena.h>
|
|
#include "test_framework.h"
|
|
#include "test_helpers.h"
|
|
#include <pipeline/pipeline.h>
|
|
#include <pipeline/pipeline_internal.h>
|
|
#include <store/store.h>
|
|
#include <discover/test_conventions.h>
|
|
#include <pipeline/pass_lsp_cross.h>
|
|
#include "result_spill.h"
|
|
#include "yyjson/yyjson.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#if !defined(CBM_INCREMENTAL_TEST_API) || !CBM_INCREMENTAL_TEST_API || \
|
|
!defined(CBM_ENABLE_TEST_SEAMS) || !CBM_ENABLE_TEST_SEAMS
|
|
#error "pipeline_conventions requires the canonical test runner's existing test API defines"
|
|
#endif
|
|
|
|
#define PC_PROJECT "pipelineconventions"
|
|
#define PC_REC(role_, macro_, arg_) \
|
|
"{\"language\":\"c\",\"role\":\"" role_ "\",\"define_macro\":\"" macro_ "\",\"name_args\":[" arg_ "]}"
|
|
#define PC_CONFIG(arg_) "{\"test_impact\":{\"version\":1,\"tests\":{\"conventions\":[" \
|
|
PC_REC("case", "CHECK", arg_) "," PC_REC("suite", "GROUP", "0") "]}}}"
|
|
static const char pc_config0[] = PC_CONFIG("0");
|
|
static const char pc_config1[] = PC_CONFIG("1");
|
|
static const char pc_empty_config[] = "{\"test_impact\":{\"version\":1,\"tests\":{}}}";
|
|
static const char pc_cpp_config[] = "{\"test_impact\":{\"version\":1,\"tests\":{\"conventions\":["
|
|
"{\"language\":\"cpp\",\"role\":\"case\",\"define_macro\":\"CHECK\",\"name_args\":[0]}]}}}";
|
|
static const char pc_legacy_source[] =
|
|
"void sink(void) {}\nvoid preserved(void) { sink(); }\n";
|
|
static const char pc_source[] =
|
|
"void case_sink(void) {}\nvoid suite_sink(void) {}\n"
|
|
"CHECK(before, after) { case_sink(); }\nGROUP(group) { suite_sink(); }\n";
|
|
/* Retain the old declarations: a graph-size drop must not satisfy the abort oracle. */
|
|
static const char pc_bad_source[] =
|
|
"void sink(void) {}\nvoid preserved(void) { sink(); }\nCHECK() { sink(); }\n";
|
|
static const char pc_cpp_source[] =
|
|
"#include \"engine.h\"\nCHECK(alpha) { Engine engine; engine.work(); }\n";
|
|
static int pc_init_status;
|
|
|
|
typedef struct { char home[1024], repo[1024], db[1024], config[1024], saved[1024]; } pc_fixture_t;
|
|
typedef struct { char generation[128]; } pc_snapshot_t;
|
|
typedef struct { const char *name; bool present; char value[4096]; } pc_env_value_t;
|
|
typedef struct { pc_env_value_t values[4]; bool saved; } pc_env_t;
|
|
|
|
static bool pc_path(char *out, size_t cap, const char *base, const char *leaf) {
|
|
int n = snprintf(out, cap, "%s/%s", base, leaf);
|
|
return n > 0 && (size_t)n < cap;
|
|
}
|
|
static bool pc_write(const char *path, const char *text) {
|
|
FILE *f = cbm_fopen(path, "wb");
|
|
if (!f) return false;
|
|
size_t size = strlen(text);
|
|
bool ok = fwrite(text, 1, size, f) == size;
|
|
return fclose(f) == 0 && ok;
|
|
}
|
|
static bool pc_source_write(pc_fixture_t *f, const char *leaf, const char *text) {
|
|
char path[1024];
|
|
return pc_path(path, sizeof(path), f->repo, leaf) && pc_write(path, text);
|
|
}
|
|
static bool pc_copy(const char *from, const char *to) {
|
|
FILE *a = cbm_fopen(from, "rb"), *b = cbm_fopen(to, "wb");
|
|
bool ok = a && b;
|
|
unsigned char bytes[4096];
|
|
while (ok) {
|
|
size_t n = fread(bytes, 1, sizeof(bytes), a);
|
|
if (n && fwrite(bytes, 1, n, b) != n) { ok = false; break; }
|
|
if (n < sizeof(bytes)) { ok = !ferror(a); break; }
|
|
}
|
|
if (a && fclose(a) != 0) ok = false;
|
|
if (b && fclose(b) != 0) ok = false;
|
|
return ok;
|
|
}
|
|
static bool pc_files_equal(const char *left, const char *right) {
|
|
FILE *a = cbm_fopen(left, "rb"), *b = cbm_fopen(right, "rb");
|
|
bool ok = a && b;
|
|
unsigned char x[4096], y[4096];
|
|
while (ok) {
|
|
size_t nx = fread(x, 1, sizeof(x), a), ny = fread(y, 1, sizeof(y), b);
|
|
if (nx != ny || memcmp(x, y, nx) != 0) { ok = false; break; }
|
|
if (nx < sizeof(x)) { ok = !ferror(a) && !ferror(b); break; }
|
|
}
|
|
if (a && fclose(a) != 0) ok = false;
|
|
if (b && fclose(b) != 0) ok = false;
|
|
return ok;
|
|
}
|
|
static bool pc_fixture_open(pc_fixture_t *f, bool parallel) {
|
|
memset(f, 0, sizeof(*f));
|
|
const char *home = th_mktempdir("cbm-pipeline-conventions");
|
|
if (!home || strlen(home) >= sizeof(f->home)) return false;
|
|
memcpy(f->home, home, strlen(home) + 1);
|
|
bool ok = pc_path(f->repo, sizeof(f->repo), home, "repo") &&
|
|
pc_path(f->db, sizeof(f->db), home, "published.db") &&
|
|
pc_path(f->saved, sizeof(f->saved), home, "saved.db") &&
|
|
pc_path(f->config, sizeof(f->config), f->repo, ".codebase-memory.json") &&
|
|
th_mkdir_p(f->repo) == 0 &&
|
|
pc_source_write(f, "aux.c", "void auxiliary(void) {}\n");
|
|
/* The existing full driver needs >50 filtered files as well as >1 worker. */
|
|
for (int i = 0; ok && parallel && i < 51; i++) {
|
|
char leaf[40], body[80];
|
|
int a = snprintf(leaf, sizeof(leaf), "filler_%02d.c", i);
|
|
int b = snprintf(body, sizeof(body), "void filler_%02d(void) {}\n", i);
|
|
ok = a > 0 && (size_t)a < sizeof(leaf) && b > 0 && (size_t)b < sizeof(body) &&
|
|
pc_source_write(f, leaf, body);
|
|
}
|
|
if (!ok) { (void)th_rmtree(f->home); f->home[0] = '\0'; }
|
|
return ok;
|
|
}
|
|
static bool pc_env_restore(pc_env_t *env) {
|
|
bool ok = true;
|
|
if (!env->saved) return true;
|
|
for (size_t i = 0; i < 4; i++) {
|
|
pc_env_value_t *v = &env->values[i];
|
|
if (v->present) { if (cbm_setenv(v->name, v->value, 1) != 0) ok = false; }
|
|
else (void)cbm_unsetenv(v->name);
|
|
const char *now = getenv(v->name);
|
|
if (v->present ? (!now || strcmp(now, v->value) != 0) : now != NULL) ok = false;
|
|
}
|
|
env->saved = false;
|
|
return ok;
|
|
}
|
|
static bool pc_env_begin(pc_env_t *env, bool parallel) {
|
|
memset(env, 0, sizeof(*env));
|
|
const char *names[] = {"CBM_WORKERS", "CBM_INDEX_SINGLE_THREAD", "CBM_DISABLE_LSP_CROSS", "CBM_MEM_SPILL"};
|
|
for (size_t i = 0; i < 4; i++) {
|
|
env->values[i].name = names[i];
|
|
const char *value = getenv(names[i]);
|
|
env->values[i].present = value != NULL;
|
|
if (value) {
|
|
if (strlen(value) >= sizeof(env->values[i].value)) return false;
|
|
memcpy(env->values[i].value, value, strlen(value) + 1);
|
|
}
|
|
}
|
|
env->saved = true;
|
|
bool ok = cbm_setenv("CBM_WORKERS", "2", 1) == 0;
|
|
if (parallel) (void)cbm_unsetenv("CBM_INDEX_SINGLE_THREAD");
|
|
else ok = cbm_setenv("CBM_INDEX_SINGLE_THREAD", "1", 1) == 0 && ok;
|
|
(void)cbm_unsetenv("CBM_DISABLE_LSP_CROSS");
|
|
(void)cbm_unsetenv("CBM_MEM_SPILL");
|
|
const char *single = getenv("CBM_INDEX_SINGLE_THREAD");
|
|
ok = ok && cbm_default_worker_count(true) == 2 &&
|
|
(parallel ? single == NULL : single && strcmp(single, "1") == 0) &&
|
|
getenv("CBM_DISABLE_LSP_CROSS") == NULL && getenv("CBM_MEM_SPILL") == NULL;
|
|
if (!ok) (void)pc_env_restore(env);
|
|
return ok;
|
|
}
|
|
static bool pc_text(const char *a, const char *b) { return a && b && strcmp(a, b) == 0; }
|
|
static bool pc_role(const cbm_node_t *n, const char *role) {
|
|
if (!n || !n->properties_json) return false;
|
|
yyjson_doc *doc = yyjson_read(n->properties_json, strlen(n->properties_json), 0);
|
|
yyjson_val *root = doc ? yyjson_doc_get_root(doc) : NULL;
|
|
yyjson_val *value = yyjson_is_obj(root) ? yyjson_obj_get(root, "test_role") : NULL;
|
|
bool ok = yyjson_is_obj(root) && (role ? yyjson_is_str(value) &&
|
|
pc_text(yyjson_get_str(value), role) : value == NULL);
|
|
yyjson_doc_free(doc);
|
|
return ok;
|
|
}
|
|
static bool pc_node(cbm_store_t *s, const char *qn, const char *name, const char *file,
|
|
const char *role, cbm_node_t *out) {
|
|
memset(out, 0, sizeof(*out));
|
|
return cbm_store_find_node_by_qn(s, PC_PROJECT, qn, out) == CBM_STORE_OK &&
|
|
pc_text(out->qualified_name, qn) && pc_text(out->name, name) &&
|
|
pc_text(out->file_path, file) && pc_role(out, role);
|
|
}
|
|
static bool pc_call(cbm_store_t *s, int64_t source, int64_t target) {
|
|
cbm_edge_t *edges = NULL; int count = 0;
|
|
bool read = cbm_store_find_edges_by_source_type(s, source, "CALLS", &edges, &count) == CBM_STORE_OK;
|
|
bool found = false;
|
|
if (read) for (int i = 0; i < count; i++)
|
|
if (edges[i].source_id == source && edges[i].target_id == target && pc_text(edges[i].type, "CALLS")) found = true;
|
|
cbm_store_free_edges(edges, count);
|
|
return read && found;
|
|
}
|
|
static bool pc_c_graph(const char *db, bool after) {
|
|
cbm_store_t *s = cbm_store_open_path_query(db);
|
|
if (!s) return false;
|
|
cbm_node_t c = {0}, g = {0}, cs = {0}, gs = {0};
|
|
bool ok = pc_node(s, after ? PC_PROJECT ".unit.CHECK_after" : PC_PROJECT ".unit.CHECK_before",
|
|
after ? "CHECK_after" : "CHECK_before", "unit.c", "case", &c) &&
|
|
pc_node(s, PC_PROJECT ".unit.GROUP_group", "GROUP_group", "unit.c", "suite", &g) &&
|
|
pc_node(s, PC_PROJECT ".unit.case_sink", "case_sink", "unit.c", NULL, &cs) &&
|
|
pc_node(s, PC_PROJECT ".unit.suite_sink", "suite_sink", "unit.c", NULL, &gs) &&
|
|
pc_call(s, c.id, cs.id) && pc_call(s, g.id, gs.id);
|
|
cbm_node_free_fields(&c); cbm_node_free_fields(&g); cbm_node_free_fields(&cs); cbm_node_free_fields(&gs);
|
|
cbm_store_close(s); return ok;
|
|
}
|
|
static bool pc_ordinary_graph(const char *db, const char *name) {
|
|
cbm_store_t *s = cbm_store_open_path_query(db);
|
|
if (!s) return false;
|
|
char qn[256]; int n = snprintf(qn, sizeof(qn), PC_PROJECT ".unit.%s", name);
|
|
cbm_node_t owner = {0}, sink = {0};
|
|
bool ok = n > 0 && (size_t)n < sizeof(qn) && pc_node(s, qn, name, "unit.c", NULL, &owner) &&
|
|
pc_node(s, PC_PROJECT ".unit.sink", "sink", "unit.c", NULL, &sink) && pc_call(s, owner.id, sink.id);
|
|
cbm_node_free_fields(&owner); cbm_node_free_fields(&sink); cbm_store_close(s); return ok;
|
|
}
|
|
static bool pc_no_roles(const char *db) {
|
|
cbm_store_t *s = cbm_store_open_path_query(db);
|
|
cbm_node_t *nodes = NULL; int count = 0;
|
|
bool ok = s && cbm_store_find_nodes(s, PC_PROJECT, &nodes, &count) == CBM_STORE_OK && count > 0;
|
|
if (ok) for (int i = 0; i < count; i++) if (!pc_role(&nodes[i], NULL)) ok = false;
|
|
cbm_store_free_nodes(nodes, count); cbm_store_close(s); return ok;
|
|
}
|
|
static bool pc_generation(const char *db, char out[128]) {
|
|
cbm_store_t *s = cbm_store_open_path_query(db);
|
|
bool ok = s && cbm_store_generation(s, out, 128) == CBM_STORE_OK && out[0] && strcmp(out, "legacy") != 0;
|
|
cbm_store_close(s); return ok;
|
|
}
|
|
static bool pc_snapshot(pc_fixture_t *f, pc_snapshot_t *out) {
|
|
return pc_generation(f->db, out->generation) && pc_copy(f->db, f->saved);
|
|
}
|
|
static bool pc_preserved(pc_fixture_t *f, const pc_snapshot_t *before) {
|
|
char now[128];
|
|
return pc_files_equal(f->db, f->saved) && pc_generation(f->db, now) &&
|
|
strcmp(now, before->generation) == 0 && pc_ordinary_graph(f->db, "preserved");
|
|
}
|
|
static bool pc_source_hash(const char *db, char out[65]) {
|
|
cbm_store_t *s = cbm_store_open_path_query(db); cbm_file_hash_t h = {0};
|
|
bool ok = s && cbm_store_get_file_hash(s, PC_PROJECT, "unit.c", &h) == CBM_STORE_OK && h.sha256 && strlen(h.sha256) == 64;
|
|
if (ok) memcpy(out, h.sha256, 65);
|
|
cbm_store_clear_file_hash(&h); cbm_store_close(s); return ok;
|
|
}
|
|
static bool pc_missing(const char *db, const char *qn) {
|
|
cbm_store_t *s = cbm_store_open_path_query(db); cbm_node_t n = {0};
|
|
bool ok = s && cbm_store_find_node_by_qn(s, PC_PROJECT, qn, &n) == CBM_STORE_NOT_FOUND;
|
|
cbm_node_free_fields(&n); cbm_store_close(s); return ok;
|
|
}
|
|
/* A direct discovery witness for both conditions of the existing full-driver gate.
|
|
* This is not a runtime worker/route observer; the pipeline handle is opaque. */
|
|
static bool pc_parallel_inputs(pc_fixture_t *f, cbm_index_mode_t mode) {
|
|
cbm_file_info_t *files = NULL; int count = 0;
|
|
cbm_discover_opts_t opts = {.mode = mode};
|
|
bool ok = cbm_default_worker_count(true) == 2 && getenv("CBM_INDEX_SINGLE_THREAD") == NULL &&
|
|
cbm_discover(f->repo, &opts, &files, &count) == 0 && count > 50;
|
|
for (int n = 0; ok && n < 51; n++) {
|
|
char leaf[40]; (void)snprintf(leaf, sizeof(leaf), "filler_%02d.c", n);
|
|
int matches = 0;
|
|
for (int i = 0; i < count; i++)
|
|
if (pc_text(files[i].rel_path, leaf) && files[i].language == CBM_LANG_C) matches++;
|
|
ok = matches == 1;
|
|
}
|
|
cbm_discover_free(files, count); return ok;
|
|
}
|
|
static bool pc_snapshot_equal(pc_fixture_t *f, const pc_snapshot_t *before) {
|
|
char now[128];
|
|
return pc_files_equal(f->db, f->saved) && pc_generation(f->db, now) &&
|
|
strcmp(now, before->generation) == 0;
|
|
}
|
|
static int pc_run(pc_fixture_t *f, cbm_index_mode_t mode, bool legacy, unsigned faults,
|
|
void (*hook)(cbm_pipeline_t *, const char *, void *), void *ctx,
|
|
cbm_incremental_route_t *route) {
|
|
cbm_pipeline_incremental_test_reset_faults();
|
|
if (getenv("CBM_INDEX_SINGLE_THREAD") == NULL && !pc_parallel_inputs(f, mode)) return -997;
|
|
cbm_pipeline_t *p = cbm_pipeline_new(f->repo, f->db, mode);
|
|
if (!p) return -999;
|
|
bool named = cbm_pipeline_set_project_name(p, PC_PROJECT);
|
|
cbm_pipeline_set_persistence(p, false);
|
|
if (hook) cbm_pipeline_set_before_publish_hook_for_tests(p, hook, ctx);
|
|
cbm_pipeline_test_set_fault_mask(p, faults);
|
|
if (legacy) cbm_pipeline_incremental_test_force_legacy_partial_once();
|
|
int rc = named ? cbm_pipeline_run(p) : -998;
|
|
if (route) *route = cbm_pipeline_incremental_test_last_route();
|
|
cbm_pipeline_free(p);
|
|
cbm_pipeline_incremental_test_reset_faults();
|
|
return rc;
|
|
}
|
|
static bool pc_seed(pc_fixture_t *f, const char *config) {
|
|
return pc_source_write(f, "unit.c", pc_legacy_source) &&
|
|
(!config || pc_write(f->config, config)) && pc_run(f, CBM_MODE_FULL, false, 0, NULL, NULL, NULL) == 0 &&
|
|
pc_ordinary_graph(f->db, "preserved");
|
|
}
|
|
static bool pc_close(pc_fixture_t *f, pc_env_t *env) {
|
|
cbm_pipeline_incremental_test_reset_faults();
|
|
bool removed = !f->home[0] || th_rmtree(f->home) == 0;
|
|
return pc_env_restore(env) && removed;
|
|
}
|
|
|
|
static int pc_configured_case(bool parallel) {
|
|
pc_env_t env; pc_fixture_t f = {0};
|
|
bool setup = pc_env_begin(&env, parallel) && pc_fixture_open(&f, parallel) &&
|
|
pc_write(f.config, pc_config0) && pc_source_write(&f, "unit.c", pc_source);
|
|
int rc = setup ? pc_run(&f, CBM_MODE_FAST, false, 0, NULL, NULL, NULL) : -999;
|
|
bool graph = setup && pc_c_graph(f.db, false);
|
|
bool cleanup = pc_close(&f, &env);
|
|
ASSERT_TRUE(setup); ASSERT_TRUE(cleanup); ASSERT_EQ(rc, 0); ASSERT_TRUE(graph); PASS();
|
|
}
|
|
TEST(pipeline_conventions_serial_roles_qns_and_calls) {
|
|
ASSERT_EQ(pc_init_status, 0); return pc_configured_case(false);
|
|
}
|
|
TEST(pipeline_conventions_parallel_roles_qns_and_calls) {
|
|
ASSERT_EQ(pc_init_status, 0); return pc_configured_case(true);
|
|
}
|
|
TEST(pipeline_conventions_config_only_change_rebuilds_same_source) {
|
|
ASSERT_EQ(pc_init_status, 0);
|
|
pc_env_t env; pc_fixture_t f = {0}; char before_hash[65], after_hash[65], before_gen[128], after_gen[128];
|
|
bool setup = pc_env_begin(&env, true) && pc_fixture_open(&f, true) && pc_write(f.config, pc_config0) &&
|
|
pc_source_write(&f, "unit.c", pc_source);
|
|
bool control = setup && pc_run(&f, CBM_MODE_FAST, false, 0, NULL, NULL, NULL) == 0 &&
|
|
pc_c_graph(f.db, false) && pc_source_hash(f.db, before_hash) && pc_generation(f.db, before_gen);
|
|
bool changed = control && pc_write(f.config, pc_config1); /* Source is deliberately not rewritten. */
|
|
int rc = changed ? pc_run(&f, CBM_MODE_FAST, false, 0, NULL, NULL, NULL) : -999;
|
|
bool rebuilt = changed && pc_c_graph(f.db, true) && pc_missing(f.db, PC_PROJECT ".unit.CHECK_before") &&
|
|
pc_source_hash(f.db, after_hash) && strcmp(before_hash, after_hash) == 0 &&
|
|
pc_generation(f.db, after_gen) && strcmp(before_gen, after_gen) != 0;
|
|
bool cleanup = pc_close(&f, &env);
|
|
ASSERT_TRUE(setup); ASSERT_TRUE(control); ASSERT_TRUE(changed); ASSERT_TRUE(cleanup);
|
|
ASSERT_EQ(rc, 0); ASSERT_TRUE(rebuilt); PASS();
|
|
}
|
|
TEST(pipeline_conventions_absent_and_default_omit_role_properties) {
|
|
ASSERT_EQ(pc_init_status, 0);
|
|
pc_env_t env; pc_fixture_t f = {0};
|
|
bool setup = pc_env_begin(&env, false) && pc_fixture_open(&f, false);
|
|
bool absent = setup && pc_seed(&f, NULL) && pc_no_roles(f.db);
|
|
bool configured = absent && pc_write(f.config, pc_empty_config);
|
|
/* Both boundary faults are inapplicable without consumed declarations/owners. */
|
|
int rc = configured ? pc_run(&f, CBM_MODE_FULL, false,
|
|
CBM_PIPELINE_TEST_FAULT_EXTRACT_NULL | CBM_PIPELINE_TEST_FAULT_DEF_MODULES_NULL,
|
|
NULL, NULL, NULL) : -999;
|
|
bool defaults = configured && pc_ordinary_graph(f.db, "preserved") && pc_no_roles(f.db);
|
|
bool cleanup = pc_close(&f, &env);
|
|
ASSERT_TRUE(setup); ASSERT_TRUE(absent); ASSERT_TRUE(configured); ASSERT_TRUE(cleanup);
|
|
ASSERT_EQ(rc, 0); ASSERT_TRUE(defaults); PASS();
|
|
}
|
|
static bool pc_coverage_kind(const char *db, const char *path, const char *kind) {
|
|
cbm_store_t *s = cbm_store_open_path_query(db);
|
|
cbm_coverage_row_t *rows = NULL; int count = 0; bool found = false;
|
|
if (s && cbm_store_coverage_get_path(s, PC_PROJECT, path, &rows, &count) == CBM_STORE_OK)
|
|
for (int i = 0; i < count; i++) found = found || pc_text(rows[i].kind, kind);
|
|
cbm_store_free_coverage(rows, count); cbm_store_close(s); return found;
|
|
}
|
|
/* A configured form that cannot be mapped degrades its FILE, not the index
|
|
* (user decision 2026-10-04): the run publishes, the file is indexed without
|
|
* configured test roles, and it carries a test_declarations diagnostic. Before,
|
|
* one such file aborted the whole index (cbm's own repro matrices did). */
|
|
static int pc_typed_degrade(bool legacy) {
|
|
pc_env_t env; pc_fixture_t f = {0}; pc_snapshot_t snapshot;
|
|
bool setup = pc_env_begin(&env, legacy) && pc_fixture_open(&f, legacy);
|
|
bool control = setup && pc_seed(&f, pc_config0) && pc_snapshot(&f, &snapshot);
|
|
bool changed = control && pc_source_write(&f, "unit.c", pc_bad_source);
|
|
cbm_incremental_route_t route = CBM_INCREMENTAL_ROUTE_NONE;
|
|
int rc = changed ? pc_run(&f, legacy ? CBM_MODE_FULL : CBM_MODE_FAST, legacy, 0, NULL, NULL, &route) : -999;
|
|
char now[128];
|
|
bool published = changed && pc_generation(f.db, now) && strcmp(now, snapshot.generation) != 0 &&
|
|
pc_ordinary_graph(f.db, "preserved") && pc_no_roles(f.db) &&
|
|
pc_coverage_kind(f.db, "unit.c", "test_declarations");
|
|
bool cleanup = pc_close(&f, &env);
|
|
ASSERT_TRUE(setup); ASSERT_TRUE(control); ASSERT_TRUE(changed); ASSERT_TRUE(cleanup);
|
|
ASSERT_EQ(route, legacy ? CBM_INCREMENTAL_ROUTE_LEGACY_PARTIAL : CBM_INCREMENTAL_ROUTE_FORCED_FULL);
|
|
ASSERT_EQ(rc, 0); ASSERT_TRUE(published); PASS();
|
|
}
|
|
TEST(pipeline_conventions_typed_failure_full_degrades_the_file) {
|
|
ASSERT_EQ(pc_init_status, 0); return pc_typed_degrade(false);
|
|
}
|
|
TEST(pipeline_conventions_typed_failure_incremental_degrades_the_file) {
|
|
ASSERT_EQ(pc_init_status, 0); return pc_typed_degrade(true);
|
|
}
|
|
|
|
typedef struct { pc_fixture_t *fixture; int called; bool staged, written; } pc_hook_t;
|
|
static void pc_mutate_config(cbm_pipeline_t *p, const char *staging, void *opaque) {
|
|
(void)p; pc_hook_t *h = opaque;
|
|
h->called++;
|
|
h->staged = pc_ordinary_graph(staging, "staged");
|
|
h->written = pc_write(h->fixture->config, pc_config1);
|
|
}
|
|
TEST(pipeline_conventions_publication_config_mutation_preserves_generation) {
|
|
ASSERT_EQ(pc_init_status, 0);
|
|
pc_env_t env; pc_fixture_t f = {0}; pc_snapshot_t snapshot;
|
|
bool setup = pc_env_begin(&env, true) && pc_fixture_open(&f, true);
|
|
bool control = setup && pc_seed(&f, pc_config0) && pc_snapshot(&f, &snapshot);
|
|
bool changed = control && pc_source_write(&f, "unit.c", "void sink(void) {}\nvoid staged(void) { sink(); }\n");
|
|
pc_hook_t hook = {.fixture = &f};
|
|
int rc = changed ? pc_run(&f, CBM_MODE_FAST, false, 0, pc_mutate_config, &hook, NULL) : -999;
|
|
bool preserved = changed && pc_preserved(&f, &snapshot);
|
|
bool cleanup = pc_close(&f, &env);
|
|
ASSERT_TRUE(setup); ASSERT_TRUE(control); ASSERT_TRUE(changed); ASSERT_TRUE(cleanup);
|
|
ASSERT_EQ(hook.called, 1); ASSERT_TRUE(hook.staged); ASSERT_TRUE(hook.written);
|
|
ASSERT_EQ(rc, CBM_PIPELINE_ABORT_PRESERVE_DB); ASSERT_TRUE(preserved); PASS();
|
|
}
|
|
TEST(pipeline_conventions_published_owners_survive_join_and_config_edit) {
|
|
ASSERT_EQ(pc_init_status, 0);
|
|
pc_env_t env; pc_fixture_t f = {0}; pc_snapshot_t snapshot;
|
|
bool setup = pc_env_begin(&env, true) && pc_fixture_open(&f, true) && pc_write(f.config, pc_config0) &&
|
|
pc_source_write(&f, "unit.c", pc_source);
|
|
bool control = setup && pc_run(&f, CBM_MODE_FAST, false, 0, NULL, NULL, NULL) == 0 &&
|
|
pc_c_graph(f.db, false) && pc_snapshot(&f, &snapshot);
|
|
cbm_store_t *store = control ? cbm_store_open_path_query(f.db) : NULL;
|
|
cbm_node_t owner = {0}, suite = {0};
|
|
bool acquired = store && pc_node(store, PC_PROJECT ".unit.CHECK_before", "CHECK_before", "unit.c", "case", &owner) &&
|
|
pc_node(store, PC_PROJECT ".unit.GROUP_group", "GROUP_group", "unit.c", "suite", &suite);
|
|
cbm_store_close(store); /* Returned node strings are owned by the caller. */
|
|
bool edited = acquired && pc_write(f.config, "{not valid JSON after the completed run");
|
|
char now[128];
|
|
bool lifetime = edited && pc_text(owner.qualified_name, PC_PROJECT ".unit.CHECK_before") && pc_role(&owner, "case") &&
|
|
pc_text(suite.qualified_name, PC_PROJECT ".unit.GROUP_group") && pc_role(&suite, "suite") &&
|
|
pc_c_graph(f.db, false) && pc_files_equal(f.db, f.saved) && pc_generation(f.db, now) &&
|
|
strcmp(now, snapshot.generation) == 0;
|
|
cbm_node_free_fields(&owner); cbm_node_free_fields(&suite);
|
|
bool cleanup = pc_close(&f, &env);
|
|
ASSERT_TRUE(setup); ASSERT_TRUE(control); ASSERT_TRUE(acquired); ASSERT_TRUE(edited);
|
|
ASSERT_TRUE(cleanup); ASSERT_TRUE(lifetime); PASS();
|
|
}
|
|
|
|
static bool pc_cpp_files(pc_fixture_t *f) {
|
|
char old[1024];
|
|
return pc_path(old, sizeof(old), f->repo, "unit.c") && cbm_unlink(old) == 0 &&
|
|
pc_write(f->config, pc_cpp_config) &&
|
|
pc_source_write(f, "engine.h", "struct Engine { void work() {} };\n") &&
|
|
pc_source_write(f, "unit.cpp", pc_cpp_source);
|
|
}
|
|
static bool pc_cpp_graph(const char *db) {
|
|
cbm_store_t *s = cbm_store_open_path_query(db); cbm_node_t owner = {0}, *nodes = NULL; int count = 0;
|
|
bool ok = s && pc_node(s, PC_PROJECT ".unit.CHECK_alpha", "CHECK_alpha", "unit.cpp", "case", &owner) &&
|
|
cbm_store_find_nodes_by_file(s, PC_PROJECT, "engine.h", &nodes, &count) == CBM_STORE_OK;
|
|
int matches = 0; int64_t target = 0;
|
|
if (ok) for (int i = 0; i < count; i++) {
|
|
if (pc_text(nodes[i].name, "work") && pc_text(nodes[i].label, "Method") &&
|
|
pc_text(nodes[i].qualified_name, PC_PROJECT ".engine.Engine.work")) {
|
|
matches++; target = nodes[i].id;
|
|
}
|
|
}
|
|
ok = ok && matches == 1 && pc_call(s, owner.id, target);
|
|
cbm_node_free_fields(&owner); cbm_store_free_nodes(nodes, count); cbm_store_close(s); return ok;
|
|
}
|
|
TEST(pipeline_conventions_disabled_required_parallel_crosswalk_preserves_generation) {
|
|
ASSERT_EQ(pc_init_status, 0);
|
|
pc_env_t env; pc_fixture_t f = {0}; pc_snapshot_t snapshot;
|
|
bool setup = pc_env_begin(&env, true) && pc_fixture_open(&f, true);
|
|
bool control = setup && pc_seed(&f, NULL) && pc_snapshot(&f, &snapshot);
|
|
bool changed = control && pc_cpp_files(&f) && cbm_setenv("CBM_DISABLE_LSP_CROSS", "1", 1) == 0;
|
|
cbm_incremental_route_t route = CBM_INCREMENTAL_ROUTE_NONE;
|
|
int rc = changed ? pc_run(&f, CBM_MODE_FAST, false, 0, NULL, NULL, &route) : -999;
|
|
bool preserved = changed && pc_preserved(&f, &snapshot);
|
|
(void)cbm_unsetenv("CBM_DISABLE_LSP_CROSS");
|
|
bool recovery = changed && getenv("CBM_DISABLE_LSP_CROSS") == NULL &&
|
|
pc_run(&f, CBM_MODE_FAST, false, 0, NULL, NULL, NULL) == 0 && pc_cpp_graph(f.db);
|
|
bool cleanup = pc_close(&f, &env);
|
|
ASSERT_TRUE(setup); ASSERT_TRUE(control); ASSERT_TRUE(changed); ASSERT_TRUE(cleanup);
|
|
ASSERT_EQ(route, CBM_INCREMENTAL_ROUTE_FORCED_FULL); ASSERT_EQ(rc, CBM_PIPELINE_ABORT_PRESERVE_DB);
|
|
ASSERT_TRUE(preserved); ASSERT_TRUE(recovery); PASS();
|
|
}
|
|
TEST(pipeline_conventions_configured_null_result_preserves_generation) {
|
|
ASSERT_EQ(pc_init_status, 0);
|
|
for (int parallel = 0; parallel < 2; parallel++) {
|
|
pc_env_t env; pc_fixture_t f = {0}; pc_snapshot_t snapshot;
|
|
bool setup = pc_env_begin(&env, parallel != 0) && pc_fixture_open(&f, parallel != 0);
|
|
bool control = setup && pc_seed(&f, pc_config0) && pc_snapshot(&f, &snapshot);
|
|
bool changed = control && pc_source_write(&f, "unit.c", pc_source);
|
|
cbm_incremental_route_t route = CBM_INCREMENTAL_ROUTE_NONE;
|
|
int rc = changed ? pc_run(&f, CBM_MODE_FAST, false, CBM_PIPELINE_TEST_FAULT_EXTRACT_NULL,
|
|
NULL, NULL, &route) : -999;
|
|
bool preserved = changed && pc_preserved(&f, &snapshot);
|
|
bool recovery = changed && pc_run(&f, CBM_MODE_FAST, false, 0, NULL, NULL, NULL) == 0 && pc_c_graph(f.db, false);
|
|
bool cleanup = pc_close(&f, &env);
|
|
if (rc != CBM_PIPELINE_ABORT_PRESERVE_DB || !preserved || !recovery)
|
|
fprintf(stderr, "configured NULL-result boundary (%s)\n", parallel ? "parallel" : "serial");
|
|
ASSERT_TRUE(setup); ASSERT_TRUE(control); ASSERT_TRUE(changed); ASSERT_TRUE(cleanup);
|
|
ASSERT_EQ(route, CBM_INCREMENTAL_ROUTE_FORCED_FULL); ASSERT_EQ(rc, CBM_PIPELINE_ABORT_PRESERVE_DB);
|
|
ASSERT_TRUE(preserved); ASSERT_TRUE(recovery);
|
|
}
|
|
PASS();
|
|
}
|
|
TEST(pipeline_conventions_required_def_modules_null_preserves_generation) {
|
|
ASSERT_EQ(pc_init_status, 0);
|
|
pc_env_t env; pc_fixture_t f = {0}; pc_snapshot_t snapshot;
|
|
bool setup = pc_env_begin(&env, false) && pc_fixture_open(&f, false);
|
|
bool control = setup && pc_seed(&f, NULL) && pc_snapshot(&f, &snapshot);
|
|
bool changed = control && pc_cpp_files(&f);
|
|
cbm_incremental_route_t route = CBM_INCREMENTAL_ROUTE_NONE;
|
|
int rc = changed ? pc_run(&f, CBM_MODE_FAST, false, CBM_PIPELINE_TEST_FAULT_DEF_MODULES_NULL,
|
|
NULL, NULL, &route) : -999;
|
|
bool preserved = changed && pc_preserved(&f, &snapshot);
|
|
bool recovery = changed && pc_run(&f, CBM_MODE_FAST, false, 0, NULL, NULL, NULL) == 0 && pc_cpp_graph(f.db);
|
|
bool cleanup = pc_close(&f, &env);
|
|
ASSERT_TRUE(setup); ASSERT_TRUE(control); ASSERT_TRUE(changed); ASSERT_TRUE(cleanup);
|
|
ASSERT_EQ(route, CBM_INCREMENTAL_ROUTE_FORCED_FULL); ASSERT_EQ(rc, CBM_PIPELINE_ABORT_PRESERVE_DB);
|
|
ASSERT_TRUE(preserved); ASSERT_TRUE(recovery); PASS();
|
|
}
|
|
|
|
/* Body-only edit: unchanged declarations, mode, config and inventory keep the
|
|
* natural closure candidate valid. The route assertion prevents full fallback. */
|
|
TEST(pipeline_conventions_closure_def_modules_null_preserves_generation) {
|
|
ASSERT_EQ(pc_init_status, 0);
|
|
pc_env_t env; pc_fixture_t f = {0}; pc_snapshot_t snapshot; char old_hash[65], new_hash[65];
|
|
bool setup = pc_env_begin(&env, false) && pc_fixture_open(&f, false) &&
|
|
pc_write(f.config, pc_config0) && pc_source_write(&f, "unit.c", pc_source);
|
|
bool control = setup && pc_run(&f, CBM_MODE_FULL, false, 0, NULL, NULL, NULL) == 0 &&
|
|
pc_c_graph(f.db, false) && pc_source_hash(f.db, old_hash) && pc_snapshot(&f, &snapshot);
|
|
const char changed_source[] =
|
|
"void case_sink(void) {}\nvoid suite_sink(void) {}\n"
|
|
"CHECK(before, after) { case_sink(); case_sink(); }\nGROUP(group) { suite_sink(); }\n";
|
|
bool changed = control && pc_source_write(&f, "unit.c", changed_source);
|
|
cbm_incremental_route_t route = CBM_INCREMENTAL_ROUTE_NONE, retry_route = CBM_INCREMENTAL_ROUTE_NONE;
|
|
int rc = changed ? pc_run(&f, CBM_MODE_FULL, false, CBM_PIPELINE_TEST_FAULT_DEF_MODULES_NULL,
|
|
NULL, NULL, &route) : -999;
|
|
bool preserved = changed && pc_snapshot_equal(&f, &snapshot) && pc_c_graph(f.db, false);
|
|
bool recovery = changed && pc_run(&f, CBM_MODE_FULL, false, 0, NULL, NULL, &retry_route) == 0 &&
|
|
pc_c_graph(f.db, false) && pc_source_hash(f.db, new_hash) && strcmp(old_hash, new_hash) != 0;
|
|
bool cleanup = pc_close(&f, &env);
|
|
ASSERT_TRUE(setup); ASSERT_TRUE(control); ASSERT_TRUE(changed); ASSERT_TRUE(cleanup);
|
|
ASSERT_EQ(route, CBM_INCREMENTAL_ROUTE_CLOSURE_REPAIR);
|
|
ASSERT_EQ(rc, CBM_PIPELINE_ABORT_PRESERVE_DB); ASSERT_TRUE(preserved);
|
|
ASSERT_EQ(retry_route, CBM_INCREMENTAL_ROUTE_CLOSURE_REPAIR); ASSERT_TRUE(recovery); PASS();
|
|
}
|
|
|
|
typedef struct {
|
|
bool setup, parked, registry, positive, rejected, reload;
|
|
} pc_spill_witness_t;
|
|
static bool pc_result_case(const CBMFileResult *r, const char *name, const char *qn) {
|
|
if (!r || r->test_declarations_status != CBM_TEST_EXTRACT_OK || !r->has_test_definition_owners)
|
|
return false;
|
|
int matches = 0;
|
|
for (int i = 0; i < r->defs.count; i++)
|
|
if (pc_text(r->defs.items[i].name, name) && pc_text(r->defs.items[i].qualified_name, qn) &&
|
|
r->defs.items[i].test_role == CBM_TEST_ROLE_CASE) matches++;
|
|
return matches == 1;
|
|
}
|
|
/* First file contributes real definitions before the second file's actual
|
|
* spilled-load boundary fails. Registry and post-failure reload stay real. */
|
|
static pc_spill_witness_t pc_collect_spill_witness(pc_fixture_t *f) {
|
|
pc_spill_witness_t w = {0}; char dir[1024], plain_path[1024], owner_path[1024];
|
|
const char plain[] = "void contributor(void) {}\n";
|
|
const char owner[] = "CHECK(parked) {}\n";
|
|
cbm_test_declarations_t *decl = NULL;
|
|
CBMFileResult *cache[2] = {NULL, NULL}, *reload = NULL;
|
|
cbm_result_spill_t *spill = NULL;
|
|
CBMLSPDef *defs = NULL;
|
|
char *modules[2] = {NULL, NULL}; int starts[3] = {-7, -7, -7}, count = -7;
|
|
CBMArena arena; cbm_arena_init_lazy(&arena, 4096);
|
|
atomic_int cancelled; atomic_init(&cancelled, 0);
|
|
cbm_pipeline_ctx_t ctx = {0};
|
|
ctx.project_name = PC_PROJECT; ctx.repo_path = f->repo; ctx.cancelled = &cancelled;
|
|
atomic_init(&ctx.test_declarations_failed, 0);
|
|
atomic_init(&ctx.test_definition_owners_seen, 0);
|
|
atomic_init(&ctx.spill_mode, 0);
|
|
if (!pc_path(dir, sizeof(dir), f->home, "collector-spill") || th_mkdir_p(dir) != 0 ||
|
|
!pc_path(plain_path, sizeof(plain_path), f->repo, "contributor.c") ||
|
|
!pc_path(owner_path, sizeof(owner_path), f->repo, "parked.c")) goto done;
|
|
decl = cbm_test_declarations_parse(pc_config0, strlen(pc_config0), false);
|
|
if (!decl) goto done;
|
|
cache[0] = cbm_extract_file_ex_with_tests(plain, (int)strlen(plain), CBM_LANG_C,
|
|
PC_PROJECT, "contributor.c", 0, NULL, NULL, NULL, NULL, decl);
|
|
if (!cache[0] || cache[0]->test_declarations_status != CBM_TEST_EXTRACT_OK || cache[0]->defs.count < 1)
|
|
goto done;
|
|
cbm_result_compact(cache[0]);
|
|
cache[1] = cbm_extract_file_ex_with_tests(owner, (int)strlen(owner), CBM_LANG_C,
|
|
PC_PROJECT, "parked.c", 0, NULL, NULL, NULL, NULL, decl);
|
|
if (!pc_result_case(cache[1], "CHECK_parked", PC_PROJECT ".parked.CHECK_parked")) goto done;
|
|
cbm_result_compact(cache[1]);
|
|
ctx.test_declarations = decl;
|
|
atomic_store(&ctx.test_definition_owners_seen, 1); /* Actual configured owner was just verified. */
|
|
ctx.gbuf = cbm_gbuf_new(PC_PROJECT, f->repo); ctx.registry = cbm_registry_new();
|
|
spill = cbm_result_spill_open(dir, 1, 2); ctx.spill = spill;
|
|
w.setup = ctx.gbuf && ctx.registry && spill;
|
|
if (!w.setup) goto done;
|
|
if (!cbm_result_spill_park(spill, 0, 1, cache[1])) goto done;
|
|
cache[1] = NULL; /* park consumed it. */
|
|
int64_t parked = 0, bytes = 0, loads_before = 0, loads_registry = 0, loads_control = 0, loads_after = 0;
|
|
cbm_result_spill_stats(spill, &parked, &bytes, &loads_before);
|
|
w.parked = cbm_result_spill_has(spill, 1) && parked == 1 && bytes > 0 && cache[0]->defs.count > 0;
|
|
if (!w.parked) goto done;
|
|
cbm_file_info_t files[2] = {
|
|
{.path = plain_path, .rel_path = "contributor.c", .language = CBM_LANG_C, .size = sizeof(plain)-1},
|
|
{.path = owner_path, .rel_path = "parked.c", .language = CBM_LANG_C, .size = sizeof(owner)-1}
|
|
};
|
|
int registry_rc = cbm_build_registry_from_cache(&ctx, files, 2, cache);
|
|
cbm_result_spill_stats(spill, &parked, &bytes, &loads_registry);
|
|
cbm_resolution_t resolved = cbm_registry_resolve(ctx.registry, "CHECK_parked",
|
|
PC_PROJECT ".parked", NULL, NULL, 0);
|
|
w.registry = registry_rc == 0 && loads_registry > loads_before &&
|
|
pc_text(resolved.qualified_name, PC_PROJECT ".parked.CHECK_parked");
|
|
if (!w.registry) goto done;
|
|
defs = cbm_pxc_collect_all_defs(&ctx, &arena, cache, files, 2, PC_PROJECT, modules, &count, starts);
|
|
cbm_result_spill_stats(spill, &parked, &bytes, &loads_control);
|
|
w.positive = defs && starts[0] == 0 && starts[1] > 0 && starts[2] > starts[1] &&
|
|
count == starts[2] && loads_control > loads_registry;
|
|
free(defs); defs = NULL;
|
|
for (int i = 0; i < 2; i++) { free(modules[i]); modules[i] = NULL; }
|
|
if (!w.positive) goto done;
|
|
ctx.test_fault_mask = CBM_PIPELINE_TEST_FAULT_COLLECT_SPILL_NULL;
|
|
count = -7; starts[0] = starts[1] = starts[2] = -7;
|
|
defs = cbm_pxc_collect_all_defs(&ctx, &arena, cache, files, 2, PC_PROJECT, modules, &count, starts);
|
|
w.rejected = defs == NULL && count == -1 && starts[0] == 0 && starts[1] == 0 && starts[2] == 0;
|
|
reload = cbm_result_spill_load(spill, 1); /* Keep mask set: only collector loads are intercepted. */
|
|
cbm_result_spill_stats(spill, &parked, &bytes, &loads_after);
|
|
w.reload = pc_result_case(reload, "CHECK_parked", PC_PROJECT ".parked.CHECK_parked") &&
|
|
cbm_result_spill_has(spill, 1) && loads_after > loads_control;
|
|
done:
|
|
free(defs);
|
|
for (int i = 0; i < 2; i++) { free(modules[i]); cbm_free_result(cache[i]); }
|
|
cbm_free_result(reload); cbm_registry_free(ctx.registry); cbm_gbuf_free(ctx.gbuf);
|
|
if (spill) cbm_result_spill_close(spill);
|
|
cbm_arena_destroy(&arena); cbm_test_declarations_free(decl);
|
|
return w;
|
|
}
|
|
TEST(pipeline_conventions_spilled_collect_failure_preserves_generation) {
|
|
ASSERT_EQ(pc_init_status, 0);
|
|
pc_env_t env; pc_fixture_t f = {0}; pc_snapshot_t snapshot;
|
|
size_t floor = cbm_result_spill_free_floor_bytes_for_tests(), room = (size_t)1 << 30;
|
|
bool pin_valid = floor <= SIZE_MAX - room;
|
|
size_t old_pin = pin_valid ? cbm_result_spill_pin_free_bytes_for_tests(floor + room) : 0;
|
|
bool setup = pc_env_begin(&env, true) && pc_fixture_open(&f, true) && pin_valid;
|
|
pc_spill_witness_t witness = setup ? pc_collect_spill_witness(&f) : (pc_spill_witness_t){0};
|
|
bool control = setup && pc_seed(&f, NULL) && pc_snapshot(&f, &snapshot);
|
|
bool changed = control && pc_write(f.config, pc_config0) && pc_source_write(&f, "unit.c", pc_source) &&
|
|
cbm_setenv("CBM_MEM_SPILL", "1", 1) == 0 && pc_text(getenv("CBM_MEM_SPILL"), "1") &&
|
|
pc_parallel_inputs(&f, CBM_MODE_FAST);
|
|
cbm_incremental_route_t route = CBM_INCREMENTAL_ROUTE_NONE;
|
|
int rc = changed ? pc_run(&f, CBM_MODE_FAST, false, CBM_PIPELINE_TEST_FAULT_COLLECT_SPILL_NULL,
|
|
NULL, NULL, &route) : -999;
|
|
bool preserved = changed && pc_preserved(&f, &snapshot);
|
|
bool recovery = changed && pc_run(&f, CBM_MODE_FAST, false, 0, NULL, NULL, NULL) == 0 &&
|
|
pc_c_graph(f.db, false);
|
|
if (pin_valid) (void)cbm_result_spill_pin_free_bytes_for_tests(old_pin);
|
|
bool cleanup = pc_close(&f, &env);
|
|
ASSERT_TRUE(setup); ASSERT_TRUE(cleanup);
|
|
ASSERT_TRUE(witness.setup); ASSERT_TRUE(witness.parked); ASSERT_TRUE(witness.registry);
|
|
ASSERT_TRUE(witness.positive); ASSERT_TRUE(witness.reload); ASSERT_TRUE(witness.rejected);
|
|
ASSERT_TRUE(control); ASSERT_TRUE(changed); ASSERT_EQ(route, CBM_INCREMENTAL_ROUTE_FORCED_FULL);
|
|
ASSERT_EQ(rc, CBM_PIPELINE_ABORT_PRESERVE_DB); ASSERT_TRUE(preserved); ASSERT_TRUE(recovery); PASS();
|
|
}
|
|
|
|
SUITE(pipeline_conventions) {
|
|
pc_init_status = cbm_init();
|
|
RUN_TEST(pipeline_conventions_serial_roles_qns_and_calls);
|
|
RUN_TEST(pipeline_conventions_parallel_roles_qns_and_calls);
|
|
RUN_TEST(pipeline_conventions_config_only_change_rebuilds_same_source);
|
|
RUN_TEST(pipeline_conventions_absent_and_default_omit_role_properties);
|
|
RUN_TEST(pipeline_conventions_typed_failure_full_degrades_the_file);
|
|
RUN_TEST(pipeline_conventions_typed_failure_incremental_degrades_the_file);
|
|
RUN_TEST(pipeline_conventions_publication_config_mutation_preserves_generation);
|
|
RUN_TEST(pipeline_conventions_published_owners_survive_join_and_config_edit);
|
|
RUN_TEST(pipeline_conventions_disabled_required_parallel_crosswalk_preserves_generation);
|
|
RUN_TEST(pipeline_conventions_configured_null_result_preserves_generation);
|
|
RUN_TEST(pipeline_conventions_required_def_modules_null_preserves_generation);
|
|
RUN_TEST(pipeline_conventions_closure_def_modules_null_preserves_generation);
|
|
RUN_TEST(pipeline_conventions_spilled_collect_failure_preserves_generation);
|
|
cbm_work_arena_release();
|
|
}
|