-
Notifications
You must be signed in to change notification settings - Fork 1
238 lines (212 loc) · 9.96 KB
/
Copy pathpython-app.yml
File metadata and controls
238 lines (212 loc) · 9.96 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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# Installs dependencies with uv, checks formatting, and runs the test suite.
# For more information see: https://docs.astral.sh/uv/guides/integration/github/
name: Python application
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
# A new push to a branch makes the run already in flight for it obsolete.
# Cancelling it frees the runner immediately, which is what a contributor
# pushing a fixup actually wants -- their new run starts now, not after the
# stale one finishes.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
UV_VERSION: "0.11.28"
jobs:
lint:
name: lint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
version: ${{ env.UV_VERSION }}
enable-cache: true
cache-dependency-glob: "uv.lock"
# Neither check needs the runtime dependencies, so this job skips the
# heavy sync entirely and reports in well under a minute.
- name: Lint with Ruff
run: uv run --frozen --only-group lint ruff check src/ tests/ scripts/
- name: Check formatting with Black
run: uv run --frozen --only-group lint black --check .
docs:
# The documentation is generated from the registry, so it can go stale
# silently: docs/environments.md and the twenty-three per-environment pages
# are written by scripts, and every count and table in them comes from the
# code. Nothing checked that until this job existed, and it drifted --
# pages missing from the nav, links to files outside the docs tree that
# resolve on GitHub but 404 on the built site, and a hand-typed
# "eighteen environments" that survived four new environments.
#
# Its own job rather than a step on the test matrix: it runs concurrently
# with the interpreters, so it adds runner minutes but no wall-clock.
name: docs
runs-on: ubuntu-latest
timeout-minutes: 10
env:
UV_PYTHON: "3.13"
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
version: ${{ env.UV_VERSION }}
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Install dependencies
# The generators import every environment, so this needs the runtime as
# well as the docs group.
run: uv sync --frozen --group dev --group docs
- name: Check the generated pages are current
# The baseline table in docs/baselines.md is written from the recorded
# returns rather than the registry, so re-recording without regenerating
# it would leave the headline results stale.
run: |
uv run python scripts/generate_env_reference.py --check
uv run python scripts/generate_env_pages.py --check
uv run python scripts/generate_baseline_table.py --check
- name: Check the documentation still describes the code
run: |
uv run python scripts/generate_physics_facts.py --check
uv run python scripts/check_doc_drift.py --check
- name: Check no environment changed without a version bump
run: uv run python scripts/stamp_env_versions.py --check
- name: Build the site
# --strict turns a dangling link into a failure. Without it the build
# reported twenty-five warnings and still exited zero.
run: uv run mkdocs build --strict
test:
# Every interpreter in the project's requires-python window. The columns
# run concurrently, so covering four of them costs runner minutes but not
# a contributor's wall-clock wait.
name: test (py${{ matrix.python-version }})
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
# 3.14 is absent because gymnax does not support it; see the
# requires-python note in pyproject.toml.
python-version: ["3.11", "3.12", "3.13"]
env:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
version: ${{ env.UV_VERSION }}
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Restore the XLA compilation cache
# Profiling found this suite's cost is compiling the plants, not stepping
# them: reverse-mode through an RK4 aircraft builds a large graph, and one
# tuner smoke test taking two gradient steps still cost 45 s of it. JAX can
# persist compiled executables keyed on the computation itself, which makes
# that a once-per-code-change cost instead of a once-per-push one.
# Measured locally over the whole fast suite: 217 s cold, 34.5 s warm.
#
# The key includes the sources, so a code change starts a fresh entry; the
# restore-keys prefix still seeds it from the last cache for this
# interpreter, and any computation that changed simply misses and is
# recompiled. Entries are keyed internally by jaxpr, backend and JAX
# version, so a stale one is never wrongly reused.
uses: actions/cache@v4
with:
path: .jax_cache
key: jax-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('src/**/*.py', 'uv.lock') }}
restore-keys: |
jax-${{ runner.os }}-py${{ matrix.python-version }}-
- name: Install dependencies
# --frozen fails loudly if uv.lock is out of date with pyproject.toml,
# rather than silently resolving something else than what we tested.
run: uv sync --frozen --group dev
- name: Type-check the enforced modules
# One interpreter is enough: what this checks is annotations, which do
# not vary across versions. The module list lives in the Makefile so CI
# and a local `make mypy` cannot drift apart.
if: matrix.python-version == '3.13'
run: make mypy
- name: Test with pytest
# -n auto spreads the suite over the runner's cores; tests/conftest.py
# pins each worker to one compute thread so they do not fight for them.
# --durations keeps the slowest tests visible, so the suite's cost stays
# something we notice rather than something that creeps.
if: matrix.python-version != '3.13'
run: uv run pytest tests/ -q -n auto --durations=10 -m "not slow"
- name: Test with pytest, enforcing coverage
# One column carries the coverage gate. pytest-cov aggregates the xdist
# workers, so this costs about twenty seconds more than the plain run
# rather than requiring a serial one.
if: matrix.python-version == '3.13'
run: uv run pytest tests/ -q -n auto --durations=10 -m "not slow" --cov=target_gym
slow-test:
# Closed-loop contracts that still roll the plant out here: PID beats the
# best constant action, states stay bounded and physical under extreme
# actions, the regimes join. Minutes of rollouts that exercise the baselines
# rather than the environments, so they run on merges to main rather than on
# every push.
#
# The MPC-versus-PID contract used to live here and was most of the bill --
# profiled, [plane] alone took 836 s of a 19:47 job, and since xdist
# parallelises across tests rather than within one, that single test set
# roughly 70% of the wall-clock floor and came close to this job's
# 30-minute timeout on four slower cores. It is now asserted from
# src/target_gym/data/baseline_returns.json, recorded by hand with
# scripts/record_baselines.py and guarded by a fingerprint of the physics,
# controllers, gains and parameters it was taken against. That moved it into
# the fast job above, where it runs on every push and across the whole
# matrix instead of once per merge on one interpreter.
#
# One interpreter is enough: what these assert is controller behaviour,
# which does not vary across Python versions the way an import or a wheel
# does. The grid above is what covers those.
#
# The floor check (test_mpc_does_not_beat_a_measured_floor) briefly brought
# MPC rollouts back here, and the same thing happened: 52 of the suite's 65
# core-minutes, and a run killed by this timeout on a four-core runner. It
# now reads the MPC's cost from src/target_gym/data/protocol_results.json
# under the same fingerprint, and runs in the fast job; one slow test
# re-runs the protocol on the pH plant to keep the recording honest.
name: slow tests
if: github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 30
env:
UV_PYTHON: "3.13"
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
version: ${{ env.UV_VERSION }}
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Restore the XLA compilation cache
# Profiling found this suite's cost is compiling the plants, not stepping
# them: reverse-mode through an RK4 aircraft builds a large graph, and one
# tuner smoke test taking two gradient steps still cost 45 s of it. JAX can
# persist compiled executables keyed on the computation itself, which makes
# that a once-per-code-change cost instead of a once-per-push one.
# Measured locally over the whole fast suite: 217 s cold, 34.5 s warm.
#
# The key includes the sources, so a code change starts a fresh entry; the
# restore-keys prefix still seeds it from the last cache for this
# interpreter, and any computation that changed simply misses and is
# recompiled. Entries are keyed internally by jaxpr, backend and JAX
# version, so a stale one is never wrongly reused.
uses: actions/cache@v4
with:
path: .jax_cache
key: jax-${{ runner.os }}-py${{ env.UV_PYTHON }}-${{ hashFiles('src/**/*.py', 'uv.lock') }}
restore-keys: |
jax-${{ runner.os }}-py${{ env.UV_PYTHON }}-
- name: Install dependencies
run: uv sync --frozen --group dev
- name: Run slow closed-loop tests
run: uv run pytest tests/ -q -n auto --durations=10 -m "slow"