name: Code Coverage
on:
schedule:
- cron: '0 4 * * 1'
workflow_dispatch:
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
RUST_MIN_LINES: 60
PYTHON_MIN_LINES: 80
jobs:
coverage-rust:
name: Rust coverage (llvm-cov)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6.0.3
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools-preview
- name: Cache cargo
uses: Swatinem/rust-cache@v2
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@v2
with:
tool: cargo-llvm-cov
- name: Run tests under coverage instrumentation
run: cargo llvm-cov --package mrrc --no-report
- name: Report to job summary
run: |
{
echo '## Rust line coverage'
echo '```text'
cargo llvm-cov report --summary-only
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
- name: Enforce minimum line coverage
run: cargo llvm-cov report --fail-under-lines "$RUST_MIN_LINES"
- name: Render HTML report
if: always()
run: cargo llvm-cov report --html
- name: Upload coverage artifacts
uses: actions/upload-artifact@v7
if: always()
with:
name: coverage-report-rust
path: target/llvm-cov/html/
coverage-python:
name: Python coverage (pytest-cov)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6.0.3
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: Swatinem/rust-cache@v2
- name: Install uv
uses: astral-sh/setup-uv@v8.2.0
- name: Sync dependencies
run: uv sync --all-extras
- name: Build extension
run: uv run maturin develop --release
- name: Run pytest with coverage
run: |
uv run --with pytest-cov python -m pytest tests/python/ -m "not benchmark" -q \
--cov=mrrc --cov-report=term
- name: Report to job summary
run: |
{
echo '## Python line coverage (mrrc/ wrapper)'
uv run --with pytest-cov python -m coverage report --format=markdown
} >> "$GITHUB_STEP_SUMMARY"
- name: Enforce minimum line coverage
run: uv run --with pytest-cov python -m coverage report --fail-under "$PYTHON_MIN_LINES"
- name: Render HTML report
if: failure()
run: uv run --with pytest-cov python -m coverage html
- name: Upload coverage artifacts
uses: actions/upload-artifact@v7
if: failure()
with:
name: coverage-report-python
path: htmlcov/
notify-failure:
name: File failure issue
needs: [coverage-rust, coverage-python]
if: (failure() || cancelled()) && github.event_name == 'schedule'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
issues: write
steps:
- name: File or update the failure-tracking issue
env:
GH_TOKEN: ${{ github.token }}
TITLE: "Scheduled coverage run failed"
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
existing=$(gh issue list --repo "$GITHUB_REPOSITORY" --state open \
--json number,title \
--jq '[.[] | select(.title == env.TITLE)][0].number // empty')
if [ -n "$existing" ]; then
gh issue comment "$existing" --repo "$GITHUB_REPOSITORY" \
--body "Failed again: $RUN_URL"
else
gh issue create --repo "$GITHUB_REPOSITORY" --title "$TITLE" \
--body "Scheduled run failed: $RUN_URL — a lane either had test failures or dropped below its coverage minimum (RUST_MIN_LINES / PYTHON_MIN_LINES in coverage.yml). The HTML report is attached to the run as an artifact. Later failures will be added here as comments."
fi