Commit f02f5f2
authored
Coverage-based test selection (done right) — shadow mode (#1461)
* docs: design spec for coverage-based test selection (done right)
* docs: implementation plan for coverage-based test selection
* feat(test): add stable param_hash key for coverage selection
* feat(test): coverage map load/save with freshness metadata
* feat(test): ALWAYS_RUN_ALL classification for coverage selection
* feat(test): conservative coverage selection ladder
* feat(test): TestCase.coverage_key() = param_hash(params)
* feat(test): robust changed-file detection (CI list + self-healing git)
* feat(test): coverage selection summary line
* feat(test): coverage map health check (loud anti-rot)
* fix(test): key coverage_key on full params, not mods (soundness)
* feat(test): gcov coverage-map collector, keyed by param_hash
* feat(test): wire --only-changes (shadow) and --build-coverage-map
* ci: coverage map refresh + health workflows
* fix(test): coverage_key on TestCaseBuilder + treat empty coverage as run (soundness)
* feat(test): accept space/comma/newline separated --changed-files (CI paths-filter)
* fix(test): force full run on src/**/include/ changes (gcov can't attribute includes)
* Delete docs/superpowers/plans/2026-05-29-coverage-test-selection.md
* Delete docs/superpowers directory
* fix(test): empty --changed-files is uncertainty (run all), not skip-all
* fix(test): ALWAYS_RUN_ALL covers test/run infra; cases.py runs new tests via rung 5 (soundness)
* fix(coverage): exclude failed/partial-coverage tests from coverage map
Fix 1: in build_coverage_map Phase 2, a test with non-empty failures produced
only partial .gcda files (crash mid-pipeline). Previously those tests were
still added to test_results, and their truncated coverage was cached. A later
.fpp change that ran only in the missing stage would be silently skipped.
Fix: when failures is non-empty, record in all_failures and continue without
adding to test_results — absent entries are conservatively included by
select_tests (rung 5).
Fix 2: in _parse_gcov_json_output, a mid-stream json.JSONDecodeError returned
the partial result set, which is untrustworthy (the truncated JSON stream may
be missing coverage for .fpp files that were not yet serialised). Fix: return
None on that error path so the caller omits the test from the map entirely.
Fix 6 (comment): correct the post_process comment (~line 347) — the regular
suite runs post_process only under --test-all (which CI sets), not never.
* fix(coverage): broaden always-run-all and rung3 Fortran extension matching
Fix 3: ALWAYS_RUN_ALL_EXACT + prefixes enumerated only a handful of toolchain
files, missing case.py, build.py, common.py, state.py, sched.py, etc. Any
toolchain/mfc/*.py change (except cases.py) affects every test's generation
or execution, so under-enumeration was unsound. Replace with a catch-all:
any(f.startswith('toolchain/mfc/') and f.endswith('.py') and f != CASES_PY).
Drop the now-redundant individual file entries and toolchain/mfc/params/ and
toolchain/mfc/run/ prefixes (all subsumed). Keep CMakeLists.txt,
toolchain/cmake/, toolchain/bootstrap/, and src include rules.
Fix 4: rung 3 matched only .f90 and .f, missing .F90, .F95, .F03, .F08, .FOR
and all other uppercase/mixed variants. Changed files ending in those extensions
under src/ would fall through to per-test selection against a coverage map that
only tracks .fpp, causing silent under-inclusion. Fix: case-insensitive match
against the full tuple (.f90, .f, .f95, .f03, .f08, .for).
Tests: add three new unit tests covering the above fixes.
* ci(coverage-refresh): add contents:write permission and document push caveat
Add 'permissions: contents: write' at the workflow top level so the
coverage-refresh job is authorized to commit and push the updated
coverage_map.json.gz back to master. Without this, the GITHUB_TOKEN has
only read permissions in newer default permission settings.
Also add a comment on the git push step noting that branch protection
may still reject the default GITHUB_TOKEN and that a PAT or GitHub App
with bypass-branch-protection permission may be needed.
* ci: coverage selection shadow mode on self-hosted jobs (git-detected changed files)
* fix(coverage): invert ALWAYS_RUN_ALL to unattributable-catch-all and validate map shape
Fix 1: Replace the allowlist of run-all files with an inverted design: define a
small, conservative allowlist of provably test-irrelevant files (docs/*.md, LICENSE,
etc.) and treat any changed file that is not .fpp, not cases.py, and not in that
allowlist as unattributable -> run all. This closes the gap where mfc.sh,
.github/**, tests/**, toolchain/pyproject.toml, and similar files would silently
fall through to rung-7 (skip-all) under --select-enforce.
Fix 2: In load_map, validate that every entry is str -> list[str] after popping
_meta. A malformed entry returns (None, None) so the caller runs the full suite
rather than silently misrouting tests.
Tests: expand test_docs_only_still_skips_all to cover docs/, LICENSE, .claude/;
add test_unattributable_nonsource_change_runs_all for mfc.sh / pyproject.toml /
tests/** / .github/workflows; add test_load_map_rejects_malformed_entry.
* fix(cli): clarify --changed-files help to mention space-separated input
* fix(ci): gate --changed-files with --only-changes via bash array (PR-only)
* fix(coverage-build): add mpiexec as fallback MPI launcher between mpirun and srun1 parent 574e53d commit f02f5f2
13 files changed
Lines changed: 1343 additions & 2 deletions
File tree
- .github
- scripts
- workflows
- common
- tests
- toolchain/mfc
- cli
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
60 | | - | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
| 56 | + | |
56 | 57 | | |
57 | 58 | | |
58 | 59 | | |
| |||
62 | 63 | | |
63 | 64 | | |
64 | 65 | | |
| 66 | + | |
65 | 67 | | |
66 | 68 | | |
67 | 69 | | |
| |||
262 | 264 | | |
263 | 265 | | |
264 | 266 | | |
265 | | - | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
266 | 273 | | |
267 | 274 | | |
268 | 275 | | |
269 | 276 | | |
| 277 | + | |
270 | 278 | | |
271 | 279 | | |
272 | 280 | | |
| |||
Binary file not shown.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
464 | 464 | | |
465 | 465 | | |
466 | 466 | | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
467 | 486 | | |
468 | 487 | | |
469 | 488 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
246 | 246 | | |
247 | 247 | | |
248 | 248 | | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
249 | 254 | | |
250 | 255 | | |
251 | 256 | | |
| |||
371 | 376 | | |
372 | 377 | | |
373 | 378 | | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
374 | 382 | | |
375 | 383 | | |
376 | 384 | | |
| |||
0 commit comments