Skip to content

Stop referencing a private function as a cross-reference #303

Stop referencing a private function as a cross-reference

Stop referencing a private function as a cross-reference #303

Workflow file for this run

name: tests
# Every commit is tested exactly once, whether or not it belongs to a pull request.
#
# Both events used to be unfiltered, so a branch with an open PR ran the whole workflow
# twice per push -- two identical builds of the same commit, which with the Coverage job
# is around twelve minutes of duplicated runner time. GitHub attaches check runs to the
# commit, not to the event, so the push-triggered run already appears on the pull request:
# the second run bought nothing.
#
# `push` matches every branch, so any branch is covered, including one with no pull
# request open (an earlier [main, dev] filter silently skipped every push to
# dev-plotting) and one whose name follows no convention. Tags are excluded because a
# release tag points at a commit that was already tested on its branch.
on:
push:
# '**' matches every branch, including names containing a slash. It has to be spelled
# out: a push filter naming only tags (even `tags-ignore: ['**']`) makes GitHub treat
# the workflow as tag-only and run it for no branch push at all -- which silently
# disabled every push build here until a pull request showed zero runs. Naming only
# branches has the mirror effect, which is the intent: tags do not build, since a
# release tag points at a commit its branch already tested.
branches: ['**']
pull_request:
# Superseded runs are not worth paying for: pushing twice to a branch in quick succession
# cancels the older build. Not on main, where each commit is a merge that deserves its own
# verification.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
pytest:
# Pull-request events are kept only for forks. A branch in this repository fires a
# push event, which is what runs the job above; a fork's branch does not, so without
# this its pull requests would get no CI at all -- a hole that would open silently the
# day the repository goes public and accepts an outside contribution.
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# `requires-python` is ">=3.10" with no upper bound, so pip will install this
# package on 3.13 and later. Testing only up to 3.12 meant claiming interpreters
# nothing ran. 3.13 is added here first; its classifier follows once this job has
# been green, rather than the other way round.
python-version: ['3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v7
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e '.[test]'
- name: Run test suite
# -n auto: the suite is embarrassingly parallel -- xdist gives each worker its
# own process, so the module-level state some tests touch (disabled engines, the
# once-per-session warning dedup) is isolated rather than shared. Measured 19m00s
# -> 9m58s on four workers. More workers buy nothing: one test accounts for 596 s
# of that, so it alone sets the floor.
run: pytest tests/ -v -n auto
coverage:
# Deliberately a separate job rather than a fourth axis of the matrix above, or a flag
# added to it. Instrumentation is not cheap here: measured locally, the suite goes
# from 188 s to 394 s, a factor of 2.1 -- an unusually large penalty, because the hot
# path is a per-slab Python loop rather than time spent inside numpy. Since what a
# line or branch is exercised by does not depend on the interpreter, running it on all
# three would pay that cost three times for one number. As its own job it runs in
# parallel with the matrix, so it does not delay the pass/fail signal from the tests
# themselves, but note it is the longest job in the workflow.
#
# (Python 3.12's sys.monitoring backend, COVERAGE_CORE=sysmon, does not help: it
# cannot measure branches before 3.14, so coverage warns and silently falls back to
# the tracing core. Verified -- it made no difference to the wall time.)
name: Coverage
# Same fork-only rule as the matrix job above.
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
runs-on: ubuntu-latest
timeout-minutes: 30
env:
# Read here at job level rather than on the upload step itself, because a step's own
# `env:` block is not visible to that step's `if:` condition -- this is what lets the
# Codecov step below switch itself off cleanly when the secret does not exist.
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
steps:
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e '.[test]'
- name: Run test suite with coverage
# Settings (branch coverage, which sources, what is omitted, and the 90% floor)
# live in [tool.coverage] in pyproject.toml, so a local `pytest --cov` measures and
# gates exactly as CI does -- no threshold is passed on the command line here,
# which would let the two drift apart.
run: pytest tests/ -q -n auto --cov --cov-report=term-missing --cov-report=xml
- name: Publish coverage summary
# Puts the table on the workflow run's summary page, so the number is readable
# without opening the log or downloading anything.
run: |
{
echo '### Coverage (statements and branches)'
echo
python -m coverage report --format=markdown
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload coverage to Codecov
# Dormant until a CODECOV_TOKEN repository secret exists: without one the condition
# is false and the step is skipped, so this costs nothing and cannot fail while the
# repository is private and no badge is wanted yet. Creating that secret is the
# entire act of switching coverage reporting on -- at which point Codecov also
# starts commenting per-PR diff coverage, which is the part that actually changes
# behaviour, since it shows the coverage of the lines in front of you.
#
# Nothing is sent anywhere until the secret is deliberately added.
#
# fail_ci_if_error is on for the same reason ruff is blocking: an upload that
# quietly fails would leave a badge frozen at a stale number under a green
# checkmark, which is worse than a visible failure. It cannot affect the tests'
# own pass/fail signal, which comes from the matrix job above.
if: env.CODECOV_TOKEN != ''
uses: codecov/codecov-action@v7
with:
files: ./coverage.xml
fail_ci_if_error: true
- name: Upload coverage report
# coverage.xml is the standard Cobertura format every coverage service consumes.
# Publishing it as an artifact keeps the report available regardless of whether the
# Codecov step above is active, and is what makes the figure inspectable today.
uses: actions/upload-artifact@v7
with:
name: coverage-xml
path: coverage.xml
if-no-files-found: error