mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
A new pr.yml job, test-impact-shadow, predicts from the code graph which C test suites a change can affect and uploads the prediction, so graph-based selection can be judged against what actually failed before anything gates on it. It is continue-on-error and not in ci-ok's needs: it can never block or pass a merge. scripts/ci/test-impact-shadow.sh --fetch-release downloads the latest published release archive for the host plus checksums.txt with the job's read-only token and verifies the SHA-256 (seconds instead of a build, and a PR that breaks the indexer cannot corrupt its own prediction); pr.yml builds the checkout only if that fails. --binary/--out then indexes the checkout into a throwaway HOME/cache with a private CBM_RUNTIME_DIR (short, under /tmp, so the daemon socket fits sun_path), runs detect_changes against the merge commit's base at depth 15 and hands it to predict.py. The graph is only consulted when C sources changed. scripts/test-impact/predict.py ports the replay's mapping: impacted test functions -> registered suites (perf excluded; nested RUN_SUITE children resolve to the registered suite that runs them), changed test files -> whole suite, fixtures -> suites that name them, grammars -> grammar suites, and run-all on the selector's full, a test header, test infrastructure, or a saturated/truncated impact set. It writes prediction.json and a short step-summary table. tests/test_test_impact_predict.sh (Step 0z5) pins the mapping on a synthetic tree; the wiring contract requires the job to stay continue-on-error; the script joins the venue-parity interface probes. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
140 lines
6.6 KiB
Bash
Executable File
140 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Test-impact prediction contract: scripts/test-impact/predict.py turns a
|
|
# detect_changes impact set into the C test suites a change can affect. It
|
|
# feeds the PR test-impact SHADOW job -- report-only, never a gate -- but a
|
|
# shadow that silently under-predicts would make its own evaluation lie, so
|
|
# the mapping and its fallbacks are pinned on a synthetic tree:
|
|
# - impacted test functions map to their registered suites (perf excluded;
|
|
# a nested suite resolves to the registered suite that runs it);
|
|
# - a changed test file always selects its whole suite;
|
|
# - a changed fixture selects the suites whose tests name it;
|
|
# - run-all: the selector's full, a test header, a saturated or truncated
|
|
# impact set -> every suite;
|
|
# - no C change -> the graph is not consulted, nothing is predicted.
|
|
#
|
|
# Usage: tests/test_test_impact_predict.sh [repo-root]
|
|
|
|
set -euo pipefail
|
|
|
|
ROOT="${1:-$(cd "$(dirname "$0")/.." && pwd)}"
|
|
PREDICT="$ROOT/scripts/test-impact/predict.py"
|
|
if [ ! -f "$PREDICT" ]; then
|
|
echo "FAIL: $PREDICT not found" >&2
|
|
exit 1
|
|
fi
|
|
WORK=$(mktemp -d "${TMPDIR:-/tmp}/cbm-impact-predict.XXXXXX")
|
|
trap 'rm -rf "$WORK"' EXIT
|
|
|
|
python3 - "$PREDICT" "$WORK" <<'PY'
|
|
import json
|
|
import pathlib
|
|
import subprocess
|
|
import sys
|
|
|
|
predict, work = sys.argv[1], pathlib.Path(sys.argv[2])
|
|
tree = work / "tree"
|
|
(tree / "tests").mkdir(parents=True)
|
|
(tree / "tests" / "test_main.c").write_text(
|
|
"RUN_SELECTED_SUITE(alpha);\nRUN_SELECTED_SUITE(beta);\n"
|
|
"RUN_SELECTED_SUITE(gamma);\nRUN_SELECTED_SUITE_PERF(delta);\n")
|
|
(tree / "tests" / "test_alpha.c").write_text(
|
|
"TEST(a1) { }\nTEST(a2) { }\nSUITE(alpha) {\n RUN_TEST(a1);\n RUN_TEST(a2);\n}\n")
|
|
(tree / "tests" / "test_beta.c").write_text(
|
|
'TEST(b1) { load("tests/fixtures/kx/in.c"); }\nSUITE(beta) {\n RUN_TEST(b1);\n}\n')
|
|
(tree / "tests" / "test_gamma.c").write_text(
|
|
"TEST(g1) { }\nvoid suite_gamma(void) {\n RUN_TEST(g1);\n}\n")
|
|
(tree / "tests" / "test_delta.c").write_text("TEST(d1) { }\nSUITE(delta) {\n RUN_TEST(d1);\n}\n")
|
|
# A nested suite runs only inside a registered parent (RUN_SUITE in its body).
|
|
(tree / "tests" / "test_nested.c").write_text(
|
|
"TEST(n1) { }\nSUITE(inner) {\n RUN_TEST(n1);\n}\nSUITE(outer) {\n RUN_SUITE(inner);\n}\n")
|
|
(tree / "tests" / "test_main.c").write_text(
|
|
(tree / "tests" / "test_main.c").read_text() + "RUN_SELECTED_SUITE(outer);\n")
|
|
failures = []
|
|
|
|
|
|
def row(qn, label, file, hop):
|
|
return {"qn": qn, "label": label, "file": file, "hop": hop}
|
|
|
|
|
|
def predict_for(name, changed, selection, detect=None):
|
|
case = work / name
|
|
case.mkdir()
|
|
(case / "changed.txt").write_text("".join(f + "\n" for f in changed))
|
|
(case / "selection.json").write_text(json.dumps(selection))
|
|
args = [sys.executable, predict, "--root", str(tree), "--changed", str(case / "changed.txt"),
|
|
"--selection", str(case / "selection.json"), "--out", str(case / "p.json"),
|
|
"--summary", str(case / "s.md")]
|
|
if detect is not None:
|
|
(case / "detect.json").write_text(json.dumps(detect))
|
|
args += ["--detect", str(case / "detect.json")]
|
|
proc = subprocess.run(args, capture_output=True, text=True)
|
|
if proc.returncode != 0:
|
|
failures.append(f"{name}: predict.py exited {proc.returncode}: {proc.stderr.strip()}")
|
|
return None
|
|
return json.loads((case / "p.json").read_text())
|
|
|
|
|
|
def expect(name, got, mode, suites):
|
|
if got is None:
|
|
return
|
|
if got["mode"] != mode:
|
|
failures.append(f"{name}: mode {got['mode']}, expected {mode}")
|
|
if got["suites"] != suites:
|
|
failures.append(f"{name}: suites {got['suites']}, expected {suites}")
|
|
|
|
|
|
NARROW = {"tier": "T3-mid", "full": False}
|
|
graph = {"impacted_total": 3, "impacted_shown": 3, "impacted": [
|
|
row("p.src.store.store_open", "Function", "src/store/store.c", 1),
|
|
row("p.tests.test_alpha.a1", "Function", "tests/test_alpha.c", 2),
|
|
row("p.tests.test_alpha.alpha", "Function", "tests/test_alpha.c", 3),
|
|
row("p.tests.test_delta.d1", "Function", "tests/test_delta.c", 2)]}
|
|
|
|
expect("graph rows map to suites, perf excluded",
|
|
predict_for("graph", ["src/store/store.c"], NARROW, graph), "graph", {"alpha": ["a1"]})
|
|
expect("a test in a nested suite maps to its registered parent",
|
|
predict_for("nested", ["src/store/store.c"], NARROW,
|
|
{"impacted_total": 1, "impacted_shown": 1,
|
|
"impacted": [row("p.tests.test_nested.n1", "Function", "tests/test_nested.c", 2)]}),
|
|
"graph", {"outer": ["n1"]})
|
|
expect("a changed test file selects its whole suite",
|
|
predict_for("changed-test", ["src/store/store.c", "tests/test_gamma.c"], NARROW, graph),
|
|
"graph", {"alpha": ["a1"], "gamma": "ALL"})
|
|
expect("a changed fixture selects the suites that name it",
|
|
predict_for("fixture", ["tests/fixtures/kx/in.c"], {"tier": "T1-tests", "full": False},
|
|
{"impacted_total": 0, "impacted_shown": 0, "impacted": []}),
|
|
"graph", {"beta": "ALL"})
|
|
ALL = {"alpha": "ALL", "beta": "ALL", "gamma": "ALL", "outer": "ALL"}
|
|
expect("the selector's full runs every suite",
|
|
predict_for("full", ["Makefile.cbm", "src/store/store.c"], {"tier": "T5-core", "full": True}, graph),
|
|
"all", ALL)
|
|
expect("a changed test header runs every suite",
|
|
predict_for("test-header", ["tests/test_helpers.h"], {"tier": "T1-tests", "full": False}, graph),
|
|
"all", ALL)
|
|
expect("a saturated impact set runs every suite",
|
|
predict_for("saturated", ["src/store/store.c"], NARROW, dict(graph, engine_saturated=True)),
|
|
"all", ALL)
|
|
expect("a truncated impact set runs every suite",
|
|
predict_for("truncated", ["src/store/store.c"], NARROW, dict(graph, impacted_total=9000)),
|
|
"all", ALL)
|
|
expect("no C change predicts nothing",
|
|
predict_for("docs", ["README.md"], {"tier": "T0a-docs", "full": False}), "none", {})
|
|
expect("a C change without a graph is not a narrow prediction",
|
|
predict_for("no-graph", ["src/store/store.c"], NARROW), "all", ALL)
|
|
|
|
for changed, want in ((["README.md"], "no"), (["src/store/store.c"], "yes"),
|
|
(["tests/test_alpha.c"], "yes")):
|
|
(work / "needs.txt").write_text("".join(f + "\n" for f in changed))
|
|
out = subprocess.run([sys.executable, predict, "--needs-graph", "--changed",
|
|
str(work / "needs.txt")], capture_output=True, text=True).stdout.strip()
|
|
if out != want:
|
|
failures.append(f"--needs-graph for {changed}: {out!r}, expected {want!r}")
|
|
|
|
if failures:
|
|
print("TEST-IMPACT PREDICTION CONTRACT VIOLATED:")
|
|
for failure in failures:
|
|
print(f" {failure}")
|
|
sys.exit(1)
|
|
print("test-impact prediction contract OK (mapping, fallbacks, run-all triggers)")
|
|
PY
|