Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
update_lib __init__ handling
  • Loading branch information
youknowone committed Jan 20, 2026
commit 7bd50b2cc564c43649c920eed9ad6689c54651e5
4 changes: 2 additions & 2 deletions .claude/commands/upgrade-pylib.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ If during the upgrade process you encounter any of the following issues with `sc

**STOP the upgrade and report the issue first.** Describe:
1. What you were trying to do
- Library name
- The full command executed (e.g. python scripts/update_lib quick cpython/Lib/$ARGUMENTS.py)
- Library name
- The full command executed (e.g. python scripts/update_lib quick cpython/Lib/$ARGUMENTS.py)
2. What went wrong or what's missing
3. Expected vs actual behavior

Expand Down
3 changes: 3 additions & 0 deletions scripts/update_lib/auto_mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,9 @@ def _is_super_call_only(func_node: ast.FunctionDef | ast.AsyncFunctionDef) -> bo
return False
if not isinstance(call.func, ast.Attribute):
return False
# Verify the method name matches
if call.func.attr != func_node.name:
return False
super_call = call.func.value
if not isinstance(super_call, ast.Call):
return False
Expand Down
1 change: 1 addition & 0 deletions scripts/update_lib/copy_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def copy_lib(
if src_path.is_dir():
if verbose:
print(f"Copying directory: {src_path} -> {lib_path}")
lib_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copytree(src_path, lib_path)
else:
if verbose:
Expand Down
13 changes: 11 additions & 2 deletions scripts/update_lib/patch_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,17 @@ def _find_import_insert_line(tree: ast.Module) -> int:
for node in tree.body:
if isinstance(node, (ast.Import, ast.ImportFrom)):
last_import_line = node.end_lineno or node.lineno
assert last_import_line is not None
return last_import_line
if last_import_line is not None:
return last_import_line
# No imports found - insert after module docstring if present, else at top
if (
tree.body
and isinstance(tree.body[0], ast.Expr)
and isinstance(tree.body[0].value, ast.Constant)
and isinstance(tree.body[0].value.value, str)
):
return tree.body[0].end_lineno or tree.body[0].lineno
return 0


def apply_patches(contents: str, patches: Patches) -> str:
Expand Down
7 changes: 7 additions & 0 deletions scripts/update_lib/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def lib_to_test_path(src_path: pathlib.Path) -> pathlib.Path:
cpython/Lib/dataclasses.py -> cpython/Lib/test/test_dataclasses/ (if dir exists)
cpython/Lib/typing.py -> cpython/Lib/test/test_typing.py (if file exists)
cpython/Lib/json/ -> cpython/Lib/test/test_json/
cpython/Lib/json/__init__.py -> cpython/Lib/test/test_json/
Lib/dataclasses.py -> Lib/test/test_dataclasses/
"""
path_str = str(src_path).replace("\\", "/")
Expand All @@ -55,6 +56,9 @@ def lib_to_test_path(src_path: pathlib.Path) -> pathlib.Path:
if lib_marker in path_str:
lib_path = parse_lib_path(src_path)
lib_name = lib_path.stem if lib_path.suffix == ".py" else lib_path.name
# Handle __init__.py: use parent directory name
if lib_name == "__init__":
lib_name = lib_path.parent.name
prefix = path_str[: path_str.index(lib_marker)]
# Try directory first, then file
dir_path = pathlib.Path(f"{prefix}/Lib/test/test_{lib_name}/")
Expand All @@ -68,6 +72,9 @@ def lib_to_test_path(src_path: pathlib.Path) -> pathlib.Path:
else:
# Path starts with Lib/ - extract name directly
lib_name = src_path.stem if src_path.suffix == ".py" else src_path.name
# Handle __init__.py: use parent directory name
if lib_name == "__init__":
lib_name = src_path.parent.name
# Try directory first, then file
dir_path = pathlib.Path(f"Lib/test/test_{lib_name}/")
if dir_path.exists():
Expand Down
56 changes: 56 additions & 0 deletions scripts/update_lib/tests/test_auto_mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from update_lib.auto_mark import (
Test,
TestResult,
_is_super_call_only,
apply_test_changes,
collect_test_changes,
extract_test_methods,
Expand Down Expand Up @@ -589,5 +590,60 @@ def test_all_regressions(self):
self.assertEqual(regressions, {("TestFoo", "test_one")})


class TestIsSuperCallOnly(unittest.TestCase):
"""Tests for _is_super_call_only function."""

def _parse_method(self, code: str):
"""Parse code and return the first method."""
import ast

tree = ast.parse(code)
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
return node
return None

def test_matching_super_call(self):
"""Test method that calls super().same_name()."""
code = """
class Foo:
def test_one(self):
return super().test_one()
"""
method = self._parse_method(code)
self.assertTrue(_is_super_call_only(method))

def test_mismatched_super_call(self):
"""Test method that calls super().different_name()."""
code = """
class Foo:
def test_one(self):
return super().test_two()
"""
method = self._parse_method(code)
self.assertFalse(_is_super_call_only(method))

def test_not_super_call(self):
"""Test method with regular body."""
code = """
class Foo:
def test_one(self):
pass
"""
method = self._parse_method(code)
self.assertFalse(_is_super_call_only(method))

def test_multiple_statements(self):
"""Test method with multiple statements."""
code = """
class Foo:
def test_one(self):
x = 1
return super().test_one()
"""
method = self._parse_method(code)
self.assertFalse(_is_super_call_only(method))


if __name__ == "__main__":
unittest.main()
37 changes: 37 additions & 0 deletions scripts/update_lib/tests/test_patch_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
COMMENT,
PatchSpec,
UtMethod,
_find_import_insert_line,
apply_patches,
extract_patches,
iter_tests,
Expand Down Expand Up @@ -321,5 +322,41 @@ def test_one(self):
self.assertIn(COMMENT, result)


class TestFindImportInsertLine(unittest.TestCase):
"""Tests for _find_import_insert_line function."""

def test_with_imports(self):
"""Test finding line after imports."""
code = """import os
import sys

class Foo:
pass
"""
tree = ast.parse(code)
line = _find_import_insert_line(tree)
self.assertEqual(line, 2)

def test_no_imports_with_docstring(self):
"""Test fallback to after docstring when no imports."""
code = '''"""Module docstring."""

class Foo:
pass
'''
tree = ast.parse(code)
line = _find_import_insert_line(tree)
self.assertEqual(line, 1)

def test_no_imports_no_docstring(self):
"""Test fallback to line 0 when no imports and no docstring."""
code = """class Foo:
pass
"""
tree = ast.parse(code)
line = _find_import_insert_line(tree)
self.assertEqual(line, 0)


if __name__ == "__main__":
unittest.main()
23 changes: 23 additions & 0 deletions scripts/update_lib/tests/test_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,29 @@ def test_lib_path_prefers_directory(self):
result = lib_to_test_path(pathlib.Path("Lib/nonexistent_module.py"))
self.assertEqual(result, pathlib.Path("Lib/test/test_nonexistent_module/"))

def test_init_py_uses_parent_name(self):
"""Test __init__.py uses parent directory name."""
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir = pathlib.Path(tmpdir)
# Create structure: tmpdir/Lib/json/__init__.py
lib_dir = tmpdir / "Lib"
lib_dir.mkdir()
json_dir = lib_dir / "json"
json_dir.mkdir()
(json_dir / "__init__.py").write_text("# json init")
test_dir = lib_dir / "test"
test_dir.mkdir()

result = lib_to_test_path(tmpdir / "Lib" / "json" / "__init__.py")
# Should use "json" not "__init__"
self.assertEqual(result, tmpdir / "Lib" / "test" / "test_json/")

def test_init_py_lib_path_uses_parent_name(self):
"""Test __init__.py with Lib/ path uses parent directory name."""
result = lib_to_test_path(pathlib.Path("Lib/json/__init__.py"))
# Should use "json" not "__init__"
self.assertEqual(result, pathlib.Path("Lib/test/test_json/"))


class TestGetTestFiles(unittest.TestCase):
"""Tests for get_test_files function."""
Expand Down