Files
codebase-memory-mcp/tests/test_coverage_merge.sh
T
Martin Vogel 36e5874525 feat(test-impact): team artifact bundles and admitted coverage
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>
2026-10-05 18:52:26 +02:00

62 lines
3.5 KiB
Bash

#!/usr/bin/env bash
# test_coverage_merge.sh — contract of scripts/test-impact/coverage-merge.py,
# the incremental coverage map of a main-branch push: re-run suites come from
# the fresh map verbatim, every other listed suite is carried with its function
# ids remapped by (file, name) onto the new table, a function the new image no
# longer has drops out, an unlisted suite is dropped, and the metadata is
# recounted so the C reader checks it like a full map.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
MERGE="$ROOT/scripts/test-impact/coverage-merge.py"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
fail() { echo "FAIL: $*" >&2; exit 1; }
mkdir -p "$TMP/previous" "$TMP/fresh"
# Previous image: f, g, gone. Suite a covers f+g, b covers g+gone, old is gone.
printf '0\tsrc/x.c\tf\n1\tsrc/x.c\tg\n2\tsrc/y.c\tgone\n' > "$TMP/previous/functions.tsv"
printf 'a:*\tcomplete\t\t\na:t1\tcomplete\t\t0 1\nb:*\tcomplete\t\t\nb:t2\tincomplete\tkilled child\t1 2\nold:*\tcomplete\t\t\nold:t3\tcomplete\t\t0\n' \
> "$TMP/previous/tests.tsv"
cat > "$TMP/previous/meta.json" <<'EOF'
{"format": 1, "commit": "1111111111111111111111111111111111111111", "platform": "p",
"llvm_profdata": "v", "functions": 3, "functions_compiled": 3, "tests": 3, "incomplete": 1,
"suites": [{"suite": "a", "tests": 1, "incomplete": 0, "exit": 0, "wall_s": 1.0},
{"suite": "b", "tests": 1, "incomplete": 1, "exit": 0, "wall_s": 2.0},
{"suite": "old", "tests": 1, "incomplete": 0, "exit": 0, "wall_s": 3.0}]}
EOF
# New image: ids shifted, `gone` removed, `h` new. Only suite a re-ran.
printf '0\tsrc/x.c\th\n1\tsrc/x.c\tf\n2\tsrc/x.c\tg\n' > "$TMP/fresh/functions.tsv"
printf 'a:*\tcomplete\t\t\na:t1\tcomplete\t\t0 1\n' > "$TMP/fresh/tests.tsv"
cat > "$TMP/fresh/meta.json" <<'EOF'
{"format": 1, "commit": "2222222222222222222222222222222222222222", "platform": "p",
"llvm_profdata": "v", "functions": 3, "functions_compiled": 3, "tests": 1, "incomplete": 0,
"suites": [{"suite": "a", "tests": 1, "incomplete": 0, "exit": 0, "wall_s": 1.5}]}
EOF
printf 'a\nb\n' > "$TMP/listed.txt"
python3 "$MERGE" --previous "$TMP/previous" --fresh "$TMP/fresh" --suites "$TMP/listed.txt" \
--commit 3333333333333333333333333333333333333333 --out "$TMP/out" > /dev/null
cmp -s "$TMP/out/functions.tsv" "$TMP/fresh/functions.tsv" || fail "the function table is not the new image's"
expected="$(printf 'a:*\tcomplete\t\t\na:t1\tcomplete\t\t0 1\nb:*\tcomplete\t\t\nb:t2\tincomplete\tkilled child\t2\n')"
[ "$(cat "$TMP/out/tests.tsv")" = "$expected" ] || fail "merged rows: $(cat "$TMP/out/tests.tsv")"
python3 - "$TMP/out/meta.json" <<'EOF' || fail "merged metadata"
import json, sys
meta = json.load(open(sys.argv[1]))
assert meta["commit"] == "3" * 40, meta["commit"]
assert (meta["tests"], meta["incomplete"]) == (2, 1), (meta["tests"], meta["incomplete"])
suites = {s["suite"]: s for s in meta["suites"]}
assert sorted(suites) == ["a", "b"], sorted(suites)
assert (suites["a"]["tests"], suites["a"]["wall_s"]) == (1, 1.5), suites["a"]
assert (suites["b"]["tests"], suites["b"]["incomplete"]) == (1, 1), suites["b"]
EOF
# A re-run suite the new runner does not list is an inconsistent input.
printf 'b\n' > "$TMP/listed-wrong.txt"
if python3 "$MERGE" --previous "$TMP/previous" --fresh "$TMP/fresh" --suites "$TMP/listed-wrong.txt" \
--commit 3333333333333333333333333333333333333333 --out "$TMP/out2" > /dev/null 2>&1; then
fail "a re-run suite missing from the runner's list was accepted"
fi
echo "coverage merge: all cases passed"