"""Canonical benchmark result handling (spec 08, test-pipeline-validation). Every implementation of a benchmark query returns the same canonical rows: tuples of str / int / float columns. Rows are compared SORTED; floats agree within 1e-9. The canonical expected artifacts are produced from PostgreSQL and cross-validated against SQLite (task 08); every measured run (task 09) re-validates its rows against them. Two serializations: - full: floats via repr() - lossless, used for stdout and expected/*.rows.csv; - rounded (6 decimals): used ONLY for the sha256 fingerprint, so engines whose aggregate arithmetic differs in the last bits hash identically. The tolerant row comparison is the authoritative check; the sha256 is a compact fingerprint for reports and markers. """ from __future__ import annotations import hashlib import sys from common.corpus import coerce FLOAT_TOL = 1e-9 Row = tuple def sort_rows(rows: list[Row]) -> list[Row]: return sorted(rows) def _fmt(value, rounded: bool) -> str: if isinstance(value, float): return f"{round(value, 6):.6f}" if rounded else repr(value) return str(value) def serialize(rows: list[Row], rounded: bool = False) -> str: return "\n".join(",".join(_fmt(v, rounded) for v in row) for row in rows) + "\n" def sha256_of(rows: list[Row]) -> str: return hashlib.sha256(serialize(sort_rows(rows), rounded=True).encode("ascii")).hexdigest() def print_rows(rows: list[Row]) -> None: sys.stdout.write(serialize(sort_rows(rows))) def parse_rows(text: str) -> list[Row]: rows = [] for line in text.splitlines(): if line: rows.append(tuple(coerce(cell) for cell in line.split(","))) return rows def compare(expected: list[Row], actual: list[Row], tol: float = FLOAT_TOL) -> str | None: """Return None when equal within tolerance, else a first-difference message.""" exp, act = sort_rows(expected), sort_rows(actual) if len(exp) != len(act): return f"row count {len(act)} != expected {len(exp)}" for i, (er, ar) in enumerate(zip(exp, act)): if len(er) != len(ar): return f"row {i}: arity {len(ar)} != {len(er)}" for j, (ev, av) in enumerate(zip(er, ar)): if isinstance(ev, float) or isinstance(av, float): if abs(float(ev) - float(av)) > tol: return f"row {i} col {j}: {av!r} != {ev!r} (tol {tol})" elif ev != av: return f"row {i} col {j}: {av!r} != {ev!r}" return None