Repository navigation
177 lines (149 loc) · 6.37 KB
/
Copy pathcode-quality.yaml
File metadata and controls
177 lines (149 loc) · 6.37 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
name: Quality Gate
on:
workflow_dispatch:
push:
# No path filter here: branch rulesets expect a status for every push. Doc-only
# changes skip heavy jobs via job-level conditions (skipped still satisfies required checks).
permissions:
contents: read
pull-requests: read
jobs:
changes:
name: Detect relevant changes
runs-on: ubuntu-latest
outputs:
python: ${{ steps.result.outputs.python }}
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Paths filter
id: filter
if: github.event_name != 'workflow_dispatch'
uses: dorny/paths-filter@v4
with:
filters: |
python:
- "**.py"
- "pyproject.toml"
- "uv.lock"
- ".github/workflows/**"
- ".github/actions/**"
- name: Decide whether Python CI should run
id: result
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "python=true" >> "$GITHUB_OUTPUT"
elif [ "${{ steps.filter.outputs.python }}" = "true" ]; then
echo "python=true" >> "$GITHUB_OUTPUT"
else
echo "python=false" >> "$GITHUB_OUTPUT"
fi
detect_pr_context:
name: Detect PR Context
needs: changes
if: needs.changes.outputs.python == 'true'
runs-on: ubuntu-latest
outputs:
has_open_pr: ${{ steps.detect.outputs.has_open_pr }}
is_draft_pr: ${{ steps.detect.outputs.is_draft_pr }}
steps:
- name: Detect open PR for branch
id: detect
uses: actions/github-script@v9
with:
script: |
const branch = context.ref.replace("refs/heads/", "");
const { owner, repo } = context.repo;
const { data: pulls } = await github.rest.pulls.list({
owner,
repo,
state: "open",
head: `${owner}:${branch}`,
});
if (pulls.length === 0) {
core.setOutput("has_open_pr", "false");
core.setOutput("is_draft_pr", "false");
return;
}
core.setOutput("has_open_pr", "true");
core.setOutput("is_draft_pr", pulls[0].draft ? "true" : "false");
quality-gate-linux-cpu:
# Single job runs both supported Python versions sequentially. 3.13 runs
# the full quality gate (lint, format, pyright, coverage) first so cheap
# checks fail fast; 3.11 only exercises the test suite (floor compatibility)
# and runs last — its result is moot if 3.13 already failed. Collapsing into
# one GitHub check trades parallelism for a quieter PR status list.
name: Quality Gate
needs: changes
if: needs.changes.outputs.python == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up Python 3.13 environment
uses: ./.github/actions/setup-raitap-env
with:
python-version: "3.13"
sync-command: uv sync --group dev --extra torch-cpu --extra mlflow --extra shap --extra captum --extra metrics --extra onnx-cpu --extra reporting --extra torchattacks --extra foolbox --extra xgboost --extra text
- name: Ruff
run: uv run ruff check .
- name: Ruff Format
run: uv run ruff format --check .
- name: Guard against defensive getattr on typed config/backend
run: |
if grep -rnE 'getattr\((config|cfg|self\.config|self\.cfg|backend|self\.backend|self\._backend)\s*,\s*"[a-zA-Z_][a-zA-Z0-9_]*"\s*,' src/raitap --include='*.py' | grep -v '/tests/'; then
echo "::error::Defensive getattr on typed config/backend. AppConfig fields and ModelBackend attrs are declared — read them directly (config.x / backend.x) so a missing field fails loud. See docs/contributor/adr.md."
exit 1
fi
- name: Pyright
run: uv run pyright
- name: Pytest on 3.13 (with coverage)
run: uv run pytest -m "not e2e and not cuda" -v --cov=src/raitap --cov-report=term-missing --cov-report=html
- name: Upload coverage report
uses: actions/upload-artifact@v7
with:
name: coverage-report
path: htmlcov/
retention-days: 30
- name: Set up Python 3.11 environment
uses: ./.github/actions/setup-raitap-env
with:
python-version: "3.11"
sync-command: uv sync --group dev --extra torch-cpu --extra mlflow --extra shap --extra captum --extra metrics --extra onnx-cpu --extra reporting --extra torchattacks --extra foolbox --extra xgboost --extra text
- name: Pytest on 3.11 (floor compatibility, no coverage)
run: uv run pytest -m "not e2e and not cuda" -v
quality-gate-linux-cuda:
name: Quality Gate (Linux CUDA)
# Hosted GPU runners are not enabled for every repository by default.
# Keep the job defined, but only run it when the repo explicitly opts in.
needs: [changes, detect_pr_context]
if: ${{ needs.changes.outputs.python == 'true' && vars.ENABLE_HOSTED_GPU_CI == 'true' && needs.detect_pr_context.outputs.has_open_pr == 'true' && needs.detect_pr_context.outputs.is_draft_pr == 'false' }}
runs-on: ubuntu-latest-gpu
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up environment
uses: ./.github/actions/setup-raitap-env
with:
python-version: "3.13"
sync-command: uv sync --group dev --extra torch-cuda --extra mlflow --extra shap --extra captum --extra metrics --extra onnx-cuda --extra reporting --extra torchattacks --extra foolbox --extra text
- name: CUDA smoke check
run: |
uv run python - <<'PY'
import onnxruntime as ort
import torch
if not torch.cuda.is_available():
raise RuntimeError("CUDA is not available on this runner.")
tensor = torch.zeros(1, device="cuda")
if tensor.device.type != "cuda":
raise RuntimeError(f"Expected CUDA tensor, got {tensor.device}.")
providers = ort.get_available_providers()
if "CUDAExecutionProvider" not in providers:
raise RuntimeError(
f"CUDAExecutionProvider missing from ONNX Runtime providers: {providers}"
)
PY
- name: Pytest
run: uv run pytest -m "cuda" -v