#!/usr/bin/env python3 """Compare two runs of the identical Wii conformance DOL.""" import argparse import csv import sys from pathlib import Path METADATA = ("schema", "rounds", "observations", "ios_version", "ios_revision") HEADER = ["seq", "test", "round", "value", "extra"] def read_capture(path: Path): with path.open(newline="", encoding="utf-8") as stream: rows = csv.reader(stream) metadata = {} for key in METADATA: row = next(rows, None) if row is None or len(row) != 2 or row[0] != key: raise ValueError(f"{path}: expected metadata row {key!r}") metadata[key] = int(row[1]) if next(rows, None) != HEADER: raise ValueError(f"{path}: invalid observation header") observations = [] for line, row in enumerate(rows, start=7): if len(row) != 5: raise ValueError(f"{path}:{line}: expected 5 columns") seq, test, round_number, value, extra = row if int(seq) != len(observations): raise ValueError(f"{path}:{line}: sequence is not contiguous") observations.append((test, int(round_number), int(value), int(extra))) if len(observations) != metadata["observations"]: raise ValueError( f"{path}: expected {metadata['observations']} observations, " f"found {len(observations)}" ) return metadata, observations def compare(reference, candidate, limit=20): reference_meta, reference_rows = reference candidate_meta, candidate_rows = candidate for key in METADATA: if reference_meta[key] != candidate_meta[key]: print(f"Incomparable runs: {key}: Wii={reference_meta[key]}, Dolphin={candidate_meta[key]}") return 2 differences = 0 for index in range(max(len(reference_rows), len(candidate_rows))): expected = reference_rows[index] if index < len(reference_rows) else None actual = candidate_rows[index] if index < len(candidate_rows) else None if expected == actual: continue differences += 1 if differences <= limit: print(f"#{index} Wii={expected} Dolphin={actual}") if differences: print(f"{differences} differences across {max(len(reference_rows), len(candidate_rows))} observations") return 1 print(f"Identical: {len(reference_rows)} observations") return 0 def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("wii", type=Path, help="CSV from the real Wii") parser.add_argument("dolphin", type=Path, help="CSV from Dolphin") args = parser.parse_args() try: return compare(read_capture(args.wii), read_capture(args.dolphin)) except (OSError, ValueError) as error: print(error, file=sys.stderr) return 2 if __name__ == "__main__": raise SystemExit(main())