-
Notifications
You must be signed in to change notification settings - Fork 1
289 lines (264 loc) · 11.8 KB
/
Copy pathblender-smoke.yml
File metadata and controls
289 lines (264 loc) · 11.8 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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
name: Blender Smoke Test
# Executes the snippets' and skills' headline examples inside REAL Blender, headless,
# on the current stable (5.2.x LTS) and the fallback LTS (4.5.x) on every PR,
# plus 5.1.x on the weekly cron, the opt-in `needs-5.1` PR label, or a
# workflow_dispatch series input, and fails on any error or
# empty-output assertion. Shipped examples go through tests/smoke/run_example.py
# (catalog.json): SKIP is exit 77 + SMOKE_SKIP below --min-version, never exit 0.
# Post-exit sidecars are opt-in. A leg with zero PASSes is red.
# py_compile (in validate.yml) cannot catch API-level regressions
# like the EEVEE-id inversion, the slotted-actions boundary, the driver TypeError, or the
# dead SDF link -- this gate runs the code so those surface in CI, not in users' files.
#
# Default PR matrix stays 5.2 + 4.5. `labeled` is in pull_request.types so
# applying `needs-5.1` starts a 5.1 job; other labels are ignored and must
# not cancel an in-progress default run. Auto-label does not apply needs-5.1.
# Manual dispatch: Actions > Blender Smoke Test > Run workflow > pick series.
on:
workflow_dispatch:
inputs:
series:
description: Blender series to smoke (single version, on-demand)
required: true
type: choice
options:
- "5.2"
- "5.1"
- "4.5"
default: "5.2"
schedule:
- cron: "0 7 * * 1" # weekly, Monday 07:00 UTC
pull_request:
branches: [main]
types: [opened, synchronize, reopened, labeled]
permissions:
contents: read
concurrency:
# Isolate labeled runs so auto-label (`ci`, `documentation`, ...) does not
# cancel the default 4.5/5.2 jobs. synchronize still cancels the previous
# synchronize on the same PR.
group: blender-smoke-${{ github.event.pull_request.number || github.ref }}${{ github.event.action == 'labeled' && format('-label-{0}', github.event.label.name) || '' }}
cancel-in-progress: true
jobs:
resolve-matrix:
name: Resolve smoke matrix
runs-on: ubuntu-latest
if: github.event.action != 'labeled' || github.event.label.name == 'needs-5.1'
outputs:
series: ${{ steps.set.outputs.series }}
steps:
- id: set
env:
EVENT_NAME: ${{ github.event_name }}
EVENT_ACTION: ${{ github.event.action }}
DISPATCH_SERIES: ${{ github.event.inputs.series }}
PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
run: |
set -euo pipefail
if [ "$EVENT_NAME" = "schedule" ]; then
json='["5.2","5.1","4.5"]'
elif [ "$EVENT_NAME" = "workflow_dispatch" ]; then
json=$(printf '["%s"]' "$DISPATCH_SERIES")
elif [ "$EVENT_ACTION" = "labeled" ]; then
json='["5.1"]'
elif echo ",$PR_LABELS," | grep -q ",needs-5.1,"; then
json='["5.2","5.1","4.5"]'
else
json='["5.2","4.5"]'
fi
echo "series=$json" >> "$GITHUB_OUTPUT"
echo "Smoke matrix: $json"
smoke:
name: Blender ${{ matrix.series }} smoke
needs: resolve-matrix
if: needs.resolve-matrix.result == 'success'
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
series: ${{ fromJSON(needs.resolve-matrix.outputs.series) }}
steps:
- uses: actions/checkout@v7
- name: Harness protocol unit tests
run: |
set -euo pipefail
python3 tests/smoke/test_harness.py -v
- name: Init smoke status log
run: |
set -euo pipefail
# runner.temp is illegal in job-level env; set it from the step shell.
echo "BDT_SMOKE_STATUS=$RUNNER_TEMP/smoke-status.jsonl" >> "$GITHUB_ENV"
: > "$RUNNER_TEMP/smoke-status.jsonl"
- name: Install Blender runtime libraries
run: |
set -euo pipefail
sudo apt-get update
# Blender needs these shared libs even in --background (GPU/GL module init);
# xvfb provides a virtual display so GL/EGL init does not abort the process.
sudo apt-get install -y --no-install-recommends \
xvfb libgl1 libegl1 libxrender1 libxxf86vm1 libxfixes3 libxi6 \
libxkbcommon0 libsm6 libice6
- name: Resolve and download Blender ${{ matrix.series }}
run: |
set -euo pipefail
series="${{ matrix.series }}"
base="https://download.blender.org/release/Blender${series}/"
echo "Listing $base"
# pick the highest point release for this series (linux x64 portable)
file=$(curl -fsSL "$base" \
| grep -oE "blender-${series}\.[0-9]+-linux-x64\.tar\.xz" \
| sort -V | uniq | tail -1)
if [ -z "$file" ]; then
echo "::error::Could not resolve a linux-x64 build for Blender ${series} at $base"
exit 1
fi
url="${base}${file}"
echo "Downloading $url"
mkdir -p "$RUNNER_TEMP/bl"
curl -fSL --retry 3 -o "$RUNNER_TEMP/bl.tar.xz" "$url"
tar -xf "$RUNNER_TEMP/bl.tar.xz" -C "$RUNNER_TEMP/bl"
bl=$(find "$RUNNER_TEMP/bl" -maxdepth 2 -type f -name blender | head -1)
if [ -z "$bl" ]; then
echo "::error::blender binary not found after extraction"
exit 1
fi
echo "BLENDER=$bl" >> "$GITHUB_ENV"
- name: Print Blender version
run: |
set -euo pipefail
"$BLENDER" --version | head -1
# series guard: confirm we actually got the matrix series
"$BLENDER" --version | head -1 | grep -q "Blender ${{ matrix.series }}\." \
|| { echo "::error::version does not match series ${{ matrix.series }}"; exit 1; }
- name: Run in-Blender smoke driver
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/out"
xvfb-run -a "$BLENDER" --background --python tests/smoke/run_smoke.py -- "$RUNNER_TEMP/out"
- name: Build template input scene
run: |
set -euo pipefail
xvfb-run -a "$BLENDER" --background --python tests/smoke/make_input.py -- "$RUNNER_TEMP/out/input.blend"
- name: Headless glTF template runs (exit 0, .glb produced)
run: |
set -euo pipefail
xvfb-run -a "$BLENDER" --background "$RUNNER_TEMP/out/input.blend" \
--python tests/smoke/tmpl_gltf.py -- \
--output "$RUNNER_TEMP/out/out.glb" --apply-modifier SUBSURF
test -s "$RUNNER_TEMP/out/out.glb" || { echo "::error::glTF output missing/empty"; exit 1; }
head -c4 "$RUNNER_TEMP/out/out.glb" | grep -q "glTF" || { echo "::error::not a glTF binary"; exit 1; }
- name: Headless template no-mesh path returns exit 2
run: |
set +e
xvfb-run -a "$BLENDER" --background "$RUNNER_TEMP/out/empty.blend" \
--python tests/smoke/tmpl_gltf.py -- --output "$RUNNER_TEMP/out/none.glb"
code=$?
set -e
[ "$code" -eq 2 ] || { echo "::error::expected exit 2 for no-mesh input, got $code"; exit 1; }
echo "no-mesh exit code = $code (correct)"
- name: Build pipeline template fixture GLB
run: |
set -euo pipefail
xvfb-run -a "$BLENDER" --background --python tests/smoke/make_pipeline_glb.py -- \
"$RUNNER_TEMP/out/pipeline_src.glb"
test -s "$RUNNER_TEMP/out/pipeline_src.glb" || { echo "::error::pipeline fixture missing"; exit 1; }
- name: Headless AI asset pipeline template runs (exit 0, LODs produced)
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/out/pipeline"
xvfb-run -a "$BLENDER" --background --python templates/ai-asset-pipeline-template/pipeline.py -- \
--input "$RUNNER_TEMP/out/pipeline_src.glb" \
--outdir "$RUNNER_TEMP/out/pipeline" \
--preset unity \
--lod-budgets 1024,256,64 \
--collider convex
test -s "$RUNNER_TEMP/out/pipeline/lod0.glb" || { echo "::error::lod0 missing/empty"; exit 1; }
test -s "$RUNNER_TEMP/out/pipeline/collider.glb" || { echo "::error::collider missing/empty"; exit 1; }
head -c4 "$RUNNER_TEMP/out/pipeline/lod0.glb" | grep -q "glTF" || { echo "::error::lod0 not a glTF binary"; exit 1; }
- name: Headless pipeline template missing input returns exit 2
run: |
set +e
xvfb-run -a "$BLENDER" --background --python templates/ai-asset-pipeline-template/pipeline.py -- \
--input "$RUNNER_TEMP/out/does-not-exist.glb" \
--outdir "$RUNNER_TEMP/out/pipeline" \
--preset unity
code=$?
set -e
[ "$code" -eq 2 ] || { echo "::error::expected exit 2 for missing input, got $code"; exit 1; }
echo "missing-input exit code = $code (correct)"
- name: Headless render template runs (exit 0, PNG produced)
run: |
set -euo pipefail
# Cycles (CPU) so this is reliable on GPU-less runners; the EEVEE-id regression
# itself is gated in run_smoke.py via engine assignment.
xvfb-run -a "$BLENDER" --background "$RUNNER_TEMP/out/input.blend" \
--python tests/smoke/tmpl_render.py -- \
--output "$RUNNER_TEMP/out/render.png" --engine CYCLES
test -s "$RUNNER_TEMP/out/render.png" || { echo "::error::render PNG missing/empty"; exit 1; }
- name: Canary - expected skip is SKIP not PASS
run: |
set -euo pipefail
python3 tests/smoke/run_example.py \
--name canary-skip \
--blender "$BLENDER" \
--script tests/smoke/canary_skip.py \
--series "${{ matrix.series }}" \
--min-version 99.0 \
--xvfb
- name: Canary - unexpected skip is red
run: |
set +e
python3 tests/smoke/run_example.py --no-record \
--name canary-skip-unexpected \
--blender "$BLENDER" \
--script tests/smoke/canary_skip.py \
--series "${{ matrix.series }}" \
--min-version 4.0 \
--xvfb
code=$?
set -e
[ "$code" -eq 1 ] || { echo "::error::unexpected skip did not fail (got $code)"; exit 1; }
echo "unexpected skip exited 1 (correct)"
- name: Canary - post-exit sidecar
run: |
set -euo pipefail
python3 tests/smoke/run_example.py \
--name canary-sidecar \
--blender "$BLENDER" \
--script tests/smoke/canary_sidecar.py \
--series "${{ matrix.series }}" \
--expect-sidecar "$RUNNER_TEMP/out/canary.sidecar" \
--sidecar-contains sidecar-ok \
--xvfb
- name: Canary - missing sidecar is red
run: |
set +e
python3 tests/smoke/run_example.py --no-record \
--name canary-sidecar-missing \
--blender "$BLENDER" \
--script tests/smoke/canary_sidecar.py \
--series "${{ matrix.series }}" \
--expect-sidecar "$RUNNER_TEMP/out/missing.sidecar" \
--sidecar-contains sidecar-ok \
--xvfb \
-- --omit-sidecar
code=$?
set -e
[ "$code" -eq 1 ] || { echo "::error::missing sidecar did not fail (got $code)"; exit 1; }
echo "missing sidecar exited 1 (correct)"
- name: Shipped examples
run: |
set -euo pipefail
python3 tests/smoke/run_catalog.py \
--blender "$BLENDER" \
--series "${{ matrix.series }}" \
--status "$BDT_SMOKE_STATUS" \
--out "$RUNNER_TEMP/out" \
--xvfb \
--catalog tests/smoke/catalog.json
- name: Smoke summary
if: ${{ always() && env.BLENDER != '' }}
run: |
set -euo pipefail
python3 tests/smoke/summarize.py --status "$BDT_SMOKE_STATUS"