name: Mutation Testing
on:
pull_request:
paths:
- 'src/**'
- 'tests/**'
- 'Cargo.toml'
- 'Cargo.lock'
workflow_dispatch:
inputs:
target:
description: 'Target file or module to mutate'
required: false
default: ''
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
jobs:
mutation-test:
name: Mutation Testing with cargo-mutants
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Setup Rust with caching
uses: ./.github/actions/setup-rust-cached
with:
toolchain: stable
cache-key: mutation
- name: Install cargo-mutants
uses: ./.github/actions/install-cargo-tool
with:
tool: cargo-mutants@24.11.0
- name: Run mutation tests
run: |
if [ -n "${{ github.event.inputs.target }}" ]; then
cargo mutants --file "${{ github.event.inputs.target }}" --output mutants.out --json
else
cargo mutants --output mutants.out --json --timeout 300
fi
continue-on-error: true
- name: Generate mutation report
if: always()
run: |
echo "# Mutation Testing Report" > mutation-report.md
echo "" >> mutation-report.md
if [ -f mutants.out/mutants.json ]; then
echo "## Summary" >> mutation-report.md
jq -r '.summary | "- Total mutants: \(.total)\n- Caught: \(.caught)\n- Missed: \(.missed)\n- Timeout: \(.timeout)\n- Score: \(.score)%"' mutants.out/mutants.json >> mutation-report.md
echo "" >> mutation-report.md
echo "## Missed Mutants (Require Attention)" >> mutation-report.md
echo "" >> mutation-report.md
jq -r '.mutants[] | select(.outcome == "missed") | "### \(.function)\n\n**File:** \(.file):\(.line)\n\n**Mutation:** \(.replacement)\n\n---\n"' mutants.out/mutants.json >> mutation-report.md || echo "No missed mutants" >> mutation-report.md
else
echo "Mutation testing output not found" >> mutation-report.md
fi
- name: Upload mutation report
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f with:
name: mutation-test-report
path: |
mutation-report.md
mutants.out/
retention-days: 30
- name: Comment PR with results
if: github.event_name == 'pull_request' && always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd with:
script: |
const fs = require('fs');
if (fs.existsSync('mutation-report.md')) {
const report = fs.readFileSync('mutation-report.md', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: report
});
}