mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 22:27:01 +01:00
Closes the remaining bake-off cells. The phase's real result was not a route
score but a MISSING GATE.
All twelve SDXL icons measured 100% opaque coverage -- square tiles with no
sprite silhouette -- and passed every gate in the harness. Gate-pass was
measuring pipeline hygiene, not usability. MARGIN_TOO_SMALL inverts the table
from a meaningless 12/12/12 tie to:
diffusion 0/12 (every output full-bleed)
vector 10/12
procedural 11/12
The two non-diffusion failures were true positives in MY shapes, not gate
error: the procedural sword pommel ran off the bottom edge and the vector meat
bone clipped the top. Both fixed; both routes back to 12/12.
Diffusion recovers to 12/12 with flood-fill background removal, but visual
inspection shows the fill eats into the art (bone reduced to a fragment, sword
nearly erased). Reported as inadequate rather than shipped -- proper matting is
needed before route (b) is a usable pipeline.
Blender exits 0 even when its embedded script raises: BLENDER_EEVEE_NEXT does
not exist in 5.2, and the failure produced a clean exit and no file. render()
now asserts the output exists, or the route would report phantom successes.
Model gating verified at execution time, never recalled: SDXL base and
nerijs/pixel-art-xl are ungated, FLUX.1-dev is gated=auto, and
runwayml/stable-diffusion-v1-5 now 307-redirects.
The finishing stage now generalises across three continuous-tone sources
(SVG raster, Blender render, SDXL): each is 0/N raw and N/N finished.
Verified: 114 tests, 100% branch coverage on 13 modules, zero suppressions,
all hooks pass. Note: a local pytest run masked a missing branch that the
pre-commit hook caught -- the hook's run is authoritative.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9rQ12yZ7sJxiPQTCS5dUF
140 lines
5.0 KiB
Python
140 lines
5.0 KiB
Python
"""Blender route tests.
|
|
|
|
The important one is ``test_exit_zero_without_output_raises``: Blender exits 0
|
|
even when the embedded script raises, so a return-code check alone reports a
|
|
phantom success. That was measured, not hypothesised -- a wrong render-engine
|
|
enum on Blender 5.2 produced exit 0 and no file.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
from typing import TYPE_CHECKING
|
|
|
|
import pytest
|
|
|
|
from python_pkg.artgate.routes import blender
|
|
from python_pkg.artgate.routes.blender_scene import (
|
|
RENDER_SIZE,
|
|
SUBJECTS_3D,
|
|
scene_script,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
|
|
class _Result:
|
|
"""Stand-in for a completed subprocess."""
|
|
|
|
def __init__(self, returncode: int = 0, stderr: bytes = b"") -> None:
|
|
"""Store the fields the caller inspects.
|
|
|
|
Args:
|
|
returncode: Simulated exit status.
|
|
stderr: Simulated error output.
|
|
"""
|
|
self.returncode = returncode
|
|
self.stderr = stderr
|
|
|
|
|
|
class TestSceneScript:
|
|
"""The generated scene script is well-formed and parameterised."""
|
|
|
|
def test_render_size_is_substituted(self) -> None:
|
|
"""The template takes the requested resolution."""
|
|
assert "resolution_x = 128" in scene_script(128)
|
|
|
|
def test_default_size(self) -> None:
|
|
"""The default matches the documented constant."""
|
|
assert f"resolution_x = {RENDER_SIZE}" in scene_script()
|
|
|
|
def test_every_subject_has_a_builder(self) -> None:
|
|
"""No 3D subject is silently missing a mesh."""
|
|
script = scene_script()
|
|
for name in SUBJECTS_3D:
|
|
assert f'"{name}": build_' in script
|
|
|
|
def test_script_is_valid_python(self) -> None:
|
|
"""The emitted template compiles, so a typo fails here not in Blender."""
|
|
compile(scene_script(), "<scene>", "exec")
|
|
|
|
def test_transparent_film(self) -> None:
|
|
"""Icons need an alpha channel, not a background."""
|
|
assert "film_transparent = True" in scene_script()
|
|
|
|
|
|
class TestRenderErrors:
|
|
"""Every failure mode is surfaced, never silently skipped."""
|
|
|
|
def test_unknown_subject_raises(self, tmp_path: Path) -> None:
|
|
"""A subject with no mesh fails loudly."""
|
|
with pytest.raises(KeyError, match="no 3D scene"):
|
|
blender.render("dragon", tmp_path / "x.png")
|
|
|
|
def test_missing_binary_raises(
|
|
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
"""A missing Blender is reported rather than treated as zero output."""
|
|
monkeypatch.setattr(blender.shutil, "which", lambda _: None)
|
|
with pytest.raises(blender.BlenderError, match="not installed"):
|
|
blender.render("coin", tmp_path / "x.png")
|
|
|
|
def test_nonzero_exit_raises(
|
|
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
"""A hard failure is surfaced with Blender's own message."""
|
|
monkeypatch.setattr(blender.shutil, "which", lambda _: "/bin/true")
|
|
monkeypatch.setattr(
|
|
blender.subprocess,
|
|
"run",
|
|
lambda *a, **k: _Result(returncode=1, stderr=b"bad mesh"),
|
|
)
|
|
with pytest.raises(blender.BlenderError, match="bad mesh"):
|
|
blender.render("coin", tmp_path / "x.png")
|
|
|
|
def test_exit_zero_without_output_raises(
|
|
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
"""Exit 0 with no file is a failure, not a success.
|
|
|
|
Regression for the measured Blender 5.2 behaviour: an exception inside
|
|
the embedded script still exits 0. Without this check the route would
|
|
report a phantom success and the cell would score a false zero.
|
|
"""
|
|
monkeypatch.setattr(blender.shutil, "which", lambda _: "/bin/true")
|
|
monkeypatch.setattr(
|
|
blender.subprocess,
|
|
"run",
|
|
lambda *a, **k: _Result(returncode=0, stderr=b"enum not found"),
|
|
)
|
|
with pytest.raises(blender.BlenderError, match="wrote no file"):
|
|
blender.render("coin", tmp_path / "missing.png")
|
|
|
|
def test_timeout_raises(
|
|
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
"""A hung render is bounded, not waited on forever."""
|
|
|
|
def _boom(*_args: object, **_kwargs: object) -> _Result:
|
|
raise subprocess.TimeoutExpired(cmd="blender", timeout=1)
|
|
|
|
monkeypatch.setattr(blender.shutil, "which", lambda _: "/bin/true")
|
|
monkeypatch.setattr(blender.subprocess, "run", _boom)
|
|
with pytest.raises(blender.BlenderError, match="timed out"):
|
|
blender.render("coin", tmp_path / "x.png")
|
|
|
|
def test_success_returns_path(
|
|
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
"""A real output file is accepted and returned."""
|
|
out = tmp_path / "coin.png"
|
|
|
|
def _write(*_args: object, **_kwargs: object) -> _Result:
|
|
out.write_bytes(b"png")
|
|
return _Result()
|
|
|
|
monkeypatch.setattr(blender.shutil, "which", lambda _: "/bin/true")
|
|
monkeypatch.setattr(blender.subprocess, "run", _write)
|
|
assert blender.render("coin", out) == out
|