scripts/tests/reproduction_utils/test_extract_function.py
scripts/tests/reproduction_utils/test_extract_function.pyBrowse 41 files
1,754 tokens
6,420 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 9import mechanical_refactor_reproduction_utils as rr10from mechanical_refactor_reproduction_utils import (11 Repro,12 _def_span,13 _find_class,14 _find_def,15 _replace_span,16 _slice_span,17 dedent,18 exec_command,19 git_add_and_commit,20 verify_mechanical_refactor,21)22from reproduction_testlib import _apply, _commit, _git, _write # noqa: F40123 24# --- extract_function ----------------------------------------------------------25 26 27def test_extract_function_relocates_body_and_replaces_with_call(tmp_path: Path) -> None:28 """An inline block is cut verbatim, re-indented under the new signature, and the call site29 replaced; the body lands at function-body indent."""30 (tmp_path / "src.py").write_text(31 "class Q:\n"32 " def run(self, n):\n"33 " total = 0\n"34 " for i in range(n):\n"35 " total += i * i\n"36 " return total\n"37 )38 (tmp_path / "dst.py").write_text("def existing():\n return 0\n")39 body = " total = 0\n for i in range(n):\n total += i * i\n"40 r = Repro("b", "t").extract_function(41 "src.py",42 "dst.py",43 name="sum_squares",44 signature="def sum_squares(n):",45 body=body,46 body_indent=8,47 call=" total = sum_squares(n)\n",48 return_text=" return total\n",49 )50 _apply(r, tmp_path)51 src_out = (tmp_path / "src.py").read_text()52 assert " total = sum_squares(n)\n" in src_out53 assert "for i in range(n)" not in src_out54 assert (55 "def sum_squares(n):\n"56 " total = 0\n"57 " for i in range(n):\n"58 " total += i * i\n"59 " return total\n"60 ) in (tmp_path / "dst.py").read_text()61 62 63def test_extract_function_inserts_before_named_sibling(tmp_path: Path) -> None:64 """With before=, the new function lands immediately above that sibling at module level."""65 (tmp_path / "src.py").write_text("x = compute()\n")66 (tmp_path / "dst.py").write_text(67 "def a():\n return 1\n\n\ndef c():\n return 3\n"68 )69 r = Repro("b", "t").extract_function(70 "src.py",71 "dst.py",72 name="b",73 signature="def b():",74 body="x = compute()\n",75 body_indent=0,76 call="x = b()\n",77 return_text=" return x\n",78 before="c",79 )80 _apply(r, tmp_path)81 dst_out = (tmp_path / "dst.py").read_text()82 assert dst_out.index("def a") < dst_out.index("def b") < dst_out.index("def c")83 assert "x = b()\n" == (tmp_path / "src.py").read_text()84 85 86def test_extract_function_asserts_block_not_unique(tmp_path: Path) -> None:87 """A block that occurs more than once in the source raises, so the cut is unambiguous."""88 (tmp_path / "src.py").write_text("p = f()\np = f()\n")89 (tmp_path / "dst.py").write_text("def z():\n return 0\n")90 r = Repro("b", "t").extract_function(91 "src.py",92 "dst.py",93 name="g",94 signature="def g():",95 body="p = f()\n",96 body_indent=0,97 call="p = g()\n",98 )99 with pytest.raises(AssertionError):100 _apply(r, tmp_path)101 102 103# --- adversarial audit: module extraction ----------------------------------------104 105 106# --- adversarial audit: extract_function -----------------------------------------107 108 109def test_extract_function_does_not_pad_blank_lines_in_the_body(tmp_path: Path) -> None:110 """Interior blank lines of the extracted body stay bare newlines, unpadded."""111 (tmp_path / "src.py").write_text(" a = 1\n\n b = 2\n")112 (tmp_path / "dst.py").write_text("def z():\n return 0\n")113 r = Repro("b", "t").extract_function(114 "src.py",115 "dst.py",116 name="g",117 signature="def g():",118 body=" a = 1\n\n b = 2\n",119 body_indent=8,120 call=" g()\n",121 )122 _apply(r, tmp_path)123 assert (tmp_path / "src.py").read_text() == " g()\n"124 assert (tmp_path / "dst.py").read_text() == (125 "def z():\n return 0\n\ndef g():\n a = 1\n\n b = 2\n"126 )127 128 129def test_extract_function_does_not_reindent_string_literal_interiors(130 tmp_path: Path,131) -> None:132 """Triple-quoted string interior lines keep their exact bytes through the extraction."""133 (tmp_path / "src.py").write_text(134 "TEMPLATE = '''\nliteral line\n'''\nx = TEMPLATE\n"135 )136 (tmp_path / "dst.py").write_text("def existing():\n return 0\n")137 r = Repro("b", "t").extract_function(138 "src.py",139 "dst.py",140 name="make",141 signature="def make():",142 body="TEMPLATE = '''\nliteral line\n'''\nx = TEMPLATE\n",143 body_indent=0,144 call="x = make()\n",145 return_text=" return x\n",146 )147 _apply(r, tmp_path)148 assert "\nliteral line\n" in (tmp_path / "dst.py").read_text()149 150 151def test_extract_function_rejects_a_mid_line_substring_match(tmp_path: Path) -> None:152 """A body that only matches mid-line must fail loudly instead of splicing the call."""153 (tmp_path / "src.py").write_text("value = prefix_total = 0\n")154 (tmp_path / "dst.py").write_text("def z():\n return 0\n")155 r = Repro("b", "t").extract_function(156 "src.py",157 "dst.py",158 name="g",159 signature="def g():",160 body="total = 0\n",161 body_indent=0,162 call="total = g()\n",163 )164 with pytest.raises(AssertionError):165 _apply(r, tmp_path)166 167 168def test_extract_function_into_class_indents_body_to_method_depth(169 tmp_path: Path,170) -> None:171 """Extracting into a class must indent the relocated body to method depth."""172 (tmp_path / "src.py").write_text("val = compute_thing()\n")173 (tmp_path / "dst.py").write_text(174 "class H:\n def last(self):\n return 0\n"175 )176 r = Repro("b", "t").extract_function(177 "src.py",178 "dst.py",179 name="helper",180 signature=" def helper(self):",181 body="val = compute_thing()\n",182 body_indent=0,183 call="val = h.helper()\n",184 return_text=" return val\n",185 into_class="H",186 )187 _apply(r, tmp_path)188 out = (tmp_path / "dst.py").read_text()189 compile(out, "dst.py", "exec")190 assert " def helper(self):\n val = compute_thing()\n" in out191