mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-06 15:49:59 +00:00
902 lines
39 KiB
C
902 lines
39 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();
|
|
}
|
|
|
|
/* A body-only edit still rebuilds resolver negative dependencies. Faults must
|
|
* preserve the prior generation, and a retry must publish the changed body. */
|
|
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_FORCED_FULL);
|
|
ASSERT_EQ(rc, CBM_PIPELINE_ABORT_PRESERVE_DB);
|
|
ASSERT_TRUE(preserved);
|
|
ASSERT_EQ(retry_route, CBM_INCREMENTAL_ROUTE_FORCED_FULL);
|
|
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();
|
|
}
|