mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
A main commit can now publish its test-impact bundle — the commit's frozen graph, its per-test coverage map and receipt.json binding both — and a PR selection given the bundle of its merge base, verified by the caller, builds its graph incrementally from the bundle's graph and narrows suites by the admitted map. Without a verified bundle nothing changes: the graph is built from scratch and every reached suite runs whole. Producer and consumer: - `codebase-memory-mcp test-impact publish|select` (in process, no daemon): publish builds the bundle of the checked-out commit, from the previous bundle's graph when that bundle verifies; select is the selection with optional --artifact/--artifact-commit/--artifact-verified. The MCP surface is unchanged. - receipt.json binds the exact imported graph (graph digest), the graph's content and topology, the map's three files and a compatibility digest (platform label + the bytes of test_impact.coverage.compatibility_paths). - A bundle is used only when the caller verified its source, it is the merge base's own, and its imported graph is the one the receipt names; the map is admitted by the existing receipt policy (source, ancestry, content, age, compatibility, metadata). Refusals become rejection reasons in the answer's receipt. - C_eff: a changed function the map knows contributes its own id; one it does not know (new, platform-only, a type or macro) contributes its nearest mapped callers on the static inbound closure; deleted names contribute every mapped function of that name. Graph: - the frozen build accepts a base candidate and repairs it through the incremental closure route; any doubt declines to the full build. Pinned to one worker like the full frozen build. - incremental closure repair skipped the interprocedural complexity pass: re-parsed functions lost transitive_loop_depth/recursive and callers kept stale values. One traversal now serves the in-memory pass and a new store-level recompute that runs before the importance rescore. - test-impact's frozen builds use a fixed display Git context: the per-run pin path and the commit ids made two builds of one tree differ and made every commit a semantic-input change, so repair always declined. - graph content and topology digests (store_content_digest.c): the exact graph digest binds one database (ids, db_uid, generation), never equal across builds; the content digest keys rows by what they say, the topology digest by what selection reads. Measured on this repository (frozen publish, macOS arm64): a two-file C change repairs in 18-20 s instead of 75-80 s with the topology of the full build; a docs-only change repairs with identical content; selections with and without the bundle are identical on all seven replayed pairs (two repaired incrementally, five declined to the full build). Recorded, not fixed: closure repair still differs from a full build in edge PROPERTIES (argument quoting and key order, CONFIGURES args) and in the corpus-wide SIMILAR_TO/SEMANTICALLY_RELATED edges; selection reads neither, so the topology digest is the incremental-equals-full check and the receipt carries both. Tooling: scripts/test-impact/coverage-merge.py builds a push's incremental coverage map (re-run suites verbatim, the rest carried with ids remapped onto the new image's function table), with its contract as test.sh Step 0z7. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
169 lines
7.3 KiB
C
169 lines
7.3 KiB
C
/*
|
|
* test_test_impact_artifact.c — the team artifact receipt and its digests.
|
|
*/
|
|
#include "test_framework.h"
|
|
#include "test_helpers.h"
|
|
|
|
#include "mcp/test_impact_artifact.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
static bool tia_write(const char *dir, const char *name, const char *text) {
|
|
char path[1024];
|
|
snprintf(path, sizeof(path), "%s/%s", dir, name);
|
|
FILE *f = fopen(path, "wb");
|
|
if (!f) {
|
|
return false;
|
|
}
|
|
bool ok = fputs(text, f) >= 0;
|
|
return fclose(f) == 0 && ok;
|
|
}
|
|
|
|
static cbm_ti_receipt_t tia_receipt(bool coverage) {
|
|
cbm_ti_receipt_t r = {0};
|
|
memset(r.commit, 'a', 40);
|
|
memset(r.graph_sha256, 'b', 64);
|
|
memset(r.graph_content_sha256, '9', 64);
|
|
memset(r.graph_topology_sha256, '8', 64);
|
|
snprintf(r.platform, sizeof(r.platform), "linux-x86_64-clang");
|
|
r.has_coverage = coverage;
|
|
if (coverage) {
|
|
memset(r.functions_sha256, 'c', 64);
|
|
memset(r.tests_sha256, 'd', 64);
|
|
memset(r.metadata_sha256, 'e', 64);
|
|
memset(r.compatibility_sha256, 'f', 64);
|
|
r.oldest_observation_at = 1791150000;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
TEST(test_impact_artifact_receipt_round_trips) {
|
|
char *dir = th_mktempdir("cbm_tia_rt");
|
|
ASSERT_NOT_NULL(dir);
|
|
for (int coverage = 0; coverage < 2; coverage++) {
|
|
cbm_ti_receipt_t in = tia_receipt(coverage);
|
|
ASSERT_TRUE(cbm_ti_receipt_write(dir, &in));
|
|
cbm_ti_receipt_t out;
|
|
ASSERT_EQ(cbm_ti_receipt_read(dir, &out), CBM_TI_RECEIPT_OK);
|
|
ASSERT_STR_EQ(out.commit, in.commit);
|
|
ASSERT_STR_EQ(out.graph_sha256, in.graph_sha256);
|
|
ASSERT_STR_EQ(out.graph_content_sha256, in.graph_content_sha256);
|
|
ASSERT_STR_EQ(out.graph_topology_sha256, in.graph_topology_sha256);
|
|
ASSERT_STR_EQ(out.platform, in.platform);
|
|
ASSERT_EQ(out.has_coverage, in.has_coverage);
|
|
ASSERT_STR_EQ(out.functions_sha256, in.functions_sha256);
|
|
ASSERT_STR_EQ(out.compatibility_sha256, in.compatibility_sha256);
|
|
ASSERT_EQ(out.oldest_observation_at, in.oldest_observation_at);
|
|
}
|
|
th_rmtree(dir);
|
|
PASS();
|
|
}
|
|
|
|
TEST(test_impact_artifact_receipt_is_strict) {
|
|
char *dir = th_mktempdir("cbm_tia_strict");
|
|
ASSERT_NOT_NULL(dir);
|
|
cbm_ti_receipt_t out;
|
|
ASSERT_EQ(cbm_ti_receipt_read(dir, &out), CBM_TI_RECEIPT_ABSENT);
|
|
static const char *const bad[] = {
|
|
"not json",
|
|
"{}",
|
|
/* unknown schema */
|
|
"{\"schema\":\"cbm.test_impact.artifact.v2\",\"commit\":\""
|
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"graph_sha256\":\""
|
|
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"graph_content_"
|
|
"sha256\":\"9999999999999999999999999999999999999999999999999999999999999999\","
|
|
"\"graph_topology_sha256\":"
|
|
"\"8888888888888888888888888888888888888888888888888888888888888888\",\"platform\":\"p\","
|
|
"\"coverage\":null}",
|
|
/* a commit of the wrong length */
|
|
"{\"schema\":\"cbm.test_impact.artifact.v1\",\"commit\":\"abc\",\"graph_sha256\":\""
|
|
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"graph_content_"
|
|
"sha256\":\"9999999999999999999999999999999999999999999999999999999999999999\","
|
|
"\"graph_topology_sha256\":"
|
|
"\"8888888888888888888888888888888888888888888888888888888888888888\",\"platform\":\"p\","
|
|
"\"coverage\":null}",
|
|
/* uppercase digest */
|
|
"{\"schema\":\"cbm.test_impact.artifact.v1\",\"commit\":\""
|
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"graph_sha256\":\""
|
|
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\",\"graph_content_"
|
|
"sha256\":\"9999999999999999999999999999999999999999999999999999999999999999\","
|
|
"\"graph_topology_sha256\":"
|
|
"\"8888888888888888888888888888888888888888888888888888888888888888\",\"platform\":\"p\","
|
|
"\"coverage\":null}",
|
|
/* coverage without its observation time */
|
|
"{\"schema\":\"cbm.test_impact.artifact.v1\",\"commit\":\""
|
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"graph_sha256\":\""
|
|
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"graph_content_"
|
|
"sha256\":\"9999999999999999999999999999999999999999999999999999999999999999\","
|
|
"\"graph_topology_sha256\":"
|
|
"\"8888888888888888888888888888888888888888888888888888888888888888\",\"platform\":\"p\","
|
|
"\"coverage\":{\"functions_sha256\":\""
|
|
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"tests_sha256\":\""
|
|
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"metadata_sha256\":\""
|
|
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\","
|
|
"\"compatibility_sha256\":\""
|
|
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\"}}",
|
|
};
|
|
for (size_t i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) {
|
|
ASSERT_TRUE(tia_write(dir, CBM_TI_RECEIPT_FILE, bad[i]));
|
|
ASSERT_EQ(cbm_ti_receipt_read(dir, &out), CBM_TI_RECEIPT_INVALID);
|
|
ASSERT_EQ(out.commit[0], '\0');
|
|
}
|
|
th_rmtree(dir);
|
|
PASS();
|
|
}
|
|
|
|
TEST(test_impact_artifact_compatibility_digest_binds_every_input) {
|
|
char *dir = th_mktempdir("cbm_tia_compat");
|
|
ASSERT_NOT_NULL(dir);
|
|
ASSERT_TRUE(tia_write(dir, "harness.h", "int x;\n"));
|
|
const char *paths[] = {"harness.h", "missing.mk"};
|
|
char base[65], again[65], other[65];
|
|
ASSERT_TRUE(cbm_ti_compatibility_digest(dir, paths, 2, "linux", base));
|
|
ASSERT_TRUE(cbm_ti_compatibility_digest(dir, paths, 2, "linux", again));
|
|
ASSERT_STR_EQ(base, again);
|
|
/* another platform */
|
|
ASSERT_TRUE(cbm_ti_compatibility_digest(dir, paths, 2, "darwin", other));
|
|
ASSERT_TRUE(strcmp(base, other) != 0);
|
|
/* a byte of a listed file */
|
|
ASSERT_TRUE(tia_write(dir, "harness.h", "int y;\n"));
|
|
ASSERT_TRUE(cbm_ti_compatibility_digest(dir, paths, 2, "linux", other));
|
|
ASSERT_TRUE(strcmp(base, other) != 0);
|
|
ASSERT_TRUE(tia_write(dir, "harness.h", "int x;\n"));
|
|
/* an absent listed file appearing, even empty */
|
|
ASSERT_TRUE(tia_write(dir, "missing.mk", ""));
|
|
ASSERT_TRUE(cbm_ti_compatibility_digest(dir, paths, 2, "linux", other));
|
|
ASSERT_TRUE(strcmp(base, other) != 0);
|
|
/* the list itself */
|
|
ASSERT_TRUE(cbm_ti_compatibility_digest(dir, paths, 1, "linux", other));
|
|
ASSERT_TRUE(strcmp(base, other) != 0);
|
|
/* an empty listed path is refused, not hashed */
|
|
const char *empty[] = {""};
|
|
ASSERT_FALSE(cbm_ti_compatibility_digest(dir, empty, 1, "linux", other));
|
|
th_rmtree(dir);
|
|
PASS();
|
|
}
|
|
|
|
TEST(test_impact_artifact_file_digest_is_exact_bytes) {
|
|
char *dir = th_mktempdir("cbm_tia_sha");
|
|
ASSERT_NOT_NULL(dir);
|
|
ASSERT_TRUE(tia_write(dir, "f", "abc"));
|
|
char path[1024];
|
|
snprintf(path, sizeof(path), "%s/f", dir);
|
|
char hex[65];
|
|
ASSERT_TRUE(cbm_ti_sha256_file(path, hex));
|
|
ASSERT_STR_EQ(hex, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
|
|
snprintf(path, sizeof(path), "%s/absent", dir);
|
|
ASSERT_FALSE(cbm_ti_sha256_file(path, hex));
|
|
th_rmtree(dir);
|
|
PASS();
|
|
}
|
|
|
|
SUITE(test_impact_artifact) {
|
|
RUN_TEST(test_impact_artifact_receipt_round_trips);
|
|
RUN_TEST(test_impact_artifact_receipt_is_strict);
|
|
RUN_TEST(test_impact_artifact_compatibility_digest_binds_every_input);
|
|
RUN_TEST(test_impact_artifact_file_digest_is_exact_bytes);
|
|
}
|