scripts/tests/proof_generator/test_script_and_diff.py
scripts/tests/proof_generator/test_script_and_diff.pyBrowse 41 files
1,720 tokens
6,890 bytes
Token encoding: o200k_base
Snapshot a9fb1c3
← Back to SKILL.md
1import subprocess2import sys3from pathlib import Path4 5import pytest6 7sys.path.insert(0, str(Path(__file__).resolve().parents[2]))8 9from generator_testlib import ( # noqa: F40110 _commit,11 _free_function_move_with_module_level_caller,12 _git,13 _method_onto_class,14 _write,15)16from mechanical_refactor_proof_generator import (17 infer_recipe,18 recipe_to_script,19)20 21 22def test_recipe_to_script_is_self_contained_and_ordered(repo: Path) -> None:23 """The emitted script imports only the reproduce util and lowers before moving."""24 _method_onto_class(repo)25 script = recipe_to_script(infer_recipe("HEAD", str(repo)), "move foo onto C")26 assert "from mechanical_refactor_reproduction_utils import Repro" in script27 assert script.index("lower_call_sites") < script.index("move_symbol")28 assert "residual = r.run()" in script29 assert "sys.exit(1 if residual else 0)" in script30 # importing nothing else from the skill keeps the script auditable in isolation31 assert "mechanical_refactor_verify_utils" not in script32 assert "mechanical_refactor_proof_generator" not in script33 34 35def test_recipe_to_script_orders_import_ops_after_moves(repo: Path) -> None:36 """The emitted script applies module-level import add/remove AFTER the move, matching37 build_repro's run order so the script and the in-process verdict cannot diverge."""38 _free_function_move_with_module_level_caller(repo)39 script = recipe_to_script(infer_recipe("HEAD", str(repo)), "move resolve to util")40 assert script.index("move_symbol") < script.index("remove_imported_name")41 assert script.index("move_symbol") < script.index("add_import")42 43 44def _emit_runnable_script(repo: Path, out: Path, commit: str, subject: str) -> Path:45 """Write the emitted script plus its util dependency into a proof-folder layout."""46 scripts_dir = out / "repro_scripts"47 scripts_dir.mkdir(parents=True, exist_ok=True)48 utils_src = Path(__file__).resolve().parents[2] / (49 "mechanical_refactor_reproduction_utils.py"50 )51 (out / "mechanical_refactor_reproduction_utils.py").write_text(52 utils_src.read_text()53 )54 script = recipe_to_script(infer_recipe(commit, str(repo)), subject)55 script_path = scripts_dir / f"{commit[:9]}.py"56 script_path.write_text(script)57 return script_path58 59 60def test_emitted_script_exits_zero_on_faithful_commit(61 repo: Path, tmp_path: Path62) -> None:63 """Running the emitted script on a clean move exits 0 and prints the PASS verdict."""64 _write(65 repo,66 **{67 "model.py": "def keep():\n return 0\n\n\ndef resolve(m):\n return m\n",68 "util.py": "import os\n",69 "caller.py": (70 "from model import resolve\n\n\ndef run(m):\n return resolve(m)\n"71 ),72 },73 )74 _commit(repo, "base")75 # The after-state is the primitives' exact output (this bare repo has no formatter76 # to absorb the cut's leftover blank lines, unlike a pre-commit-clean real repo).77 _write(78 repo,79 **{80 "model.py": "def keep():\n return 0\n\n\n",81 "util.py": "import os\n\ndef resolve(m):\n return m\n",82 "caller.py": (83 "from util import resolve\n\n\ndef run(m):\n return resolve(m)\n"84 ),85 },86 )87 commit = _commit(repo, "move resolve to util")88 script_path = _emit_runnable_script(repo, tmp_path / "out", commit, "move")89 90 result = subprocess.run(91 [sys.executable, str(script_path)], cwd=repo, capture_output=True, text=True92 )93 94 assert result.returncode == 0, result.stdout + result.stderr95 assert "PASS" in result.stdout96 97 98def test_emitted_script_exits_nonzero_on_bundled_change(99 repo: Path, tmp_path: Path100) -> None:101 """A commit bundling a non-move change makes the emitted script exit non-zero."""102 _write(103 repo,104 **{105 "model.py": "def keep():\n return 0\n\n\ndef resolve(m):\n return m\n",106 "util.py": "import os\n",107 },108 )109 _commit(repo, "base")110 _write(111 repo,112 **{113 "model.py": "def keep():\n return 99\n",114 "util.py": "import os\n\n\ndef resolve(m):\n return m\n",115 },116 )117 commit = _commit(repo, "move resolve AND change keep")118 script_path = _emit_runnable_script(repo, tmp_path / "out", commit, "dirty move")119 120 result = subprocess.run(121 [sys.executable, str(script_path)], cwd=repo, capture_output=True, text=True122 )123 124 assert result.returncode == 1, result.stdout + result.stderr125 assert "RESIDUAL" in result.stdout126 127 128def test_emitted_script_passes_on_move_above_typechecking_guard(129 repo: Path, tmp_path: Path130) -> None:131 """A module-level def relocated to just above an ``if TYPE_CHECKING:`` guard reproduces132 via an inferred after= anchor and the emitted script exits 0."""133 _write(134 repo,135 **{136 "model.py": (137 "def keep():\n return 0\n\n\ndef helper(x):\n return x + 1\n"138 ),139 "util.py": (140 "from u import is_hip\n"141 "\n"142 "_is_hip = is_hip()\n"143 "\n"144 "if TYPE_CHECKING:\n"145 " from m import Thing\n"146 ),147 },148 )149 _commit(repo, "base")150 # After-state = the primitive's exact output (bare repo, no formatter to absorb blanks).151 _write(152 repo,153 **{154 "model.py": "def keep():\n return 0\n\n\n",155 "util.py": (156 "from u import is_hip\n"157 "\n"158 "_is_hip = is_hip()\n"159 "\n"160 "def helper(x):\n"161 " return x + 1\n"162 "\n"163 "if TYPE_CHECKING:\n"164 " from m import Thing\n"165 ),166 },167 )168 commit = _commit(repo, "move helper above the TYPE_CHECKING guard")169 script_path = _emit_runnable_script(170 repo, tmp_path / "out", commit, "after-anchor move"171 )172 173 result = subprocess.run(174 [sys.executable, str(script_path)], cwd=repo, capture_output=True, text=True175 )176 177 assert result.returncode == 0, result.stdout + result.stderr178 assert "PASS" in result.stdout179 assert "after='_is_hip'" in script_path.read_text()180 181 182def test_per_file_diff_keeps_content_lines_starting_with_plus_signs(repo: Path) -> None:183 """An added content line beginning with '++' is collected, not mistaken for a header."""184 from mechanical_refactor_proof_generator import _per_file_diff185 186 _write(repo, **{"notes.py": "a = 1\n"})187 _commit(repo, "base")188 _write(repo, **{"notes.py": 'a = 1\nb = "++x"\n'})189 commit = _commit(repo, "add plus-plus line")190 191 files = _per_file_diff(commit, str(repo))192 193 assert files["notes.py"]["added"] == ['b = "++x"']194