Repository navigation
199 lines (182 loc) · 8.24 KB
/
Copy pathci.yml
File metadata and controls
199 lines (182 loc) · 8.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
name: CI
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
jobs:
supply-chain:
# SDK-3 (security audit 2026-11-06). This job is the check that keeps
# the CI itself honest, and it runs first so a mutable action ref or a
# lockless dependency install fails in seconds instead of after the
# whole test matrix.
#
# The two steps:
# 1. every ``uses:`` points at a 40-hex commit SHA, and every
# dependency install comes from uv.lock;
# 2. uv.lock still matches pyproject.toml, so ``--frozen`` installs
# the versions the commit was reviewed with.
#
# The lint needs no third-party package. It reads the workflows with
# PyYAML when PyYAML is importable and with a text scanner when it is
# not, and a test proves the two scanners agree. Installing PyYAML here
# would be the very thing the lint forbids.
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
- name: Set up uv
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
with:
# An exact uv version. "latest" would let a new uv change how the
# lock is read.
version: "0.11.28"
enable-cache: true
cache-dependency-glob: uv.lock
- name: Check the action pins and the lock-based installs
run: python scripts/check_supply_chain.py
- name: Check that uv.lock matches pyproject.toml
run: uv lock --check
commit-message:
# The commits in this repository are authored solely by the repo owner.
# That rule is in CLAUDE.md and was still broken twice, so it is checked
# here rather than trusted.
#
# Scope is `origin/master..HEAD`, not `--all`. A whole-history grep counts
# trailers on the 200+ commits that are already published and are nobody's
# to rewrite; the meaningful question is only whether THIS branch adds
# one, and that is the number worth a red build.
#
# This checks the trailer form — a `Co-Authored-By:` line at the start of
# a line. A commit body that discusses the rule, or a CHANGELOG entry
# that credits a person, is not a violation and must not fail the build.
#
# A `commit-msg` hook is the stronger place for this (it fails before the
# commit exists rather than after), and one is configured in
# `.pre-commit-config.yaml`. This step is the backstop for the case the
# hook cannot cover: a contributor with no pre-commit installed, or a
# `--no-commit` push. Two mechanisms fail differently, which is the
# point — one rule enforced one way is a rule enforced once.
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
# Full history, because the range check needs the merge base with
# origin/master, and `fetch-depth: 0` is what makes that base
# exist. A shallow clone has no origin/master to diff against and
# the range would silently be empty — a check that always passes
# is worse than no check, because it reads as enforcement.
fetch-depth: 0
- name: Reject a Co-Authored-By trailer on this branch
run: |
set -euo pipefail
git fetch --no-tags --depth=1 origin master || true
range=origin/master..HEAD
if ! git rev-parse --verify origin/master >/dev/null 2>&1; then
echo "::warning::origin/master unavailable; trailer check skipped"
exit 0
fi
offenders="$(
git log "$range" --format=%B \
| grep -inE '^[[:space:]]*co-authored-by[[:space:]]*:' || true
)"
if [ -n "$offenders" ]; then
echo "::error::Co-Authored-By trailer found in $range"
echo "$offenders"
echo "Remove the trailer and amend. See CLAUDE.md."
exit 1
fi
echo "no Co-Authored-By trailer in $range"
test:
runs-on: ubuntu-latest
permissions:
contents: read
# 2026-07-08: fail-fast on the first matrix failure instead of
# wasting runner minutes on the remaining Python versions when
# the suite is already red. Speed gain is per-run, not per-test.
strategy:
fail-fast: true
matrix:
python: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Set up Python ${{ matrix.python }}
id: setup-python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ matrix.python }}
- name: Set up uv
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
with:
version: "0.11.28"
# uv caches the resolved packages, not pip's download cache.
# The key is uv.lock, so a lock change invalidates the cache.
enable-cache: true
cache-dependency-glob: uv.lock
- name: Point uv at the interpreter setup-python installed
# Without this, uv picks its own managed interpreter and the matrix
# leg would not test the Python version it claims to test.
run: echo "UV_PYTHON=${{ steps.setup-python.outputs.python-path }}" >> "$GITHUB_ENV"
- name: Install the dependencies from the lock
# --frozen: the lock file is the contract. The job must never
# rewrite it, and it must never resolve a version at run time.
# --extra dev: the dev extra carries pytest, pytest-xdist,
# pytest-rerunfailures, pytest-cov, ruff, mypy and the rest.
run: uv sync --frozen --extra dev
- name: Run tests
# `-n auto` lets xdist pick a worker count from the runner's
# CPU count. With the transport cancellable-sleep fix the
# 5s-per-shutdown multiplier is gone, and xdist plus the
# existing respx-based mocking keeps the per-test wall clock
# near single-thread baseline (no shared state between
# workers — ``reset_runtime`` autouse fixture in conftest
# is per-process by construction under xdist).
#
# `uv run` puts the command inside the environment the lock
# installed, so pytest is the pinned pytest.
run: uv run --frozen --extra dev pytest -n auto --durations=20
- name: Run ruff
run: uv run --frozen --extra dev ruff check src/
- name: Run mypy
run: uv run --frozen --extra dev mypy src/
coverage:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Set up Python
id: setup-python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
- name: Set up uv
uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
with:
version: "0.11.28"
enable-cache: true
cache-dependency-glob: uv.lock
- name: Point uv at the interpreter setup-python installed
run: echo "UV_PYTHON=${{ steps.setup-python.outputs.python-path }}" >> "$GITHUB_ENV"
- run: uv sync --frozen --extra dev
# Single Python leg for coverage — multi-version coverage
# reports don't add signal and double the runner time. 3.12
# is the modern floor for typing-only changes.
# pytest-cov starts coverage in every xdist worker and combines
# the data before producing the report. ``coverage run`` only
# traced the coordinator process, so every parallel run uploaded
# 0 hits even though all tests passed.
- run: uv run --frozen --extra dev pytest -n auto --cov=src/nullrun --cov-branch --cov-report=xml:coverage.xml --cov-report=term
- uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4
if: always()
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
fail_ci_if_error: true