diff --git a/wizard-work/forge/verify_reply_check_dry_refactor.py b/wizard-work/forge/verify_reply_check_dry_refactor.py new file mode 100644 index 0000000..c94ebf0 --- /dev/null +++ b/wizard-work/forge/verify_reply_check_dry_refactor.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +"""Verify the report() DRY refactor in tools/reply_check.py is behavior-preserving. + +Smell: report() built a `counts` tally with a manual `counts.get(c,0)+1` loop and +then, for EACH of the 12 classes in FAULTS+ADVISORIES, re-scanned ALL rows to find +the files carrying that class (1 tally + 12 per-class scans = 13 scans of `rows`). + +Refactor: a single pass over `rows` builds both a `Counter` (the tally) and a +`defaultdict(list)` grouping rows by class. The per-class print sweep now reads +the pre-grouped list instead of re-scanning. Row order within each class is +filename order (scan() produces rows sorted by name), so rendered output and the +JSON `counts` are byte-identical to the per-class `for row in rows` sweep. + +This verifier runs the original (HEAD) and the refactored (working tree) against +the SAME live repo and compares: + [1] selftest: original rc 0 + [2] selftest: refactored rc 0 + [3] selftest: stdout byte-identical + [4] live scan --json: rc identical + [5] live scan --json: stdout byte-identical + [6] live scan --json: JSON byte-identical (sha256) + [7] emit-baseline: rc identical + [8] emit-baseline: stdout byte-identical + +Exit 0 iff all pass, else 1. The command's own exit code is the verdict. +""" +import hashlib +import os +import subprocess +import sys +import tempfile + +REPO = "/root/troupe-standup" +TOOL = "tools/reply_check.py" + + +def orig_source(): + """Materialize the pre-refactor module from git HEAD into a temp file.""" + out = tempfile.NamedTemporaryFile(suffix=".py", delete=False, mode="wb") + try: + subprocess.run(["git", "-C", REPO, "show", f"HEAD:{TOOL}"], + check=True, stdout=out, timeout=30) + finally: + out.close() + return out.name + + +def run(path, args, timeout=300): + p = subprocess.run([sys.executable, path, *args], + capture_output=True, text=True, cwd=REPO, timeout=timeout) + return p.returncode, p.stdout, p.stderr + + +def main(): + cells, failed = [], 0 + + def check(label, ok, detail=""): + cells.append((label, ok)) + nonlocal failed + if not ok: + failed += 1 + print(" [%s] %s%s" % ("ok " if ok else "FAIL", label, + (" [%s]" % detail) if detail and not ok else "")) + + orig_path = orig_source() + ref_path = os.path.join(REPO, TOOL) + tmpdir = tempfile.mkdtemp(prefix="reply_verify_") + try: + # --- [1-3] selftest parity --- + rc_o, so, _ = run(orig_path, ["--selftest"]) + rc_r, sr, _ = run(ref_path, ["--selftest"]) + check("selftest: original rc 0", rc_o == 0, f"rc={rc_o}") + check("selftest: refactored rc 0", rc_r == 0, f"rc={rc_r}") + check("selftest: identical stdout (byte-for-byte)", so == sr) + + # --- [4-6] live scan --json equivalence --- + # NOTE: --json prints `json: ` to stdout, and the two runs write + # to different temp dirs, so that line is env-specific -- not a behavior + # signal. We compare the JSON FILE by sha256 (the real census) and the + # report stdout with the `json:` line stripped. + oj_path = os.path.join(tmpdir, "orig.json") + rj_path = os.path.join(tmpdir, "ref.json") + rc_o, so_o, _ = run(orig_path, ["--repo", ".", "--json", oj_path]) + rc_r, sr_r, _ = run(ref_path, ["--repo", ".", "--json", rj_path]) + check("live scan: rc identical", rc_o == rc_r, f"orig={rc_o} ref={rc_r}") + + def strip_json_line(s): + return "".join(l for l in s.splitlines(keepends=True) + if not l.startswith("json: ")) + check("live scan: report stdout byte-identical (json path stripped)", + strip_json_line(so_o) == strip_json_line(sr_r) and len(so_o) > 0, + f"orig={len(so_o)} ref={len(sr_r)}") + + ok_o = os.path.exists(oj_path) and os.path.getsize(oj_path) > 0 + ok_r = os.path.exists(rj_path) and os.path.getsize(rj_path) > 0 + sha_o = (hashlib.sha256(open(oj_path, "rb").read()).hexdigest() + if ok_o else "NOFILE") + sha_r = (hashlib.sha256(open(rj_path, "rb").read()).hexdigest() + if ok_r else "NOFILE") + check("live scan: JSON byte-identical (sha256)", + ok_o and ok_r and sha_o == sha_r, f"orig={sha_o} ref={sha_r}") + + # --- [7-8] emit-baseline equivalence --- + rc_o, so_o, _ = run(orig_path, ["--emit-baseline"]) + rc_r, sr_r, _ = run(ref_path, ["--emit-baseline"]) + check("emit-baseline: rc identical", rc_o == rc_r, f"orig={rc_o} ref={rc_r}") + check("emit-baseline: stdout byte-identical", + so_o == sr_r and len(so_o) > 0, f"bytes={len(so_o)} vs {len(sr_r)}") + finally: + if os.path.exists(orig_path): + os.unlink(orig_path) + import shutil + shutil.rmtree(tmpdir, ignore_errors=True) + + total = len(cells) + passed = total - failed + print("\nverify: %d checks, %d passed, %d failed" % (total, passed, failed)) + return 1 if failed else 0 + + +if __name__ == "__main__": + sys.exit(main())