import json, shutil, subprocess, time, sys from pathlib import Path REPO = Path(__file__).resolve().parents[2] BACKUP_DIR = REPO / "scripts" / "qc_flag" / ".backups" RECEIPTS_DIR = REPO / "scripts" / "qc_flag" / "receipts" def run_single(src_path, mut_path, target="SilverSightRRC", timeout=60): """Backup → apply mutation → build → restore. Returns dict with result.""" BACKUP_DIR.mkdir(parents=True, exist_ok=True) backup = BACKUP_DIR / src_path.name if not backup.exists(): shutil.copy2(src_path, backup) orig = src_path.read_text() src_path.write_bytes(mut_path.read_bytes()) t0 = time.perf_counter() try: r = subprocess.run( ["lake", "build", target], cwd=REPO, capture_output=True, text=True, timeout=timeout ) exit_code = r.returncode output = (r.stdout + r.stderr)[-500:] except subprocess.TimeoutExpired: exit_code = -1 output = "TIMEOUT" except FileNotFoundError: exit_code = -2 output = "lake not found" src_path.write_text(orig) elapsed = round(time.perf_counter() - t0, 2) mut_id = mut_path.stem.split("_")[0] actual = "FAIL" if exit_code != 0 else "PASS" return { "mutation_id": mut_id, "source": str(src_path), "expected": "FAIL", "actual": actual, "exit_code": exit_code, "elapsed_s": elapsed, "build_log_snippet": output, } def compute_coverage(results): total = len(results) failed = sum(1 for r in results if r["actual"] == "FAIL") return { "total": total, "failed_expected": failed, "passed_unexpectedly": total - failed, "coverage_ratio": round(failed / total, 4) if total else 0, "verdict": "PASS" if total > 0 and failed == total else "FAIL", } def emit_receipt(results, coverage): RECEIPTS_DIR.mkdir(parents=True, exist_ok=True) path = RECEIPTS_DIR / f"coverage_{int(time.time())}.json" path.write_text(json.dumps({ "schema": "mutation_coverage_receipt_v1", "generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "coverage": coverage, "entries": results, }, indent=2)) return path