Repository navigation
Mutation #10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Mutation | |
| # Coverage says a line ran; mutation testing asks whether anything would have NOTICED had it been | |
| # wrong. It is a review tool, not a gate: most survivors are equivalent mutants (message text no test | |
| # asserts on, formatting, `__repr__`), so the job stays green on survivors and publishes the list to | |
| # triage. Weekly, not nightly - the score only moves when assertpy2/ or tests/ move, and a recurring | |
| # red that means nothing is a red people learn to ignore. | |
| # | |
| # It does go red when the RUN itself is broken. mutmut runs the unmutated suite once to learn which | |
| # tests cover which function, and exits 1 if that fails, so a test the trampoline breaks stops the | |
| # whole run instead of quietly leaving mutants unjudged. That red is never routine: it means the | |
| # config rotted, which is the failure the previous cosmic-ray setup hid for five weeks behind a | |
| # stale module path. | |
| # | |
| # Linux only: mutmut forks and calls os.wait, and does not run on Windows. | |
| on: | |
| schedule: | |
| - cron: "0 4 * * 1" # weekly, Monday 04:00 UTC | |
| workflow_dispatch: {} | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }} | |
| cancel-in-progress: true | |
| jobs: | |
| mutation: | |
| runs-on: ubuntu-latest | |
| # 10 060 mutants over the whole package. Measured on four pinned cores with `--max-children 4`, | |
| # which is this runner's shape: 41m15s. Slower runner cores put the real figure somewhere | |
| # around an hour, so this leaves comfortable headroom without letting a hang burn two hours. | |
| timeout-minutes: 120 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 | |
| - name: Set up Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.14" | |
| - name: Install dependencies | |
| # Every optional integration, matching the full-deps cell of ci.yml. A skipped test cannot | |
| # kill a mutant, so mutating dataframe.py or json_mixin.py without pandas/polars/jsonschema | |
| # installed reports its whole surface as untested. | |
| run: uv sync --locked --extra json --extra data --extra inline --group integrations --group mutation | |
| - name: Run the mutants | |
| # Exits non-zero when it cannot collect stats, which is what a baseline failure looks like. | |
| # Survivors alone do not fail it. | |
| # | |
| # `--no-sync` on every `uv run` below: without it uv re-resolves the environment to the | |
| # project's default groups and uninstalls the extras the step above just installed, so the | |
| # mutants would be judged by a suite that skips pandas, polars and jsonschema. | |
| run: uv run --no-sync mutmut run | |
| - name: Report | |
| if: always() | |
| run: | | |
| uv run --no-sync python scripts/mutation_report.py >> "$GITHUB_STEP_SUMMARY" | |
| uv run --no-sync mutmut results > not-killed.txt | |
| - name: Upload the mutants that lived | |
| # `mutmut results` lists every mutant that was not killed, which is survivors plus timeouts | |
| # plus the ones no test covered. Each line carries its own status, so the file is named for | |
| # what it holds rather than for survivors alone - reading it as a survivor list overcounts. | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: mutants-not-killed | |
| path: not-killed.txt |