diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0bdc1ac..0f2dbd1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,12 @@ jobs: name: py${{ matrix.python-version }} / alembic ${{ matrix.alembic }} runs-on: ubuntu-latest timeout-minutes: 15 + permissions: + contents: read + # For Codecov's OIDC upload, so there is no CODECOV_TOKEN secret to manage or leak. The token is + # minted for the audience https://codecov.io and can do nothing else; note this is still a job that + # runs project code, which is why release.yml deliberately keeps id-token away from its build job. + id-token: write strategy: fail-fast: false matrix: @@ -71,12 +77,26 @@ jobs: - name: Test (resolved Alembic) if: matrix.alembic == 'resolved' # The coverage gate lives here rather than in pyproject's addopts, so a local partial run - # (pytest -k ...) does not fail on coverage while CI still holds the line the badge claims. It runs - # on every supported Python, so a regression cannot hide behind one interpreter. - run: uv run --frozen pytest -q -W error::DeprecationWarning --cov-fail-under=100 + # (pytest -k ...) does not fail on coverage while CI still holds the line. It runs on every supported + # Python, so a regression cannot hide behind one interpreter. The XML report is for Codecov, which + # reports the number rather than enforcing it - this gate is what enforces it. + run: uv run --frozen pytest -q -W error::DeprecationWarning --cov-fail-under=100 --cov-report=xml - name: Test (Alembic ${{ matrix.alembic }}) if: matrix.alembic != 'resolved' # No coverage gate here on purpose: below Alembic 1.18 two plugin-system tests skip themselves, so # the measured percentage is not comparable. The gate lives on the jobs that run the whole suite. run: uv run --frozen --with "alembic==${{ matrix.alembic }}" pytest -q --no-cov -W error::DeprecationWarning + + - name: Upload coverage to Codecov + if: matrix.alembic == 'resolved' + uses: codecov/codecov-action@v7.1.1 + with: + files: coverage.xml + # One flag per interpreter, so Codecov merges the four reports instead of overwriting one with + # the next, and a gap on a single Python stays visible. + flags: py${{ matrix.python-version }} + use_oidc: true + # Reporting must not be able to fail the build: the gate above already enforces the line, and a + # Codecov outage or a fork PR (where OIDC is unavailable) says nothing about this code. + fail_ci_if_error: false diff --git a/.gitignore b/.gitignore index 6e069ff..544bf84 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ __pycache__/ *.pyc .coverage +coverage.xml .pytest_cache/ .ruff_cache/ dist/ diff --git a/README.md b/README.md index f9cc85d..0341704 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue?logo=python&logoColor=white)](https://www.python.org/downloads/) [![Alembic 1.17+](https://img.shields.io/badge/alembic-1.17%2B-ca4245)](#alembic-compatibility) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) -[![Coverage: 100%](https://img.shields.io/badge/coverage-100%25-brightgreen)](https://github.com/rt87/alembic-static-data/actions/workflows/ci.yml) +[![Coverage](https://img.shields.io/codecov/c/github/rt87/alembic-static-data?logo=codecov&logoColor=white)](https://codecov.io/github/rt87/alembic-static-data) [![Checked with mypy](https://img.shields.io/badge/mypy-strict-2a6db2)](https://mypy-lang.org/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000)](https://github.com/psf/black) [![License: MIT](https://img.shields.io/badge/license-MIT-750014)](LICENSE) diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..44e5e5a --- /dev/null +++ b/codecov.yml @@ -0,0 +1,30 @@ +# Codecov is the *reporting* side of coverage; the line itself is enforced by --cov-fail-under=100 in +# .github/workflows/ci.yml, which fails the build before any upload happens. These settings only make +# Codecov's own statuses agree with that gate instead of inventing a second, looser one. +coverage: + precision: 2 + round: down + range: "90...100" + status: + project: + default: + target: 100% + threshold: 0% + patch: + default: + target: 100% + threshold: 0% + +# Four jobs upload the same suite under one flag each; wait for all of them before judging a commit. +codecov: + require_ci_to_pass: true + notify: + after_n_builds: 4 + +comment: + layout: "condensed_header, condensed_files" + require_changes: true + +ignore: + - "tests/**" + - "examples/**"