Spaces:
Running
Running
| """ | |
| 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()) | |