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" ]