Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ jobs:

- name: Download odiff binary
run: |
curl -sL https://registry.npmjs.org/odiff-bin/-/odiff-bin-4.3.2.tgz \
curl -sL https://registry.npmjs.org/odiff-bin/-/odiff-bin-4.3.8.tgz \
| tar -xz --strip-components=2 package/raw_binaries/odiff-linux-x64
sudo install -m 755 odiff-linux-x64 /usr/local/bin/odiff
rm odiff-linux-x64
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@
"jest-fail-on-console": "3.3.4",
"jest-junit": "16.0.0",
"knip": "6.4.1",
"odiff-bin": "^4.3.2",
"odiff-bin": "4.3.8",
"oxfmt": "^0.41.0",
"playwright": "^1.58.2",
"postcss-styled-syntax": "0.7.0",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 6 additions & 21 deletions src/sentry/preprod/snapshots/image_diff/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from PIL import Image

from .odiff import OdiffServer, compare_cli
from .odiff import OdiffServer
from .types import DiffResult

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -79,17 +79,13 @@ def compare_images_batch(
tmpdir_path = Path(tmpdir)
if server is not None:
return _compare_pairs(pairs, server, tmpdir_path)
# Temporarily skip creating OdiffServer due to server-mode stdin
# buffer reuse bug (https://github.com/dmtrKovalenko/odiff/pull/170).
# Revert once the fix is released in odiff-bin.
# with OdiffServer() as new_server:
# return _compare_pairs(pairs, new_server, tmpdir_path)
return _compare_pairs(pairs, None, tmpdir_path)
with OdiffServer() as new_server:
return _compare_pairs(pairs, new_server, tmpdir_path)


def _compare_pairs(
pairs: Sequence[tuple[bytes | Image.Image, bytes | Image.Image]],
server: OdiffServer | None,
server: OdiffServer,
tmpdir_path: Path,
) -> list[DiffResult | None]:
return [
Expand All @@ -102,7 +98,7 @@ def _compare_single_pair(
idx: int,
before: bytes | Image.Image,
after: bytes | Image.Image,
server: OdiffServer | None,
server: OdiffServer,
tmpdir_path: Path,
) -> DiffResult | None:
before_img: Image.Image | None = None
Expand All @@ -127,18 +123,7 @@ def _compare_single_pair(
after_padded.save(after_path, "PNG")

output_path = tmpdir_path / f"diff_{idx}.png"
# Use CLI mode instead of server mode to work around stdin buffer
# reuse bug (https://github.com/dmtrKovalenko/odiff/pull/170).
# Revert to server.compare() once the fix is released in odiff-bin.
# resp = server.compare(
# before_path,
# after_path,
# output_path,
# threshold=ODIFF_SENSITIVITY_DIFF_THRESHOLD,
# antialiasing=True,
# outputDiffMask=True,
# )
resp = compare_cli(
resp = server.compare(
before_path,
after_path,
output_path,
Expand Down
61 changes: 5 additions & 56 deletions src/sentry/preprod/snapshots/image_diff/odiff.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from __future__ import annotations

import platform
import select
import shutil
import subprocess
Expand All @@ -10,30 +9,16 @@
from sentry.preprod.snapshots.image_diff.types import OdiffResponse
from sentry.utils import json

_CLI_FLAG_MAP = {
"outputDiffMask": "diff-mask",
}

ODIFF_PLATFORM_SUFFIXES = {
("arm64", "Darwin"): "macos-arm64",
("aarch64", "Darwin"): "macos-arm64",
("x86_64", "Darwin"): "macos-x64",
("x86_64", "Linux"): "linux-x64",
("aarch64", "Linux"): "linux-arm64",
("arm64", "Linux"): "linux-arm64",
}


def _find_odiff_binary() -> str:
suffix = ODIFF_PLATFORM_SUFFIXES.get((platform.machine(), platform.system()))

current = Path(__file__).resolve()
for parent in current.parents:
if (parent / "pyproject.toml").exists():
if suffix:
raw = parent / "node_modules" / "odiff-bin" / "raw_binaries" / f"odiff-{suffix}"
if raw.exists():
return str(raw)
# odiff-bin's post_install.js copies the platform-specific raw binary here with +x
# on every OS; the .exe suffix is a package convention, not Windows-only.
binary = parent / "node_modules" / "odiff-bin" / "bin" / "odiff.exe"
if binary.exists():
return str(binary)
break

found = shutil.which("odiff")
Expand All @@ -42,42 +27,6 @@ def _find_odiff_binary() -> str:
raise FileNotFoundError("odiff binary not found. Run 'pnpm install' to install odiff-bin.")


def compare_cli(
base_path: str | Path,
compare_path: str | Path,
output_path: str | Path,
**options: object,
) -> OdiffResponse:
"""Run a single odiff comparison via CLI (no server mode).

Workaround for server-mode stdin buffer reuse bug
(https://github.com/dmtrKovalenko/odiff/pull/170).
Remove once the fix is released in odiff-bin.
"""
binary = _find_odiff_binary()
cmd = [binary, str(base_path), str(compare_path), str(output_path)]

for key, value in options.items():
flag = _CLI_FLAG_MAP.get(key, key)
if isinstance(value, bool):
if value:
cmd.append(f"--{flag}")
else:
cmd.extend([f"--{flag}", str(value)])

result = subprocess.run(cmd, capture_output=True, timeout=30)

# Exit codes: 0=match, 21=layout diff, 22=pixel diff
if result.returncode not in (0, 21, 22):
stderr = result.stderr.decode(errors="replace").strip()
raise RuntimeError(f"odiff CLI error (exit {result.returncode}): {stderr}")

return OdiffResponse(
requestId=0,
match=(result.returncode == 0),
)


class OdiffServer:
def __init__(self) -> None:
self._process: subprocess.Popen[bytes] | None = None
Expand Down
Loading
Loading