Skip to content

Commit edd6ab5

Browse files
TMHSDigitalclaude
andcommitted
fix(smoke): pass --python-exit-code 1 so an uncaught exception is red
Blender exits 0 when a --python script raises unless --python-exit-code is set, and protocol.classify maps exit 0 to PASS. Module-level failures (imports, the gallery_framing sys.path shim, check_extension_template's register()) were silent greens. - run_example.build_cmd, every direct Blender call in blender-smoke.yml, and the subprocess calls in render_hero, measure_hero_drift, asset_sheet and check_falsifier_targets now pass --python-exit-code 1 before --python. - New canary tests/smoke/canary_raise.py + workflow step: must exit 1. - test_harness: build_cmd carries the flag ahead of --python. Proof (local, .scratch/blender-5.2.1-windows-x64/blender.exe, "Blender 5.2.1 LTS"): before: run_example on a module-level raise printed [PASS], exit 0. after: [FAIL] canary-raise: blender exit 1, exit 1; canary-sidecar still [PASS]. Closes #319 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 8600502 commit edd6ab5

8 files changed

Lines changed: 55 additions & 13 deletions

File tree

‎.github/workflows/blender-smoke.yml‎

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -181,18 +181,18 @@ jobs:
181181
run: |
182182
set -euo pipefail
183183
mkdir -p "$RUNNER_TEMP/out"
184-
xvfb-run -a "$BLENDER" --background --python tests/smoke/run_smoke.py -- "$RUNNER_TEMP/out"
184+
xvfb-run -a "$BLENDER" --background --python-exit-code 1 --python tests/smoke/run_smoke.py -- "$RUNNER_TEMP/out"
185185
186186
- name: Build template input scene
187187
run: |
188188
set -euo pipefail
189-
xvfb-run -a "$BLENDER" --background --python tests/smoke/make_input.py -- "$RUNNER_TEMP/out/input.blend"
189+
xvfb-run -a "$BLENDER" --background --python-exit-code 1 --python tests/smoke/make_input.py -- "$RUNNER_TEMP/out/input.blend"
190190
191191
- name: Headless glTF template runs (exit 0, .glb produced)
192192
run: |
193193
set -euo pipefail
194194
xvfb-run -a "$BLENDER" --background "$RUNNER_TEMP/out/input.blend" \
195-
--python templates/headless-batch-script-template/script.py -- \
195+
--python-exit-code 1 --python templates/headless-batch-script-template/script.py -- \
196196
--output "$RUNNER_TEMP/out/out.glb" --apply-modifier SUBSURF
197197
test -s "$RUNNER_TEMP/out/out.glb" || { echo "::error::glTF output missing/empty"; exit 1; }
198198
head -c4 "$RUNNER_TEMP/out/out.glb" | grep -q "glTF" || { echo "::error::not a glTF binary"; exit 1; }
@@ -201,7 +201,7 @@ jobs:
201201
run: |
202202
set +e
203203
xvfb-run -a "$BLENDER" --background "$RUNNER_TEMP/out/empty.blend" \
204-
--python templates/headless-batch-script-template/script.py -- --output "$RUNNER_TEMP/out/none.glb"
204+
--python-exit-code 1 --python templates/headless-batch-script-template/script.py -- --output "$RUNNER_TEMP/out/none.glb"
205205
code=$?
206206
set -e
207207
[ "$code" -eq 2 ] || { echo "::error::expected exit 2 for no-mesh input, got $code"; exit 1; }
@@ -215,15 +215,15 @@ jobs:
215215
- name: Build pipeline template fixture GLB
216216
run: |
217217
set -euo pipefail
218-
xvfb-run -a "$BLENDER" --background --python tests/smoke/make_pipeline_glb.py -- \
218+
xvfb-run -a "$BLENDER" --background --python-exit-code 1 --python tests/smoke/make_pipeline_glb.py -- \
219219
"$RUNNER_TEMP/out/pipeline_src.glb"
220220
test -s "$RUNNER_TEMP/out/pipeline_src.glb" || { echo "::error::pipeline fixture missing"; exit 1; }
221221
222222
- name: Headless AI asset pipeline template runs (exit 0, LODs produced)
223223
run: |
224224
set -euo pipefail
225225
mkdir -p "$RUNNER_TEMP/out/pipeline"
226-
xvfb-run -a "$BLENDER" --background --python templates/ai-asset-pipeline-template/pipeline.py -- \
226+
xvfb-run -a "$BLENDER" --background --python-exit-code 1 --python templates/ai-asset-pipeline-template/pipeline.py -- \
227227
--input "$RUNNER_TEMP/out/pipeline_src.glb" \
228228
--outdir "$RUNNER_TEMP/out/pipeline" \
229229
--preset unity \
@@ -236,7 +236,7 @@ jobs:
236236
- name: Headless pipeline template missing input returns exit 2
237237
run: |
238238
set +e
239-
xvfb-run -a "$BLENDER" --background --python templates/ai-asset-pipeline-template/pipeline.py -- \
239+
xvfb-run -a "$BLENDER" --background --python-exit-code 1 --python templates/ai-asset-pipeline-template/pipeline.py -- \
240240
--input "$RUNNER_TEMP/out/does-not-exist.glb" \
241241
--outdir "$RUNNER_TEMP/out/pipeline" \
242242
--preset unity
@@ -251,7 +251,7 @@ jobs:
251251
# Cycles (CPU) so this is reliable on GPU-less runners; the EEVEE-id regression
252252
# itself is gated in run_smoke.py via engine assignment.
253253
xvfb-run -a "$BLENDER" --background "$RUNNER_TEMP/out/input.blend" \
254-
--python tests/smoke/tmpl_render.py -- \
254+
--python-exit-code 1 --python tests/smoke/tmpl_render.py -- \
255255
--output "$RUNNER_TEMP/out/render.png" --engine CYCLES
256256
test -s "$RUNNER_TEMP/out/render.png" || { echo "::error::render PNG missing/empty"; exit 1; }
257257
@@ -281,6 +281,20 @@ jobs:
281281
[ "$code" -eq 1 ] || { echo "::error::unexpected skip did not fail (got $code)"; exit 1; }
282282
echo "unexpected skip exited 1 (correct)"
283283
284+
- name: Canary - uncaught exception is red
285+
run: |
286+
set +e
287+
python3 tests/smoke/run_example.py --no-record \
288+
--name canary-raise \
289+
--blender "$BLENDER" \
290+
--script tests/smoke/canary_raise.py \
291+
--series "${{ matrix.series }}" \
292+
--xvfb
293+
code=$?
294+
set -e
295+
[ "$code" -eq 1 ] || { echo "::error::uncaught exception did not fail (got $code)"; exit 1; }
296+
echo "uncaught exception exited 1 (correct)"
297+
284298
- name: Canary - post-exit sidecar
285299
run: |
286300
set -euo pipefail

‎scripts/asset_sheet.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ def render_panel(blender: str, entry: dict, png: Path) -> bool:
160160
env = dict(os.environ, BDT_SHEET_SCRIPT=str(script), BDT_SHEET_SELECT=inc,
161161
BDT_SHEET_EXCLUDE=exc or "", BDT_SHEET_OUT=str(png))
162162
proc = subprocess.run(
163-
[blender, "--background", "--factory-startup", "--python",
163+
[blender, "--background", "--factory-startup", "--python-exit-code", "1", "--python",
164164
str(REPO / "scripts" / "asset_sheet_panel.py"), "--", *cmd],
165165
capture_output=True, text=True, encoding="utf-8", errors="replace",
166166
cwd=REPO, env=env,

‎scripts/measure_hero_drift.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def main(argv=None) -> int:
169169
png.unlink()
170170
t0 = time.time()
171171
proc = subprocess.run(
172-
[blender, "--background", "--factory-startup", "--python", str(script), "--", *cmd],
172+
[blender, "--background", "--factory-startup", "--python-exit-code", "1", "--python", str(script), "--", *cmd],
173173
capture_output=True, text=True, encoding="utf-8", errors="replace", cwd=REPO,
174174
)
175175
secs = time.time() - t0

‎scripts/render_hero.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def main(argv=None) -> int:
7171
continue
7272
script = next((REPO / e["dir"]).glob("*.py"))
7373
proc = subprocess.run(
74-
[blender, "--background", "--factory-startup", "--python", str(script),
74+
[blender, "--background", "--factory-startup", "--python-exit-code", "1", "--python", str(script),
7575
"--", *cmd],
7676
capture_output=True, text=True, encoding="utf-8", errors="replace", cwd=REPO,
7777
)

‎tests/check_falsifier_targets.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ def check_runtime(blender, only=None):
228228
for flag, (want, _target) in sorted(falsifiers.items()):
229229
checked += 1
230230
proc = subprocess.run(
231-
[blender, "--background", "--python", script, "--", flag],
231+
[blender, "--background", "--python-exit-code", "1", "--python", script, "--", flag],
232232
capture_output=True,
233233
)
234234
got = proc.returncode

‎tests/smoke/canary_raise.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""Harness canary: raise at module level, outside any FATAL wrapper.
2+
3+
Without --python-exit-code Blender exits 0 here and the harness would record
4+
PASS. The workflow runs this with --no-record and requires exit 1. Not a
5+
shipped example.
6+
"""
7+
raise RuntimeError("harness canary: uncaught exception must be red")

‎tests/smoke/run_example.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def append_status(path, record):
3333

3434

3535
def build_cmd(args):
36-
cmd = [args.blender, "--background", "--python", args.script, "--"]
36+
cmd = [args.blender, "--background", "--python-exit-code", "1", "--python", args.script, "--"]
3737
cmd.extend(args.script_args)
3838
if args.xvfb:
3939
cmd = ["xvfb-run", "-a"] + cmd

‎tests/smoke/test_harness.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,27 @@ def test_exit_pre_row_has_sidecar_and_floor(self):
171171
self.assertEqual(row["sidecar_contains"], "exit_pre-ok")
172172

173173

174+
class BuildCmd(unittest.TestCase):
175+
"""Blender exits 0 on an uncaught exception unless --python-exit-code is set."""
176+
177+
def _cmd(self, xvfb=False):
178+
import argparse
179+
180+
import run_example
181+
182+
ns = argparse.Namespace(blender="bl", script="s.py", script_args=["--x"], xvfb=xvfb)
183+
return run_example.build_cmd(ns)
184+
185+
def test_exit_code_flag_precedes_python(self):
186+
cmd = self._cmd()
187+
i = cmd.index("--python-exit-code")
188+
self.assertEqual(cmd[i + 1], "1")
189+
self.assertLess(i, cmd.index("--python"))
190+
191+
def test_xvfb_keeps_exit_code_flag(self):
192+
self.assertIn("--python-exit-code", self._cmd(xvfb=True))
193+
194+
174195
class RunCatalog(unittest.TestCase):
175196
"""run_catalog runs every row and reports all failures; empty is red."""
176197

0 commit comments

Comments
 (0)