Repository navigation
ci: run only the mutants of the changed functions in the mutation gate (ADR-1122) - #898
Conversation
…e (ADR-1122) mutation_check.sh ran `mutmut run` with no name, so mutmut ran every mutant of each changed file and the gate judged the changed functions afterwards. #865 (backend_resolution.py, 262 mutants) was cancelled at the six-hour job limit after 141; #877 ran past two hours. The script now passes one `<prefix>*` name per changed function (scripts/ci_mutation_scope.py --globs). mutmut 3.8.1 asserts when no mutant matches; after a failed run the gate passes only when mutmut's stats file exists and its .meta files hold no mutant in scope, any other failure fails the job. Without MUTATION_ONLY_PREFIXES, every mutant runs as before. Measured on #877's diff (local macOS arm64, PostgreSQL 18.6): 36 mutants instead of 1,164, verdict in 21 min 15 s. No mutant hit mutmut's CPU timeout, so no wall-clock cap is added.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Claude finished @cdeust's task in 3m 23s —— View job Review of PR #898: done
Result: no Blockers and three Notes. Verdict: approve. I could not run |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 31790c8a30
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if [ -n "${MUTATION_ONLY_PREFIXES:-}" ] \ | ||
| && python3 "$ROOT/scripts/ci_mutation_scope.py" --empty-scope "$ROOT/mutants"; then | ||
| exit 0 |
There was a problem hiding this comment.
Require fresh mutation artifacts before accepting a failed run
If mutants/ already contains artifacts from a previous manual mutmut run, a scoped invocation can incorrectly pass without running mutation tests. The script only deletes that directory on exit, so an old stats file and metadata with no mutants matching the current prefixes satisfy --empty-scope even when the current command fails before mutmut starts. I reproduced this with pre-existing stats and a fake uv exiting 42: the gate returned 0. Clear the previous artifacts before starting, or otherwise verify that the stats and metadata belong to the current invocation before converting its failure into success.
Useful? React with 👍 / 👎.
Review 1 of 3: PR #898I ran five reviews: code quality, documentation, performance, security and test coverage. No Blockers. I could not run the test suite in this environment, so these findings come from reading the code, not from a run. What I traced:
Notes, no action before merge
Verdict: approve |
… meta for an empty scope Review 1 of #898 notes: - A failure of `ci_mutation_scope.py --globs` inside `< <(...)` did not stop the script: NAMES stayed empty and every mutant ran. The names are now read from an assignment, so `set -e` stops the gate. - `--empty-scope` passed when the mutated file's `.meta` was missing. It now takes the mutated files and concludes only when each `.meta` exists; an empty file list is a usage error. - The shell tests used rc=1 for the fake failures; they now use 3, so the script must forward mutmut's own exit code.
|
Claude finished @cdeust's task in 2m 12s —— View job Review 2 of 3: PR #898, done
Result: no Blockers. The three notes from review 1 are fixed and each fix has a test. There are three new Notes, the first about code already on main. Codex's comment on stale Verdict: approve |
Review 2 of 3: PR #898Blockers from review 1: there were none. This review covers only The three notes from review 1 are fixed:
Codex's comment on stale
Notes, no action before merge
Verdict: approve |
Summary
The mutation gate (ADR-1122) ran
mutmut runwith no name, so mutmut ran every mutant of each changed file and the gate judged only the changed functions afterwards (MUTATION_ONLY_PREFIXES). Theunitjob of a PR that touches a large module therefore outlived the six-hour job limit:backend_resolution.py, 262 mutants): cancelled at 6 h after 141 mutants, run 42044899519.unitstill running more than 2 h after they started.scripts/mutation_check.shnow passes one<prefix>*name per changed function tomutmut run(scripts/ci_mutation_scope.py --globs), so only the mutants of the changed functions run. WithoutMUTATION_ONLY_PREFIXES(a manual run), every mutant runs as before.mutmut 3.8.1 asserts when no mutant matches the names, for example when a changed function holds nothing to mutate. After a failed run, the gate passes only when mutmut's own files show that case:
mutants/mutmut-stats.jsonexists (written only after the stats run succeeds) and the.metafiles hold no mutant in scope. Any other failure fails the job. The check reads files, not error text.Checked in the mutmut 3.8.1 source:
tests_for_mutant_namesmatches a wildcard against function names, finds none for<prefix>*, and hands an empty set to the clean run.run_tests([])runs the whole selection, so the clean-test safeguard keeps its full scope.Mutant resultsblock printed when names are given lists🙁 <name>. Names start withmcp_server, so the🙁 [0-9]+tally still reads the progress line.ADR-1122 is amended in the canonical page and its mirror. No wall-clock cap is added: no mutant hit mutmut's CPU timeout in the measurement, and the ADR keeps "no cap is applied, so no change goes unchecked".
Refs #865
Type of change
Test plan
fix/721merged with main, local macOS arm64, PostgreSQL 18.6 fromlauncher_pg_assets,ci_unit.shwithout the pytest line):remember.py: 26 of 741;domain_mapping.py: about 10 of 423). The gate reaches its verdict in 21 min 15 s, stats and clean-test runs included.remember.py, from the measured 0.07 mutants/s. This is an extrapolation, not a run._resolve_domain, 1 inresolve_explicit_domain). This is a real test gap that fix(remember): keep an explicit domain instead of remapping it by substring (#721, slice 1) #877 owns, not a defect of this PR.tests_py/scripts/test_ci_mutation_scope.py(13 tests) drives the realmutation_check.shagainst a fakeuv, under/bin/bash3.2 and Homebrew bash. It covers:With main's
mutation_check.sh, the scoped-names and empty-scope tests fail (4 failures, both bashes).pytest tests_py/scripts: 1,832 passed. The 41 errors all come fromtest_launcher_platform_coverage.py, whereuv venvhits a broken x86_64 managed interpreter on this host ("Bad CPU type"). They are unrelated to this diff.python scripts/check_craftsmanship.py: OK.ruff checkandruff format --checkpass on the new files.Audit notes
pipefailcapture and the tally parsing, all checked above.fixup!commits.Coding-standards compliance
mcp_serverimport in production code.# source:line.