78 lines
3.4 KiB
Python
78 lines
3.4 KiB
Python
"""Icon generation reports per-target failures without hiding later targets."""
|
|
import importlib.util
|
|
import io
|
|
import sys
|
|
from pathlib import Path
|
|
from types import ModuleType, SimpleNamespace
|
|
|
|
import pytest
|
|
from PIL import Image
|
|
|
|
|
|
@pytest.mark.parametrize("bom", [b"", b"\xef\xbb\xbf"])
|
|
def test_svg_readers_accept_bom_without_rewriting_assets(tmp_path, monkeypatch, bom):
|
|
monkeypatch.setitem(sys.modules, "resvg_py", ModuleType("resvg_py"))
|
|
script = Path(__file__).resolve().parents[2] / "scripts/generate_icons.py"
|
|
spec = importlib.util.spec_from_file_location("icon_readers_under_test", script)
|
|
assert spec is not None and spec.loader is not None
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
path = tmp_path / "art.svg"
|
|
element = '<path d="M0 0 L1 1" aria-label="café 東京"/>'
|
|
raw = bom + f'<svg viewBox="0 0 20 30">{element}</svg>'.encode("utf-8")
|
|
path.write_bytes(raw)
|
|
art = SimpleNamespace(girls={"black": path}, paths={}, backgrounds=tmp_path, colors=None)
|
|
assert module.girl_path(art, "black") == element
|
|
assert module.background_inner(art, path.name) == (element, 20, 30)
|
|
assert path.read_bytes() == raw
|
|
path.write_bytes(bom + b"<svg/>")
|
|
art.paths.clear()
|
|
with pytest.raises(AssertionError, match="no <path>"):
|
|
module.girl_path(art, "black")
|
|
with pytest.raises(AssertionError, match="viewBox"):
|
|
module.background_inner(art, path.name)
|
|
|
|
|
|
@pytest.mark.parametrize("failure", [None, "render", "directory", "verify"])
|
|
def test_write_status_includes_every_target(tmp_path, monkeypatch, capsys, failure):
|
|
# The renderer is build-only. This test injects failures at its byte boundary.
|
|
monkeypatch.setitem(sys.modules, "resvg_py", ModuleType("resvg_py"))
|
|
script = Path(__file__).resolve().parents[2] / "scripts" / "generate_icons.py"
|
|
spec = importlib.util.spec_from_file_location("icon_generator_under_test", script)
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
source = tmp_path / "immutable source"
|
|
source.mkdir()
|
|
monkeypatch.setattr(module, "IconArt", lambda root: root)
|
|
monkeypatch.setattr(sys, "argv", [str(script), "--source", str(source), "--out", str(tmp_path)])
|
|
|
|
image = io.BytesIO()
|
|
Image.new("RGBA", (2, 2), (0, 0, 0, 0)).save(image, "PNG")
|
|
good_bytes = image.getvalue()
|
|
first = "blocked/icon.png" if failure == "directory" else "first.png"
|
|
if failure == "directory":
|
|
(tmp_path / "blocked").write_text("not a directory", encoding="utf-8")
|
|
monkeypatch.setattr(module, "TARGETS", [(first, "png", "first"), ("last.png", "png", "last")])
|
|
|
|
def target_bytes(art, kind, target):
|
|
assert art == source
|
|
assert not list(source.iterdir())
|
|
if target == "first":
|
|
if failure == "render":
|
|
raise RuntimeError("injected render failure")
|
|
if failure == "verify":
|
|
return b"not an image"
|
|
return good_bytes
|
|
|
|
monkeypatch.setattr(module, "target_bytes", target_bytes)
|
|
code = 0
|
|
try:
|
|
module.main()
|
|
except SystemExit as stopped:
|
|
code = stopped.code
|
|
assert bool(code) is (failure is not None)
|
|
assert (tmp_path / "last.png").read_bytes() == good_bytes
|
|
output = capsys.readouterr().out
|
|
assert "last.png: PNG (2, 2)" in output
|
|
assert ("FAILED" in output) is (failure is not None)
|