From 08f4df7a7bfbed68e80cabeea3296c2c75f20e5d Mon Sep 17 00:00:00 2001 From: sjquant Date: Fri, 7 Aug 2026 14:28:13 +0900 Subject: [PATCH] test: compare snapshots by what they draw, not how they were encoded A PNG carries a deflate stream, and the same pixels compress to different bytes across zlib builds. The snapshot suite compared the files byte for byte, so it was testing whichever encoder happened to be installed: the solid-colour snapshot, a 200x150 flat fill, differs from a fresh render in 332 bytes of IDAT while being pixel-for-pixel identical, same dimensions, same mode, same file length. Snapshots now compare as images. Nothing in the tests changes, because the stored value is what carries the comparison. --- tests/conftest.py | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index f5e3ec73..3b6d0a91 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,11 @@ import os import tempfile +from io import BytesIO from pathlib import Path import pytest from inline_snapshot import Format, register_format +from PIL import Image, UnidentifiedImageError # Pin fontconfig (used by cairosvg) to the repo fonts so SVG rasterization in # snapshot tests resolves the same font files as the PIL pipeline, independent @@ -23,11 +25,46 @@ os.environ["FONTCONFIG_FILE"] = str(_FONTCONFIG_DIR / "fonts.conf") +class RenderedImage: + """A stored image compared by what it draws, not by how it was encoded. + + Two encoders can write the same picture as different bytes: PNG carries a + deflate stream, and the same pixels compress differently across zlib builds. + Comparing the files byte for byte therefore tests the encoder that happened + to be installed, and a snapshot suite that does it drifts out of date + without a single pixel having moved. + """ + + def __init__(self, data: bytes) -> None: + self._data = data + + @staticmethod + def _pixels(data: bytes): + with Image.open(BytesIO(data)) as image: + return image.size, image.convert("RGBA").tobytes() + + def __eq__(self, other: object) -> bool: + if isinstance(other, RenderedImage): + return self._pixels(self._data) == self._pixels(other._data) + if isinstance(other, bytes): + try: + return self._pixels(self._data) == self._pixels(other) + except UnidentifiedImageError: + return self._data == other + return NotImplemented + + def __hash__(self) -> int: + return hash(self._data) + + def __repr__(self) -> str: + return f"RenderedImage({len(self._data)} bytes)" + + class ImageFormat(Format): suffix = ".png" - def decode(self, path: Path) -> bytes: - return path.read_bytes() + def decode(self, path: Path) -> RenderedImage: + return RenderedImage(path.read_bytes()) def encode( self,