joeylib2/tools/diff-uber-hashes

93 lines
2.6 KiB
Python
Executable file

#!/usr/bin/env python3
"""Compare two UBER joeylog.txt files by per-op surface hash.
Used by the planar 68k rewrite (project_planar_68k_plan.md): IIgs
captures the golden reference, each 68k port re-runs UBER after a
primitive conversion, and this tool tells you which ops produced
different pixels. Without this, "looks right visually" misses the
subtle mismatches that cascade into hard-to-debug corruption.
Usage:
tools/diff-uber-hashes <reference-log> <test-log>
Exit code:
0 = all hashes match
1 = at least one mismatch
2 = usage error or missing file
"""
import re
import sys
# Match e.g.:
# UBER: drawCircle r=80: 56 iters / 4 frames = 840 ops/sec | hash=A1B2C3D4
LINE_RE = re.compile(
r"UBER:\s+(?P<op>[^:]+):\s+\d+\s+iters\s+/\s+\d+\s+frames\s+=\s+\d+\s+ops/sec\s+\|\s+hash=(?P<hash>[0-9A-Fa-f]+)"
)
def parse_log(path):
"""Return ordered dict {op_name: hash} from a UBER log file.
Multiple runs may be concatenated in the same log (joeyLog appends)
-- in that case the LAST hash for each op wins, matching the most
recent run.
"""
hashes = {}
with open(path) as f:
for line in f:
m = LINE_RE.search(line)
if m:
hashes[m.group("op").strip()] = m.group("hash").upper()
return hashes
def main(argv):
if len(argv) != 3:
sys.stderr.write(
"usage: diff-uber-hashes <reference-log> <test-log>\n"
)
return 2
try:
ref = parse_log(argv[1])
test = parse_log(argv[2])
except OSError as e:
sys.stderr.write(f"error: {e}\n")
return 2
if not ref:
sys.stderr.write(f"error: no UBER hash lines found in {argv[1]}\n")
return 2
if not test:
sys.stderr.write(f"error: no UBER hash lines found in {argv[2]}\n")
return 2
mismatches = 0
matches = 0
for op, ref_hash in ref.items():
test_hash = test.get(op)
if test_hash is None:
print(f" MISSING in test: {op} (ref={ref_hash})")
mismatches += 1
elif test_hash != ref_hash:
print(f" MISMATCH {op}: ref={ref_hash} test={test_hash}")
mismatches += 1
else:
matches += 1
extras = [op for op in test if op not in ref]
for op in extras:
print(f" EXTRA in test: {op} (test={test[op]})")
total = len(ref) + len(extras)
print()
if mismatches == 0 and not extras:
print(f"OK: {matches}/{total} ops match")
return 0
print(f"FAIL: {matches} match, {mismatches} mismatch, {len(extras)} extras")
return 1
if __name__ == "__main__":
sys.exit(main(sys.argv))