93 lines
2.6 KiB
Python
Executable file
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))
|