diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml
index 76262d70..75c0dc71 100644
--- a/.gitea/workflows/ci.yml
+++ b/.gitea/workflows/ci.yml
@@ -109,10 +109,15 @@ jobs:
test-with-coverage:
runs-on: ubuntu-latest
+ outputs:
+ diff-coverage: ${{ steps.diff_cover.outputs.percent }}
steps:
- name: Checkout code
uses: https://github.com/actions/checkout@v5
+ with:
+ # diff-cover needs the base branch present to diff against
+ fetch-depth: 0
- name: Setup
uses: ./.gitea/actions/install
@@ -129,14 +134,41 @@ jobs:
source .venv/bin/activate
pytest --cov=aare --cov-config=./pyproject.toml --cov-branch --cov-report=xml --no-cov-on-fail ./tests/unit
+ # No --fail-under here: the gate lives in coverage-analysis so that the PR
+ # comment is posted before the build is failed.
+ - name: Diff coverage
+ id: diff_cover
+ env:
+ BASE_BRANCH: ${{ github.base_ref || 'main' }}
+ run: |
+ source .venv/bin/activate
+ diff-cover coverage.xml \
+ --compare-branch="origin/${BASE_BRANCH}" \
+ --format "markdown:diff-cover.md,json:diff-cover.json"
+ percent=$(python -c "import json; print(json.load(open('diff-cover.json'))['total_percent_covered'])")
+ echo "percent=${percent}" >> "$GITHUB_OUTPUT"
+ echo "Diff coverage: ${percent}%"
+
- name: Build coverage summary
+ env:
+ BASE_BRANCH: ${{ github.base_ref || 'main' }}
+ DIFF_COVERAGE: ${{ steps.diff_cover.outputs.percent }}
run: |
source .venv/bin/activate
{
echo ""
echo "### Coverage report"
echo
- echo "**Total line + branch coverage: $(coverage report --format=total)%**"
+ echo "| Metric | Value |"
+ echo "| --- | --- |"
+ echo "| Total line + branch coverage | $(coverage report --format=total)% |"
+ echo "| Diff coverage vs \`${BASE_BRANCH}\` | ${DIFF_COVERAGE}% (minimum 80%) |"
+ echo
+ echo "Diff coverage detail
"
+ echo
+ cat diff-cover.md
+ echo
+ echo " "
echo
echo "Per-file breakdown
"
echo
@@ -182,3 +214,19 @@ jobs:
issue-number: ${{ github.event.pull_request.number }}
body-path: coverage-summary.md
edit-mode: replace
+
+ # Last, so the report is always visible on the PR even when this fails.
+ - name: Enforce diff coverage
+ env:
+ DIFF_COVERAGE: ${{ needs.test-with-coverage.outputs.diff-coverage }}
+ MINIMUM: "80"
+ run: |
+ if [ -z "${DIFF_COVERAGE}" ]; then
+ echo "::error::test-with-coverage produced no diff coverage value"
+ exit 1
+ fi
+ echo "Diff coverage: ${DIFF_COVERAGE}% (minimum ${MINIMUM}%)"
+ if awk -v p="${DIFF_COVERAGE}" -v m="${MINIMUM}" 'BEGIN { exit !(p + 0 < m + 0) }'; then
+ echo "::error::Diff coverage ${DIFF_COVERAGE}% is below the required ${MINIMUM}%"
+ exit 1
+ fi
diff --git a/pyproject.toml b/pyproject.toml
index 005125bc..3408b544 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -43,6 +43,7 @@ test = [
"pytest-qt",
"pytest-asyncio",
"pytest-timeout",
+ "diff-cover",
"ruff>=0.15"
]