Skip to content

Commit 9849ec9

Browse files
committed
feat(test): conservative coverage selection ladder
1 parent 61ed14d commit 9849ec9

2 files changed

Lines changed: 101 additions & 1 deletion

File tree

toolchain/mfc/test/coverage.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,51 @@ def load_map(path: Path) -> Tuple[Optional[dict], Optional[dict]]:
6767
return None, None
6868
meta = data.pop("_meta")
6969
return data, meta
70+
71+
72+
def _covered_fpp(coverage_map: dict) -> set:
73+
files = set()
74+
for cov in coverage_map.values():
75+
files.update(cov)
76+
return files
77+
78+
79+
def select_tests(cases, coverage_map, changed_files):
80+
"""Return (to_run, skipped, reason). Conservative ladder -- only over-includes.
81+
82+
`cases` items expose `.coverage_key()`. `changed_files` is a set of repo-relative
83+
paths, or None if detection failed.
84+
"""
85+
# Rung 1: no changed-file info -> run all.
86+
if changed_files is None:
87+
return list(cases), [], "rung1: changed-file list unavailable"
88+
89+
# Rung 2: macro/codegen/build inputs -> run all.
90+
if is_always_run_all(changed_files):
91+
return list(cases), [], "rung2: macro/codegen/build input changed"
92+
93+
# Rung 3: changed .f90/.f under src/ (map tracks .fpp only) -> run all.
94+
if any(f.startswith("src/") and f.endswith((".f90", ".f")) for f in changed_files):
95+
return list(cases), [], "rung3: hand-written .f90/.f changed"
96+
97+
changed_fpp = {f for f in changed_files if f.endswith(".fpp")}
98+
if not changed_fpp:
99+
return [], list(cases), "rung7: no Fortran source changed"
100+
101+
# Rung 4: a changed .fpp that no test covers -> run all (GPU-only blind spot).
102+
covered = _covered_fpp(coverage_map)
103+
if changed_fpp - covered:
104+
return list(cases), [], "rung4: changed .fpp not covered by any test"
105+
106+
# Rungs 5-7: per-test.
107+
to_run, skipped = [], []
108+
for case in cases:
109+
key = case.coverage_key()
110+
cov = coverage_map.get(key)
111+
if cov is None: # rung 5: unmapped/new test
112+
to_run.append(case)
113+
elif set(cov) & changed_fpp: # rung 6: overlap
114+
to_run.append(case)
115+
else: # rung 7: skip
116+
skipped.append(case)
117+
return to_run, skipped, f"selected {len(to_run)}/{len(cases)} by coverage overlap"

toolchain/mfc/test/test_coverage_unit.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import tempfile
22
from pathlib import Path
33

4-
from mfc.test.coverage import is_always_run_all, load_map, param_hash, save_map
4+
from mfc.test.coverage import is_always_run_all, load_map, param_hash, save_map, select_tests
55

66

77
def test_param_hash_is_order_independent():
@@ -67,3 +67,55 @@ def test_ordinary_common_module_does_not_force_all():
6767

6868
def test_ordinary_sim_module_does_not_force_all():
6969
assert not is_always_run_all({"src/simulation/m_rhs.fpp"})
70+
71+
72+
class _Case:
73+
def __init__(self, ph, params=None):
74+
self._ph = ph
75+
self.params = params or {}
76+
77+
def coverage_key(self):
78+
return self._ph
79+
80+
81+
def _cases(*phs):
82+
return [_Case(p) for p in phs]
83+
84+
85+
def test_rung1_no_changed_files_runs_all():
86+
cases = _cases("a", "b")
87+
run, skip, reason = select_tests(cases, {"a": ["src/x.fpp"]}, None)
88+
assert len(run) == 2 and skip == [] and reason.startswith("rung1")
89+
90+
91+
def test_rung2_always_run_all():
92+
cases = _cases("a", "b")
93+
run, skip, reason = select_tests(cases, {"a": [], "b": []}, {"CMakeLists.txt"})
94+
assert len(run) == 2 and reason.startswith("rung2")
95+
96+
97+
def test_rung3_f90_change_runs_all():
98+
cases = _cases("a")
99+
run, skip, reason = select_tests(cases, {"a": []}, {"src/common/m_precision_select.f90"})
100+
assert len(run) == 1 and reason.startswith("rung3")
101+
102+
103+
def test_rung4_changed_fpp_with_zero_coverage_runs_all():
104+
cases = _cases("a")
105+
# m_gpu_only.fpp is covered by no test in the map
106+
run, skip, reason = select_tests(cases, {"a": ["src/simulation/m_rhs.fpp"]}, {"src/simulation/m_gpu_only.fpp"})
107+
assert len(run) == 1 and reason.startswith("rung4")
108+
109+
110+
def test_rung5_unmapped_test_is_included():
111+
cases = _cases("a", "new") # 'new' not in map
112+
run, skip, _ = select_tests(cases, {"a": ["src/simulation/m_rhs.fpp"]}, {"src/simulation/m_rhs.fpp"})
113+
assert {c.coverage_key() for c in run} == {"a", "new"}
114+
115+
116+
def test_rung6_and_7_overlap_selects_subset():
117+
cases = _cases("hit", "miss")
118+
cov = {"hit": ["src/simulation/m_bubbles_EE.fpp"], "miss": ["src/simulation/m_rhs.fpp"]}
119+
run, skip, _ = select_tests(cases, cov, {"src/simulation/m_bubbles_EE.fpp"})
120+
assert [c.coverage_key() for c in run] == ["hit"]
121+
assert [c.coverage_key() for c in skip] == ["miss"]

0 commit comments

Comments
 (0)