scripts/tests/reproduction_utils/test_add_imports.py
scripts/tests/reproduction_utils/test_add_imports.pyBrowse 41 files
2,654 tokens
9,598 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# --- add_import ----------------------------------------------------------------25 26 27def test_add_import_appends_after_last_top_level_import(tmp_path: Path) -> None:28 """A new import is inserted right after the last module-level import."""29 (tmp_path / "m.py").write_text("import os\nimport sys\n\nx = 1\n")30 r = Repro("b", "t").add_import("m.py", "from pkg import Thing")31 _apply(r, tmp_path)32 assert (33 tmp_path / "m.py"34 ).read_text() == "import os\nimport sys\nfrom pkg import Thing\n\nx = 1\n"35 36 37# --- add_imported_name ---------------------------------------------------------38 39 40def test_add_imported_name_extends_a_single_line_import(tmp_path: Path) -> None:41 """A new name is appended to an existing from-import on the same statement."""42 (tmp_path / "m.py").write_text("from pkg import a\n\nx = 1\n")43 r = Repro("b", "t").add_imported_name("m.py", module="pkg", name="b")44 _apply(r, tmp_path)45 assert (tmp_path / "m.py").read_text() == "from pkg import a, b\n\nx = 1\n"46 47 48def test_add_imported_name_carries_an_asname(tmp_path: Path) -> None:49 """The added name keeps its `as` alias."""50 (tmp_path / "m.py").write_text("from pkg import a\n")51 r = Repro("b", "t").add_imported_name("m.py", module="pkg", name="b", asname="c")52 _apply(r, tmp_path)53 assert (tmp_path / "m.py").read_text() == "from pkg import a, b as c\n"54 55 56def test_add_imported_name_refuses_a_commented_import(tmp_path: Path) -> None:57 """An import carrying comments is refused, since a rebuild would drop them."""58 (tmp_path / "m.py").write_text("from pkg import (\n a, # keep\n)\n")59 r = Repro("b", "t").add_imported_name("m.py", module="pkg", name="b")60 with pytest.raises(AssertionError):61 _apply(r, tmp_path)62 63 64def test_add_imported_name_rejects_a_name_already_present(tmp_path: Path) -> None:65 """Adding a name the import already has fails loudly."""66 (tmp_path / "m.py").write_text("from pkg import a, b\n")67 r = Repro("b", "t").add_imported_name("m.py", module="pkg", name="b")68 with pytest.raises(AssertionError):69 _apply(r, tmp_path)70 71 72def test_add_imported_name_raises_without_a_matching_import(tmp_path: Path) -> None:73 """A file lacking a `from module import` for the module fails loudly."""74 (tmp_path / "m.py").write_text("from other import a\n")75 r = Repro("b", "t").add_imported_name("m.py", module="pkg", name="b")76 with pytest.raises(AssertionError):77 _apply(r, tmp_path)78 79 80# --- repath_import / add_typechecking_import -----------------------------------81 82 83def test_add_typechecking_import_inserts_in_block(tmp_path: Path) -> None:84 """The import is appended inside the existing TYPE_CHECKING block."""85 (tmp_path / "m.py").write_text(86 "from typing import TYPE_CHECKING\n"87 "\n"88 "if TYPE_CHECKING:\n"89 " from a import X\n"90 "\n"91 "\n"92 "def f():\n"93 " pass\n"94 )95 r = Repro("b", "t").add_typechecking_import("m.py", "from b import Y")96 _apply(r, tmp_path)97 assert (tmp_path / "m.py").read_text() == (98 "from typing import TYPE_CHECKING\n"99 "\n"100 "if TYPE_CHECKING:\n"101 " from a import X\n"102 " from b import Y\n"103 "\n"104 "\n"105 "def f():\n"106 " pass\n"107 )108 109 110def test_add_typechecking_import_creates_missing_block(tmp_path: Path) -> None:111 """With no TYPE_CHECKING block, one is created after the trailing module import."""112 (tmp_path / "m.py").write_text(113 "from typing import TYPE_CHECKING\n\nfrom a import X\n\n\ndef f():\n pass\n"114 )115 r = Repro("b", "t").add_typechecking_import("m.py", "from b import Y")116 _apply(r, tmp_path)117 assert (tmp_path / "m.py").read_text() == (118 "from typing import TYPE_CHECKING\n"119 "\n"120 "from a import X\n"121 "\n"122 "if TYPE_CHECKING:\n"123 " from b import Y\n"124 "\n"125 "\n"126 "def f():\n"127 " pass\n"128 )129 130 131def test_add_import_into_an_empty_file(tmp_path: Path) -> None:132 """Adding an import to an empty file writes just the statement."""133 (tmp_path / "m.py").write_text("")134 r = Repro("b", "t").add_import("m.py", "import os")135 _apply(r, tmp_path)136 assert (tmp_path / "m.py").read_text() == "import os\n"137 138 139def test_add_import_lands_below_a_module_docstring(tmp_path: Path) -> None:140 """In a file with only a docstring, the new import must land below the docstring."""141 (tmp_path / "m.py").write_text('"""Module doc."""\n\nx = 1\n')142 r = Repro("b", "t").add_import("m.py", "import os")143 _apply(r, tmp_path)144 assert (tmp_path / "m.py").read_text().startswith('"""Module doc."""')145 146 147def test_add_typechecking_import_matches_qualified_typing_form(tmp_path: Path) -> None:148 """A `if typing.TYPE_CHECKING:` block is recognized and receives the import."""149 (tmp_path / "m.py").write_text(150 "import typing\n"151 "\n"152 "if typing.TYPE_CHECKING:\n"153 " from a import X\n"154 "\n"155 "\n"156 "def f():\n"157 " pass\n"158 )159 r = Repro("b", "t").add_typechecking_import("m.py", "from b import Y")160 _apply(r, tmp_path)161 assert (tmp_path / "m.py").read_text() == (162 "import typing\n"163 "\n"164 "if typing.TYPE_CHECKING:\n"165 " from a import X\n"166 " from b import Y\n"167 "\n"168 "\n"169 "def f():\n"170 " pass\n"171 )172 173 174def test_add_typechecking_import_after_a_multiline_final_import(tmp_path: Path) -> None:175 """The insert lands after the closing paren of a multi-line final guarded import."""176 (tmp_path / "m.py").write_text(177 "from typing import TYPE_CHECKING\n"178 "\n"179 "if TYPE_CHECKING:\n"180 " from a import (\n"181 " X,\n"182 " )\n"183 "\n"184 "x = 1\n"185 )186 r = Repro("b", "t").add_typechecking_import("m.py", "from b import Y")187 _apply(r, tmp_path)188 assert (tmp_path / "m.py").read_text() == (189 "from typing import TYPE_CHECKING\n"190 "\n"191 "if TYPE_CHECKING:\n"192 " from a import (\n"193 " X,\n"194 " )\n"195 " from b import Y\n"196 "\n"197 "x = 1\n"198 )199 200 201def test_add_typechecking_import_raises_without_imports(tmp_path: Path) -> None:202 """A file with no imports cannot anchor a new TYPE_CHECKING block and fails loudly."""203 (tmp_path / "m.py").write_text("x = 1\n")204 r = Repro("b", "t").add_typechecking_import("m.py", "from b import Y")205 with pytest.raises(AssertionError):206 _apply(r, tmp_path)207 208 209def test_add_typechecking_import_drops_a_lone_pass_placeholder(tmp_path: Path) -> None:210 """Populating a `pass`-only TYPE_CHECKING block replaces the placeholder."""211 (tmp_path / "m.py").write_text(212 "from typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n pass\n\nx = 1\n"213 )214 r = Repro("b", "t").add_typechecking_import("m.py", "from b import Y")215 _apply(r, tmp_path)216 assert (tmp_path / "m.py").read_text() == (217 "from typing import TYPE_CHECKING\n"218 "\n"219 "if TYPE_CHECKING:\n"220 " from b import Y\n"221 "\n"222 "x = 1\n"223 )224 225 226def test_add_typechecking_import_keeps_a_pass_that_is_not_alone(tmp_path: Path) -> None:227 """A `pass` beside a real import is left untouched; only the new import is appended."""228 (tmp_path / "m.py").write_text(229 "from typing import TYPE_CHECKING\n"230 "\n"231 "if TYPE_CHECKING:\n"232 " from a import X\n"233 " pass\n"234 "\n"235 "x = 1\n"236 )237 r = Repro("b", "t").add_typechecking_import("m.py", "from b import Y")238 _apply(r, tmp_path)239 assert (tmp_path / "m.py").read_text() == (240 "from typing import TYPE_CHECKING\n"241 "\n"242 "if TYPE_CHECKING:\n"243 " from a import X\n"244 " pass\n"245 " from b import Y\n"246 "\n"247 "x = 1\n"248 )249 250 251# --- add_import(after=...) -----------------------------------------------------252 253 254def test_add_import_after_anchors_into_a_split_import_block(tmp_path: Path) -> None:255 """With `after`, the import lands right after the named import -- needed when a256 statement splits the imports into separate blocks and the default (after the last257 import) would land in the wrong block."""258 (tmp_path / "m.py").write_text(259 "import os\n\n_flag = os.getpid()\n\nfrom pkg import a\n\nx = 1\n"260 )261 r = Repro("b", "t").add_import("m.py", "from new import Thing", after="import os")262 _apply(r, tmp_path)263 assert (tmp_path / "m.py").read_text() == (264 "import os\nfrom new import Thing\n\n_flag = os.getpid()\n\nfrom pkg import a\n\nx = 1\n"265 )266 267 268def test_add_import_after_raises_when_anchor_absent(tmp_path: Path) -> None:269 """An `after` substring that matches no top-level import raises."""270 (tmp_path / "m.py").write_text("import os\n\nx = 1\n")271 r = Repro("b", "t").add_import("m.py", "from new import Thing", after="import nope")272 with pytest.raises(AssertionError):273 _apply(r, tmp_path)274