File size: 8,796 Bytes
0b19a1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#!/usr/bin/env python3
"""Shared pieces: loading, fence stripping, the two oracles, and the mandatory harness self-tests.

Two oracles are applied to the SAME predictions on the SAME benchmark, so the delta between them
is a measurement rather than an opinion.

  OURS   -- `pybytecode_core.verify.code_fingerprint`. Recompile the prediction and require the
            resulting code object to be byte-identical to the reference's, recursively, INCLUDING
            docstrings and `co_exceptiontable`. Sound: a pass is a proof, never a guess.
  THEIRS -- `pylingual.equivalence_check.compare_pyc`, imported not reimplemented, so no one can
            say we loosened their bar. CFG-coarsened and docstring-blind (measured, not assumed).
            OPTIONAL: absent PyLingual, everything below still runs on our oracle alone.

Two self-tests gate every score this harness prints, per standing foundry discipline:

  PRE-FLIGHT     grade every reference label against itself. A byte-perfect model MUST score 100%.
                 Anything less means the harness is broken and no score may be quoted.
  MUTATION TEST  deliberately corrupt each label and confirm the oracle KILLS it. A grader that
                 passes mutants is a stub and its scores are meaningless.
"""
from __future__ import annotations

import ast
import json
import py_compile
import re
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))

from config import resolve_bench_asset  # noqa: E402
from pybytecode_core.verify import code_fingerprint  # noqa: E402

FENCE = re.compile(r"```(?:python|py)?\s*\n(.*?)(?:```|\Z)", re.S)


def strip_fences(text: str) -> str:
    m = FENCE.search(text or "")
    return (m.group(1) if m else (text or "")).strip()


def load_jsonl(path: str | Path) -> list[dict]:
    return [json.loads(l) for l in Path(path).read_text().splitlines() if l.strip()]


def load_bench(path: str | Path) -> tuple[dict[int, dict], Path]:
    """Return {i: row} with every row's `pyc_path` rewritten to a path that exists HERE."""
    bench_file = Path(path).resolve()
    rows = {}
    for r in load_jsonl(bench_file):
        r["pyc_path"] = str(resolve_bench_asset(bench_file, r.get("pyc_path", ""), "pyc", r["i"]))
        r["src_path"] = str(resolve_bench_asset(bench_file, r.get("src_path", ""), "src", r["i"]))
        rows[r["i"]] = r
    return rows, bench_file


# ------------------------------------------------------------------ our oracle
def ours_ok(pred_src: str, expected_src: str) -> bool:
    """Byte-identical code object, docstrings and exception tables included."""
    try:
        g = compile(pred_src, "<pred>", "exec", dont_inherit=True, optimize=0)
        w = compile(expected_src, "<ref>", "exec", dont_inherit=True, optimize=0)
        return code_fingerprint(g) == code_fingerprint(w)
    except Exception:  # noqa: BLE001
        return False


# ---------------------------------------------------------------- their oracle
_compare_pyc = None


def pylingual_available() -> bool:
    global _compare_pyc
    if _compare_pyc is None:
        try:
            from pylingual.equivalence_check import compare_pyc
            _compare_pyc = compare_pyc
        except Exception:  # noqa: BLE001
            _compare_pyc = False
    return _compare_pyc is not False


def theirs_ok(pred_src: str, ref_pyc: Path, tmp: Path, tag: str) -> tuple[bool, str]:
    """PyLingual's own definition of Perfect: recompile, compare_pyc, all-or-nothing."""
    if not pylingual_available():
        return False, "pylingual not installed"
    if not pred_src.strip():
        return False, "empty"
    try:
        ast.parse(pred_src)
    except SyntaxError:
        return False, "syntax error"
    p, c = tmp / f"{tag}.py", tmp / f"{tag}.pyc"
    try:
        p.write_text(pred_src, encoding="utf-8")
        py_compile.compile(str(p), cfile=str(c), doraise=True, optimize=0)
    except Exception:  # noqa: BLE001
        return False, "does not compile"
    try:
        results = _compare_pyc(Path(ref_pyc), c)
    except Exception as e:  # noqa: BLE001
        return False, f"oracle error: {type(e).__name__}"
    if not results:
        return False, "oracle returned no results"
    if all(r.success for r in results):
        return True, "PERFECT"
    notes = [str(getattr(r, "note", "")) for r in results if not r.success]
    return False, "semantic error: " + "; ".join(n for n in notes[:2] if n)[:120]


# ------------------------------------------------------------------ docstrings
def docstrings_of(src: str) -> list[str]:
    out = []
    try:
        tree = ast.parse(src)
    except SyntaxError:
        return out
    for n in ast.walk(tree):
        if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Module)):
            d = ast.get_docstring(n, clean=False)
            if d is not None:
                out.append(d)
    return out


# ------------------------------------------------------------------ self-tests
def mutations(src: str) -> list[tuple[str, str]]:
    """Semantically REAL corruptions of `src`, each of which a sound oracle must reject.

    Candidates that do not actually change the program are discarded rather than counted. The
    `return_none` rewrite turns `return x` into `return None #x`, which is a genuine change --
    but applied to a bare `return None` it produces `return None #None`, differing only by a
    comment. Counting that as a surviving mutant would blame the oracle for being right; the
    original harness scored 131/131 only because no row in its first 120 had a bare
    `return None`, and this benchmark has three.

    The no-op filter compares ASTs, NOT the oracle under test, so it cannot launder a real
    mutant into a discarded one: `ast.dump` is blind to comments and formatting and to nothing
    else.
    """
    try:
        base = ast.dump(ast.parse(src))
    except SyntaxError:
        return []

    candidates = []
    for name, a, b in (("plus_to_minus", " + ", " - "), ("eq_to_ne", " == ", " != "),
                       ("lt_to_gt", " < ", " > "), ("and_to_or", " and ", " or ")):
        if a in src:
            candidates.append((name, src.replace(a, b, 1)))
    if "return " in src:
        candidates.append(("return_none", src.replace("return ", "return None #", 1)))

    out = []
    for name, m in candidates:
        try:
            if ast.dump(ast.parse(m)) != base:
                out.append((name, m))
        except SyntaxError:
            continue  # a mutant that does not parse tests nothing about the oracle
    return out


def self_test(bench: dict[int, dict], oracle: str = "ours", mutation_rows: int = 120) -> dict:
    """Pre-flight + mutation test. `oracle` is "ours" or "theirs"."""
    import tempfile

    with tempfile.TemporaryDirectory() as td:
        tmp = Path(td)

        def ok(src: str, row: dict, tag: str) -> bool:
            if oracle == "ours":
                return ours_ok(src, row["expected"])
            return theirs_ok(src, Path(row["pyc_path"]), tmp, tag)[0]

        pf_pass, pf_fail, failures = 0, 0, []
        for i, r in bench.items():
            if ok(r["expected"], r, f"pf{i}"):
                pf_pass += 1
            else:
                pf_fail += 1
                if len(failures) < 5:
                    failures.append({"i": i, "func": r.get("csn_func", "")})

        killed = survived = 0
        survivors = []
        for i, r in list(bench.items())[:mutation_rows]:
            for name, m in mutations(r["expected"]):
                if ok(m, r, f"mut{i}"):
                    survived += 1
                    if len(survivors) < 5:
                        survivors.append({"i": i, "mutation": name})
                else:
                    killed += 1

    total = killed + survived
    return {
        "oracle": oracle,
        "preflight_n": len(bench),
        "preflight_perfect": pf_pass,
        "preflight_failed": pf_fail,
        "preflight_pct": round(100 * pf_pass / max(1, len(bench)), 2),
        "preflight_failures": failures,
        "mutation_total": total,
        "mutation_killed": killed,
        "mutation_survived": survived,
        "mutation_kill_rate_pct": round(100 * killed / max(1, total), 2),
        "mutation_survivors": survivors,
        "SOUND": pf_fail == 0 and survived == 0,
    }


def require_sound(st: dict) -> None:
    """A harness that fails either self-test may not report a score. Refuse, loudly."""
    if not st["SOUND"]:
        print(json.dumps(st, indent=2), file=sys.stderr)
        raise SystemExit(
            f"REFUSING TO SCORE: preflight {st['preflight_perfect']}/{st['preflight_n']}, "
            f"mutation kill rate {st['mutation_kill_rate_pct']}%. Both must be 100%."
        )