mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-06 15:49:59 +00:00
285 lines
13 KiB
C
285 lines
13 KiB
C
#include "test_framework.h"
|
|
#include "resolve/solver.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
typedef struct {
|
|
CBMArena arena;
|
|
cbm_resolve_proof_pool_t proofs;
|
|
cbm_resolve_solver_t *solver;
|
|
} solver_fixture_t;
|
|
|
|
static bool solver_fixture_init(solver_fixture_t *fixture, cbm_resolve_solver_limits_t limits) {
|
|
memset(fixture, 0, sizeof(*fixture));
|
|
cbm_arena_init(&fixture->arena);
|
|
cbm_resolve_proof_pool_init(&fixture->proofs, &fixture->arena, 0, 0, 0);
|
|
fixture->solver = cbm_resolve_solver_new(&fixture->arena, &fixture->proofs, limits);
|
|
return fixture->solver != NULL;
|
|
}
|
|
|
|
static const cbm_resolve_proof_t *solver_audit(solver_fixture_t *fixture, const char *key) {
|
|
cbm_resolve_proof_source_t source = {
|
|
.project = "solver",
|
|
.file = "input.py",
|
|
.module = "solver.input",
|
|
.scope = "solver.input",
|
|
.content_identity = "f0c0a6775f31f5fb00a86b00c4d4581ef5600a551b9879fa7fd87014fbebaf26"};
|
|
cbm_resolve_proof_key_t lookup = {.lookup_domain = "solver.input",
|
|
.lookup_key = key,
|
|
.binding_identity = key,
|
|
.detail = "source_audit"};
|
|
cbm_resolve_result_t outcome = {.analysis = CBM_RESOLVE_ANALYSIS_COMPLETE};
|
|
return cbm_resolve_proof_leaf(&fixture->proofs, CBM_PROOF_ADAPTER_DOMAIN, &source, &lookup,
|
|
&outcome);
|
|
}
|
|
|
|
static bool solver_leaf(solver_fixture_t *fixture, const char *key, const char *target,
|
|
cbm_resolve_solver_status_t status) {
|
|
return cbm_resolve_solver_leaf(fixture->solver, key, status,
|
|
(cbm_resolve_solver_value_t){.target = target, .kind = 1},
|
|
solver_audit(fixture, key));
|
|
}
|
|
|
|
static bool solver_copy(solver_fixture_t *fixture, const char *key, const char *dependency) {
|
|
return cbm_resolve_solver_copy(fixture->solver, key, dependency, solver_audit(fixture, key));
|
|
}
|
|
|
|
TEST(resolver_solver_pending_is_not_unknown_and_copy_settles) {
|
|
solver_fixture_t fixture;
|
|
ASSERT(solver_fixture_init(&fixture, (cbm_resolve_solver_limits_t){0}));
|
|
ASSERT(solver_copy(&fixture, "alias", "definition"));
|
|
ASSERT(solver_leaf(&fixture, "definition", "project.actual.target", CBM_SOLVER_KNOWN));
|
|
cbm_resolve_solver_result_t before, after, absent;
|
|
cbm_resolve_solver_get(fixture.solver, "alias", &before);
|
|
ASSERT_EQ(before.status, CBM_SOLVER_PENDING);
|
|
ASSERT_NULL(before.value.target);
|
|
ASSERT(cbm_resolve_solver_solve(fixture.solver));
|
|
cbm_resolve_solver_get(fixture.solver, "alias", &after);
|
|
cbm_resolve_solver_get(fixture.solver, "absent", &absent);
|
|
ASSERT_EQ(after.status, CBM_SOLVER_KNOWN);
|
|
ASSERT_STR_EQ(after.value.target, "project.actual.target");
|
|
ASSERT(after.predecessors_closed);
|
|
ASSERT_EQ(after.dependency_count, 1);
|
|
ASSERT_STR_EQ(after.dependencies[0], "definition");
|
|
ASSERT_EQ(absent.status, CBM_SOLVER_UNKNOWN);
|
|
const cbm_resolve_proof_view_t *proof = cbm_resolve_proof_view(after.proof);
|
|
ASSERT_NOT_NULL(proof);
|
|
ASSERT_EQ(proof->kind, CBM_PROOF_NAMED_COPY);
|
|
ASSERT_EQ(proof->premise_count, 2);
|
|
cbm_arena_destroy(&fixture.arena);
|
|
PASS();
|
|
}
|
|
|
|
TEST(resolver_solver_join_preserves_unknown_and_unsupported_competitors) {
|
|
const cbm_resolve_solver_status_t states[] = {CBM_SOLVER_UNKNOWN, CBM_SOLVER_UNSUPPORTED,
|
|
CBM_SOLVER_LIMITED, CBM_SOLVER_FAILED};
|
|
for (size_t i = 0; i < sizeof(states) / sizeof(states[0]); i++) {
|
|
solver_fixture_t fixture;
|
|
ASSERT(solver_fixture_init(&fixture, (cbm_resolve_solver_limits_t){0}));
|
|
ASSERT(solver_leaf(&fixture, "known", "project.Type", CBM_SOLVER_KNOWN));
|
|
ASSERT(solver_leaf(&fixture, "competitor", NULL, states[i]));
|
|
const char *dependencies[] = {"known", "competitor"};
|
|
ASSERT(cbm_resolve_solver_join(fixture.solver, "joined", dependencies, 2,
|
|
solver_audit(&fixture, "joined")));
|
|
ASSERT(cbm_resolve_solver_solve(fixture.solver));
|
|
cbm_resolve_solver_result_t joined;
|
|
cbm_resolve_solver_get(fixture.solver, "joined", &joined);
|
|
ASSERT_EQ(joined.status, states[i]);
|
|
ASSERT_NULL(joined.value.target);
|
|
ASSERT(joined.predecessors_closed);
|
|
ASSERT_EQ(joined.dependency_count, 2);
|
|
ASSERT_EQ(cbm_resolve_proof_view(joined.proof)->premise_count, 3);
|
|
cbm_arena_destroy(&fixture.arena);
|
|
}
|
|
PASS();
|
|
}
|
|
|
|
TEST(resolver_solver_join_compares_target_kind_and_module_identity) {
|
|
for (int mismatch = 0; mismatch < 3; mismatch++) {
|
|
solver_fixture_t fixture;
|
|
ASSERT(solver_fixture_init(&fixture, (cbm_resolve_solver_limits_t){0}));
|
|
ASSERT(solver_leaf(&fixture, "left", "project.Type", CBM_SOLVER_KNOWN));
|
|
cbm_resolve_solver_value_t right = {.target =
|
|
mismatch == 0 ? "project.Other" : "project.Type",
|
|
.kind = mismatch == 1 ? 2 : 1,
|
|
.module = mismatch == 2};
|
|
ASSERT(cbm_resolve_solver_leaf(fixture.solver, "right", CBM_SOLVER_KNOWN, right,
|
|
solver_audit(&fixture, "right")));
|
|
const char *dependencies[] = {"left", "right"};
|
|
ASSERT(cbm_resolve_solver_join(fixture.solver, "joined", dependencies, 2,
|
|
solver_audit(&fixture, "joined")));
|
|
ASSERT(cbm_resolve_solver_solve(fixture.solver));
|
|
cbm_resolve_solver_result_t joined;
|
|
cbm_resolve_solver_get(fixture.solver, "joined", &joined);
|
|
ASSERT_EQ(joined.status, CBM_SOLVER_AMBIGUOUS);
|
|
ASSERT_NULL(joined.value.target);
|
|
cbm_arena_destroy(&fixture.arena);
|
|
}
|
|
PASS();
|
|
}
|
|
|
|
typedef struct {
|
|
bool left, right, joined;
|
|
size_t nodes;
|
|
} solver_supports_t;
|
|
static bool collect_solver_support(const cbm_resolve_proof_t *proof, void *context) {
|
|
solver_supports_t *supports = context;
|
|
const cbm_resolve_proof_view_t *view = cbm_resolve_proof_view(proof);
|
|
supports->nodes++;
|
|
if (view->kind == CBM_PROOF_ADAPTER_DOMAIN) {
|
|
supports->left |= !strcmp(view->key.lookup_key, "left");
|
|
supports->right |= !strcmp(view->key.lookup_key, "right");
|
|
supports->joined |= !strcmp(view->key.lookup_key, "joined");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
TEST(resolver_solver_equal_join_retains_every_support) {
|
|
solver_fixture_t fixture;
|
|
ASSERT(solver_fixture_init(&fixture, (cbm_resolve_solver_limits_t){0}));
|
|
ASSERT(solver_leaf(&fixture, "left", "project.Type", CBM_SOLVER_KNOWN));
|
|
ASSERT(solver_leaf(&fixture, "right", "project.Type", CBM_SOLVER_KNOWN));
|
|
const char *dependencies[] = {"right", "left", "right"};
|
|
ASSERT(cbm_resolve_solver_join(fixture.solver, "joined", dependencies, 3,
|
|
solver_audit(&fixture, "joined")));
|
|
ASSERT(cbm_resolve_solver_solve(fixture.solver));
|
|
cbm_resolve_solver_result_t joined;
|
|
cbm_resolve_solver_get(fixture.solver, "joined", &joined);
|
|
ASSERT_EQ(joined.status, CBM_SOLVER_KNOWN);
|
|
ASSERT_EQ(joined.dependency_count, 2);
|
|
solver_supports_t supports = {0};
|
|
ASSERT(cbm_resolve_proof_visit(joined.proof, 16, collect_solver_support, &supports));
|
|
ASSERT(supports.left);
|
|
ASSERT(supports.right);
|
|
ASSERT(supports.joined);
|
|
ASSERT_EQ(supports.nodes, 4);
|
|
cbm_arena_destroy(&fixture.arena);
|
|
PASS();
|
|
}
|
|
|
|
TEST(resolver_solver_missing_dependencies_and_cycles_never_borrow_known_arm) {
|
|
solver_fixture_t fixture;
|
|
ASSERT(solver_fixture_init(&fixture, (cbm_resolve_solver_limits_t){0}));
|
|
ASSERT(solver_leaf(&fixture, "seed", "project.Type", CBM_SOLVER_KNOWN));
|
|
ASSERT(solver_copy(&fixture, "a", "b"));
|
|
const char *dependencies[] = {"a", "seed"};
|
|
ASSERT(
|
|
cbm_resolve_solver_join(fixture.solver, "b", dependencies, 2, solver_audit(&fixture, "b")));
|
|
ASSERT(solver_copy(&fixture, "missing", "not_registered"));
|
|
ASSERT(cbm_resolve_solver_solve(fixture.solver));
|
|
cbm_resolve_solver_result_t a, b, missing;
|
|
cbm_resolve_solver_get(fixture.solver, "a", &a);
|
|
cbm_resolve_solver_get(fixture.solver, "b", &b);
|
|
cbm_resolve_solver_get(fixture.solver, "missing", &missing);
|
|
ASSERT_EQ(a.status, CBM_SOLVER_UNKNOWN);
|
|
ASSERT_EQ(b.status, CBM_SOLVER_UNKNOWN);
|
|
ASSERT_NULL(a.value.target);
|
|
ASSERT_NULL(b.value.target);
|
|
ASSERT(!a.predecessors_closed);
|
|
ASSERT(!b.predecessors_closed);
|
|
ASSERT_EQ(b.dependency_count, 2);
|
|
ASSERT_EQ(missing.status, CBM_SOLVER_UNKNOWN);
|
|
ASSERT(missing.predecessors_closed);
|
|
ASSERT_EQ(cbm_resolve_proof_view(missing.proof)->premise_count, 2);
|
|
cbm_arena_destroy(&fixture.arena);
|
|
PASS();
|
|
}
|
|
|
|
static bool register_solver_chain(solver_fixture_t *fixture, int count, bool reverse) {
|
|
for (int at = 0; at < count; at++) {
|
|
int i = reverse ? count - at - 1 : at;
|
|
char key[32], dependency[32];
|
|
snprintf(key, sizeof(key), "name_%03d", i);
|
|
snprintf(dependency, sizeof(dependency), "name_%03d", i + 1);
|
|
if (!(i == count - 1 ? solver_leaf(fixture, key, "project.actual", CBM_SOLVER_KNOWN)
|
|
: solver_copy(fixture, key, dependency)))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
TEST(resolver_solver_registration_permutations_have_same_summary_and_linear_work) {
|
|
solver_fixture_t forward, reverse;
|
|
ASSERT(solver_fixture_init(&forward, (cbm_resolve_solver_limits_t){0}));
|
|
ASSERT(solver_fixture_init(&reverse, (cbm_resolve_solver_limits_t){0}));
|
|
ASSERT(register_solver_chain(&forward, 64, false));
|
|
ASSERT(register_solver_chain(&reverse, 64, true));
|
|
ASSERT(cbm_resolve_solver_solve(forward.solver));
|
|
ASSERT(cbm_resolve_solver_solve(reverse.solver));
|
|
ASSERT_EQ(cbm_resolve_solver_steps(forward.solver), 64 + 3 * 63);
|
|
ASSERT_EQ(cbm_resolve_solver_steps(reverse.solver), 64 + 3 * 63);
|
|
ASSERT_EQ(cbm_resolve_solver_count(forward.solver), 64);
|
|
for (size_t i = 0; i < 64; i++) {
|
|
const cbm_resolve_solver_result_t *left = cbm_resolve_solver_at(forward.solver, i);
|
|
const cbm_resolve_solver_result_t *right = cbm_resolve_solver_at(reverse.solver, i);
|
|
ASSERT_STR_EQ(left->key, right->key);
|
|
ASSERT_EQ(left->status, CBM_SOLVER_KNOWN);
|
|
ASSERT_EQ(right->status, CBM_SOLVER_KNOWN);
|
|
ASSERT_STR_EQ(left->value.target, right->value.target);
|
|
ASSERT_EQ(cbm_resolve_proof_compare(left->proof, right->proof), 0);
|
|
}
|
|
cbm_arena_destroy(&forward.arena);
|
|
cbm_arena_destroy(&reverse.arena);
|
|
PASS();
|
|
}
|
|
|
|
TEST(resolver_solver_conflicting_registrations_fail_closed_in_both_orders) {
|
|
for (int reverse = 0; reverse < 2; reverse++) {
|
|
solver_fixture_t fixture;
|
|
ASSERT(solver_fixture_init(&fixture, (cbm_resolve_solver_limits_t){0}));
|
|
ASSERT(solver_leaf(&fixture, "duplicate", reverse ? "B" : "A", CBM_SOLVER_KNOWN));
|
|
ASSERT(solver_leaf(&fixture, "duplicate", reverse ? "A" : "B", CBM_SOLVER_KNOWN));
|
|
ASSERT(solver_leaf(&fixture, "other", "C", CBM_SOLVER_KNOWN));
|
|
ASSERT(!cbm_resolve_solver_solve(fixture.solver));
|
|
cbm_resolve_solver_result_t duplicate, other;
|
|
cbm_resolve_solver_get(fixture.solver, "duplicate", &duplicate);
|
|
cbm_resolve_solver_get(fixture.solver, "other", &other);
|
|
ASSERT_EQ(duplicate.status, CBM_SOLVER_FAILED);
|
|
ASSERT_EQ(other.status, CBM_SOLVER_FAILED);
|
|
ASSERT_NULL(duplicate.value.target);
|
|
ASSERT_NULL(duplicate.proof);
|
|
ASSERT(!duplicate.predecessors_closed);
|
|
cbm_arena_destroy(&fixture.arena);
|
|
}
|
|
PASS();
|
|
}
|
|
|
|
TEST(resolver_solver_limits_abstain_globally_independent_of_registration_order) {
|
|
const cbm_resolve_solver_limits_t limits[] = {{.nodes = 2}, {.edges = 1}, {.steps = 8}};
|
|
for (size_t limit = 0; limit < sizeof(limits) / sizeof(limits[0]); limit++) {
|
|
for (int reverse = 0; reverse < 2; reverse++) {
|
|
solver_fixture_t fixture;
|
|
ASSERT(solver_fixture_init(&fixture, limits[limit]));
|
|
ASSERT(register_solver_chain(&fixture, 3, reverse != 0));
|
|
ASSERT(cbm_resolve_solver_solve(fixture.solver));
|
|
ASSERT(!cbm_resolve_solver_complete(fixture.solver));
|
|
ASSERT_EQ(cbm_resolve_solver_count(fixture.solver), 0);
|
|
ASSERT_NULL(cbm_resolve_solver_at(fixture.solver, 0));
|
|
for (int i = 0; i < 3; i++) {
|
|
char key[32];
|
|
snprintf(key, sizeof(key), "name_%03d", i);
|
|
cbm_resolve_solver_result_t result;
|
|
cbm_resolve_solver_get(fixture.solver, key, &result);
|
|
ASSERT_EQ(result.status, CBM_SOLVER_LIMITED);
|
|
ASSERT_NULL(result.value.target);
|
|
ASSERT_NULL(result.proof);
|
|
ASSERT(!result.predecessors_closed);
|
|
}
|
|
cbm_arena_destroy(&fixture.arena);
|
|
}
|
|
}
|
|
PASS();
|
|
}
|
|
|
|
SUITE(resolver_solver) {
|
|
RUN_TEST(resolver_solver_pending_is_not_unknown_and_copy_settles);
|
|
RUN_TEST(resolver_solver_join_preserves_unknown_and_unsupported_competitors);
|
|
RUN_TEST(resolver_solver_join_compares_target_kind_and_module_identity);
|
|
RUN_TEST(resolver_solver_equal_join_retains_every_support);
|
|
RUN_TEST(resolver_solver_missing_dependencies_and_cycles_never_borrow_known_arm);
|
|
RUN_TEST(resolver_solver_registration_permutations_have_same_summary_and_linear_work);
|
|
RUN_TEST(resolver_solver_conflicting_registrations_fail_closed_in_both_orders);
|
|
RUN_TEST(resolver_solver_limits_abstain_globally_independent_of_registration_order);
|
|
}
|