Skip to content

Mutants

Mutants #177

Workflow file for this run

# SPDX-FileCopyrightText: RAprogramm <andrey.rozanov.vl@gmail.com>
# SPDX-License-Identifier: MIT
# Mutation testing is the strongest available evidence that line/branch
# coverage is not coverage theatre. cargo-mutants injects mutations into
# the source and reports those that survive the test suite — a survived
# mutant is a behaviour the tests do not exercise.
#
# The full run happens nightly (cron) and on demand (workflow_dispatch);
# a single full mutants run can take 30-60 minutes, so it stays out of
# the PR critical path. Pull requests get a scoped `--in-diff` pass
# instead: only mutants touching the PR's changed code are generated,
# which keeps the signal per-change without the full-run cost. The
# in-diff pass is advisory (continue-on-error) until its runtime on this
# repo's PR sizes is established.
name: Mutants
on:
schedule:
- cron: "0 4 * * *"
workflow_dispatch:
pull_request:
branches:
- main
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
CARGO_INCREMENTAL: 0
jobs:
mutants:
name: cargo-mutants
runs-on: ubuntu-latest
timeout-minutes: 90
if: github.event_name != 'pull_request'
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # master
with:
toolchain: stable
- name: Load cached target directory
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
key: mutants
save-if: true
- name: Install cargo-mutants
uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5
with:
tool: cargo-mutants
- name: Run cargo-mutants
continue-on-error: true
run: cargo mutants --no-shuffle --timeout-multiplier 2 --output mutants.out
- name: Append mutants summary to workflow output
if: always()
run: |
set -euo pipefail
if [ -f mutants.out/outcomes.json ]; then
total=$(jq '[.outcomes[]] | length' mutants.out/outcomes.json)
caught=$(jq '[.outcomes[] | select(.summary == "CaughtMutant")] | length' mutants.out/outcomes.json)
missed=$(jq '[.outcomes[] | select(.summary == "MissedMutant")] | length' mutants.out/outcomes.json)
timeout=$(jq '[.outcomes[] | select(.summary == "Timeout")] | length' mutants.out/outcomes.json)
unviable=$(jq '[.outcomes[] | select(.summary == "Unviable")] | length' mutants.out/outcomes.json)
{
echo "## Mutation testing"
echo
printf '| Metric | Count |\n'
printf '|---|---:|\n'
printf '| Total mutants | %s |\n' "$total"
printf '| Caught | %s |\n' "$caught"
printf '| Missed (survived) | %s |\n' "$missed"
printf '| Timeout | %s |\n' "$timeout"
printf '| Unviable | %s |\n' "$unviable"
} >> "$GITHUB_STEP_SUMMARY"
else
echo "(mutants.out/outcomes.json not produced)" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Upload mutants report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: mutants-report
path: mutants.out/
retention-days: 30
mutants-diff:
name: cargo-mutants (PR diff)
runs-on: ubuntu-latest
timeout-minutes: 45
if: github.event_name == 'pull_request'
continue-on-error: true
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # master
with:
toolchain: stable
- name: Load cached target directory
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
key: mutants-diff
save-if: false
- name: Install cargo-mutants
uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5
with:
tool: cargo-mutants
- name: Compute the PR diff
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: git diff "$BASE_SHA"...HEAD > pr.diff
- name: Run cargo-mutants on the diff
run: cargo mutants --no-shuffle --timeout-multiplier 2 --in-diff pr.diff --output mutants.out
- name: Append mutants summary to workflow output
if: always()
run: |
set -euo pipefail
if [ -f mutants.out/outcomes.json ]; then
total=$(jq '[.outcomes[]] | length' mutants.out/outcomes.json)
caught=$(jq '[.outcomes[] | select(.summary == "CaughtMutant")] | length' mutants.out/outcomes.json)
missed=$(jq '[.outcomes[] | select(.summary == "MissedMutant")] | length' mutants.out/outcomes.json)
{
echo "## Mutation testing (PR diff)"
echo
printf '| Metric | Count |\n'
printf '|---|---:|\n'
printf '| Total mutants in diff | %s |\n' "$total"
printf '| Caught | %s |\n' "$caught"
printf '| Missed (survived) | %s |\n' "$missed"
} >> "$GITHUB_STEP_SUMMARY"
else
echo "No mutants report produced" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Upload mutants report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: mutants-diff-report
path: mutants.out
retention-days: 14