YAML · 6512 bytes Raw Blame History
1 name: CI
2
3 on:
4 push:
5 branches: [trunk]
6 pull_request:
7 schedule:
8 # Nightly slow-lane run at 07:00 UTC.
9 - cron: "0 7 * * *"
10 workflow_dispatch:
11 inputs:
12 regenerate_goldens:
13 description: "Regenerate tests/golden/expected_<platform>.json from the current run (S18)."
14 type: boolean
15 default: false
16
17 concurrency:
18 group: ci-${{ github.workflow }}-${{ github.ref }}
19 cancel-in-progress: true
20
21 # F08 (Audit 03) — opt into Node.js 24 for javascript-based actions
22 # (actions/checkout@v4, astral-sh/setup-uv@v6, dorny/paths-filter@v3
23 # all ship Node 20 today; deadline is June 2026). This env applies
24 # workflow-wide and silences the deprecation warning block each job
25 # log currently carries. Remove once every action we pull bumps to
26 # Node 24 in its own tag and we can drop the env override.
27 env:
28 FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
29
30 jobs:
31 fast-lane:
32 # Unit tests + lint + type check. Runs on every push, every PR.
33 name: fast (unit + lint + mypy)
34 runs-on: ubuntu-latest
35 timeout-minutes: 10
36 steps:
37 - uses: actions/checkout@v4
38
39 - uses: astral-sh/setup-uv@v6
40 with:
41 enable-cache: true
42
43 - name: Set up Python
44 run: uv python install 3.11
45
46 - name: Create venv
47 run: uv venv --python 3.11 .venv
48
49 # ``uv sync`` resolves every optional-dependency group to write a
50 # universal lockfile, which fails because ``[dlm]`` depends on
51 # the not-yet-published ``dlm`` package. ``uv pip install -e``
52 # only installs what's named — matches the README's documented
53 # install path and keeps CI honest about what users see.
54 - name: Install dev deps (core + [serve] for unit tests + mypy)
55 # The serve package's mypy run needs fastapi/uvicorn type info,
56 # and the new serve unit tests exercise FastAPI's TestClient.
57 # The deps are lightweight (no torch/transformers).
58 run: uv pip install -e ".[serve]" --group dev
59
60 - name: ruff check
61 run: uv run --no-sync ruff check src tests
62
63 - name: ruff format check
64 run: uv run --no-sync ruff format --check src tests
65
66 - name: mypy strict
67 run: uv run --no-sync mypy src
68
69 - name: pytest (unit)
70 run: uv run --no-sync pytest tests/unit --no-header -v
71
72 changes:
73 # Decides whether the slow lane runs on this PR. A backend touch or
74 # an integration-test touch triggers it; otherwise the nightly cron
75 # is the sole slow-lane driver.
76 name: detect backend/integration changes
77 runs-on: ubuntu-latest
78 timeout-minutes: 2
79 outputs:
80 backend_touched: ${{ steps.filter.outputs.backend_touched }}
81 golden_touched: ${{ steps.filter.outputs.golden_touched }}
82 steps:
83 - uses: actions/checkout@v4
84 - id: filter
85 uses: dorny/paths-filter@v3
86 with:
87 filters: |
88 backend_touched:
89 - 'src/dlm_sway/backends/**'
90 - 'tests/integration/**'
91 - 'pyproject.toml'
92 golden_touched:
93 - 'tests/golden/**'
94 - 'src/dlm_sway/core/golden.py'
95 - 'tests/integration/test_determinism_golden.py'
96
97 slow-lane:
98 # Integration tests (slow + online). Runs on: schedule, manual
99 # dispatch, or when the change-filter flags a backend / integration
100 # diff. Skipped on vanilla PRs that only touch probes / docs.
101 name: slow (integration, HF backend)
102 needs: [changes]
103 if: >-
104 github.event_name == 'schedule' ||
105 github.event_name == 'workflow_dispatch' ||
106 github.event_name == 'push' ||
107 (github.event_name == 'pull_request' && needs.changes.outputs.backend_touched == 'true')
108 runs-on: ubuntu-latest
109 timeout-minutes: 30
110 env:
111 HF_HUB_DISABLE_PROGRESS_BARS: "1"
112 TRANSFORMERS_VERBOSITY: "error"
113 steps:
114 - uses: actions/checkout@v4
115
116 - uses: astral-sh/setup-uv@v6
117 with:
118 enable-cache: true
119
120 - name: Set up Python
121 run: uv python install 3.11
122
123 - name: Create venv
124 run: uv venv --python 3.11 .venv
125
126 # ``[semsim]`` pulls scikit-learn + sentence-transformers; without
127 # it, test_cluster_kl_e2e.py (S16) silently skips with
128 # "No module named 'sklearn'". The cluster_kl probe is a shipped
129 # primitive — its integration test belongs in the slow lane.
130 - name: Install dev + hf + semsim extras
131 run: uv pip install -e ".[hf,semsim]" --group dev
132
133 - name: pytest (slow + online)
134 run: uv run --no-sync pytest tests/integration -m "slow or online" --no-header -v
135
136 determinism-golden:
137 # Cross-platform determinism golden (S18). Runs on a matrix
138 # (ubuntu-latest + macos-latest) when the golden fixture or its
139 # comparator changes, plus on schedule + dispatch. macOS runners
140 # are 10× Linux cost; keep the test scope minimal (2 probes) so
141 # each leg stays under 5 min.
142 name: determinism-golden (${{ matrix.os }})
143 needs: [changes]
144 if: >-
145 github.event_name == 'schedule' ||
146 github.event_name == 'workflow_dispatch' ||
147 github.event_name == 'push' ||
148 (github.event_name == 'pull_request' && needs.changes.outputs.golden_touched == 'true')
149 strategy:
150 fail-fast: false
151 matrix:
152 os: [ubuntu-latest, macos-latest]
153 runs-on: ${{ matrix.os }}
154 timeout-minutes: 20
155 env:
156 HF_HUB_DISABLE_PROGRESS_BARS: "1"
157 TRANSFORMERS_VERBOSITY: "error"
158 # ``workflow_dispatch`` carries a boolean input that toggles
159 # regen mode. Every other trigger writes an empty string and
160 # the test asserts (default mode).
161 SWAY_UPDATE_GOLDENS: "${{ inputs.regenerate_goldens && '1' || '' }}"
162 steps:
163 - uses: actions/checkout@v4
164
165 - uses: astral-sh/setup-uv@v6
166 with:
167 enable-cache: true
168
169 - name: Set up Python
170 run: uv python install 3.11
171
172 - name: Create venv
173 run: uv venv --python 3.11 .venv
174
175 - name: Install dev + hf extras
176 run: uv pip install -e ".[hf]" --group dev
177
178 - name: pytest (determinism golden)
179 run: uv run --no-sync pytest tests/integration/test_determinism_golden.py -m "slow or online" --no-header -v
180
181 - name: Upload regenerated golden
182 if: ${{ inputs.regenerate_goldens == true }}
183 uses: actions/upload-artifact@v4
184 with:
185 name: expected-${{ matrix.os }}
186 path: tests/golden/expected_*.json
187 if-no-files-found: error
188