""" Compare two render-model JSON snapshots and report differences. """ import argparse import json import math import sys from pathlib import Path from typing import Any, List def load_json(path: Path) -> Any: with path.open("r", encoding="utf-8") as f: return json.load(f) def is_number(value: Any) -> bool: return isinstance(value, (int, float)) and not isinstance(value, bool) def nearly_equal(a: float, b: float, tol: float) -> bool: if math.isnan(a) and math.isnan(b): return True return abs(a - b) <= tol def compare(a: Any, b: Any, path: str, diffs: List[str], tol: float, max_diffs: int) -> None: if len(diffs) >= max_diffs: return if type(a) != type(b): diffs.append(f"{path}: type {type(a).__name__} != {type(b).__name__}") return if isinstance(a, dict): a_keys = set(a.keys()) b_keys = set(b.keys()) for key in sorted(a_keys - b_keys): diffs.append(f"{path}.{key}: missing in candidate") if len(diffs) >= max_diffs: return for key in sorted(b_keys - a_keys): diffs.append(f"{path}.{key}: extra in candidate") if len(diffs) >= max_diffs: return for key in sorted(a_keys & b_keys): compare(a[key], b[key], f"{path}.{key}", diffs, tol, max_diffs) if len(diffs) >= max_diffs: return return if isinstance(a, list): if len(a) != len(b): diffs.append(f"{path}: list length {len(a)} != {len(b)}") min_len = min(len(a), len(b)) for idx in range(min_len): compare(a[idx], b[idx], f"{path}[{idx}]", diffs, tol, max_diffs) if len(diffs) >= max_diffs: return return if is_number(a) and is_number(b) and tol > 0: if not nearly_equal(float(a), float(b), tol): diffs.append(f"{path}: {a} != {b} (tol={tol})") return if a != b: diffs.append(f"{path}: {a!r} != {b!r}") def main() -> int: parser = argparse.ArgumentParser(description="Compare render-model JSON snapshots.") parser.add_argument("--baseline", type=Path, required=True, help="Baseline JSON path") parser.add_argument("--candidate", type=Path, required=True, help="Candidate JSON path") parser.add_argument("--float-tol", type=float, default=0.0, help="Float comparison tolerance") parser.add_argument("--max-diffs", type=int, default=200, help="Max diffs to display") args = parser.parse_args() baseline = load_json(args.baseline) candidate = load_json(args.candidate) diffs: List[str] = [] compare(baseline, candidate, "$", diffs, args.float_tol, args.max_diffs) if diffs: print(f"Differences found: {len(diffs)}") for line in diffs: print(line) return 1 print("No differences found.") return 0 if __name__ == "__main__": sys.exit(main())