"""Tests for deps.py - dependency resolution.""" import pathlib import tempfile import unittest from update_lib.deps import ( get_lib_paths, get_soft_deps, get_test_dependencies, get_test_paths, parse_lib_imports, parse_test_imports, ) class TestParseTestImports(unittest.TestCase): """Tests for parse_test_imports function.""" def test_from_test_import(self): """Test parsing 'from test import foo'.""" code = """ from test import string_tests from test import lock_tests, other_tests """ imports = parse_test_imports(code) self.assertEqual(imports, {"string_tests", "lock_tests", "other_tests"}) def test_from_test_dot_module(self): """Test parsing 'from test.foo import bar'.""" code = """ from test.string_tests import CommonTest from test.support import verbose """ imports = parse_test_imports(code) self.assertEqual(imports, {"string_tests"}) # support is excluded def test_excludes_support(self): """Test that 'support' is excluded.""" code = """ from test import support from test.support import verbose """ imports = parse_test_imports(code) self.assertEqual(imports, set()) def test_regular_imports_ignored(self): """Test that regular imports are ignored.""" code = """ import os from collections import defaultdict from . import helper """ imports = parse_test_imports(code) self.assertEqual(imports, set()) def test_syntax_error_returns_empty(self): """Test that syntax errors return empty set.""" code = "this is not valid python {" imports = parse_test_imports(code) self.assertEqual(imports, set()) class TestGetLibPaths(unittest.TestCase): """Tests for get_lib_paths function.""" def test_auto_detect_py_module(self): """Test auto-detection of _py{module}.py pattern.""" with tempfile.TemporaryDirectory() as tmpdir: tmpdir = pathlib.Path(tmpdir) lib_dir = tmpdir / "Lib" lib_dir.mkdir() (lib_dir / "mymodule.py").write_text("# mymodule") (lib_dir / "_pymymodule.py").write_text("# _pymymodule") paths = get_lib_paths("mymodule", str(tmpdir)) self.assertEqual(len(paths), 2) self.assertIn(tmpdir / "Lib" / "mymodule.py", paths) self.assertIn(tmpdir / "Lib" / "_pymymodule.py", paths) def test_default_file(self): """Test default to .py file.""" with tempfile.TemporaryDirectory() as tmpdir: tmpdir = pathlib.Path(tmpdir) lib_dir = tmpdir / "Lib" lib_dir.mkdir() (lib_dir / "foo.py").write_text("# foo") paths = get_lib_paths("foo", str(tmpdir)) self.assertEqual(paths, (tmpdir / "Lib" / "foo.py",)) def test_default_directory(self): """Test default to directory when file doesn't exist.""" with tempfile.TemporaryDirectory() as tmpdir: tmpdir = pathlib.Path(tmpdir) lib_dir = tmpdir / "Lib" lib_dir.mkdir() (lib_dir / "foo").mkdir() paths = get_lib_paths("foo", str(tmpdir)) self.assertEqual(paths, (tmpdir / "Lib" / "foo",)) class TestGetTestPaths(unittest.TestCase): """Tests for get_test_paths function.""" def test_known_dependency(self): """Test test with known path override.""" paths = get_test_paths("regrtest", "cpython") self.assertEqual(len(paths), 1) self.assertEqual(paths[0], pathlib.Path("cpython/Lib/test/test_regrtest")) def test_default_directory(self): """Test default to test_name/ directory.""" with tempfile.TemporaryDirectory() as tmpdir: tmpdir = pathlib.Path(tmpdir) test_dir = tmpdir / "Lib" / "test" test_dir.mkdir(parents=True) (test_dir / "test_foo").mkdir() paths = get_test_paths("foo", str(tmpdir)) self.assertEqual(paths, (tmpdir / "Lib" / "test" / "test_foo",)) def test_default_file(self): """Test fallback to test_name.py file.""" with tempfile.TemporaryDirectory() as tmpdir: tmpdir = pathlib.Path(tmpdir) test_dir = tmpdir / "Lib" / "test" test_dir.mkdir(parents=True) (test_dir / "test_foo.py").write_text("# test") paths = get_test_paths("foo", str(tmpdir)) self.assertEqual(paths, (tmpdir / "Lib" / "test" / "test_foo.py",)) class TestGetTestDependencies(unittest.TestCase): """Tests for get_test_dependencies function.""" def test_parse_file_imports(self): """Test parsing imports from test file.""" with tempfile.TemporaryDirectory() as tmpdir: tmpdir = pathlib.Path(tmpdir) test_dir = tmpdir / "test" test_dir.mkdir() # Create test file with import test_file = test_dir / "test_foo.py" test_file.write_text(""" from test import string_tests class TestFoo: pass """) # Create the dependency file (test_dir / "string_tests.py").write_text("# string tests") result = get_test_dependencies(test_file) self.assertEqual(len(result["hard_deps"]), 1) self.assertEqual(result["hard_deps"][0], test_dir / "string_tests.py") self.assertEqual(result["data"], []) def test_nonexistent_path(self): """Test nonexistent path returns empty.""" result = get_test_dependencies(pathlib.Path("/nonexistent/path")) self.assertEqual(result, {"hard_deps": [], "data": []}) def test_transitive_data_dependency(self): """Test that data deps are resolved transitively. Chain: test_codecencodings_kr -> multibytecodec_support -> cjkencodings """ with tempfile.TemporaryDirectory() as tmpdir: tmpdir = pathlib.Path(tmpdir) test_dir = tmpdir / "test" test_dir.mkdir() # Create test_codecencodings_kr.py that imports multibytecodec_support test_file = test_dir / "test_codecencodings_kr.py" test_file.write_text(""" from test import multibytecodec_support class TestKR: pass """) # Create multibytecodec_support.py (the intermediate dependency) (test_dir / "multibytecodec_support.py").write_text("# support module") # Create cjkencodings directory (the data dependency of multibytecodec_support) (test_dir / "cjkencodings").mkdir() result = get_test_dependencies(test_file) # Should find multibytecodec_support.py as a hard_dep self.assertEqual(len(result["hard_deps"]), 1) self.assertEqual( result["hard_deps"][0], test_dir / "multibytecodec_support.py" ) # Should find cjkencodings as data (from multibytecodec_support's TEST_DEPENDENCIES) self.assertEqual(len(result["data"]), 1) self.assertEqual(result["data"][0], test_dir / "cjkencodings") class TestParseLibImports(unittest.TestCase): """Tests for parse_lib_imports function.""" def test_import_statement(self): """Test parsing 'import foo'.""" code = """ import os import sys import collections.abc """ imports = parse_lib_imports(code) self.assertEqual(imports, {"os", "sys", "collections.abc"}) def test_from_import(self): """Test parsing 'from foo import bar'.""" code = """ from os import path from collections.abc import Mapping from typing import Optional """ imports = parse_lib_imports(code) self.assertEqual(imports, {"os", "collections.abc", "typing"}) def test_mixed_imports(self): """Test mixed import styles.""" code = """ import sys from os import path from collections import defaultdict import functools """ imports = parse_lib_imports(code) self.assertEqual(imports, {"sys", "os", "collections", "functools"}) def test_syntax_error_returns_empty(self): """Test that syntax errors return empty set.""" code = "this is not valid python {" imports = parse_lib_imports(code) self.assertEqual(imports, set()) def test_relative_import_skipped(self): """Test that relative imports (no module) are skipped.""" code = """ from . import foo from .. import bar """ imports = parse_lib_imports(code) self.assertEqual(imports, set()) class TestGetSoftDeps(unittest.TestCase): """Tests for get_soft_deps function.""" def test_with_temp_files(self): """Test soft deps detection with temp files.""" with tempfile.TemporaryDirectory() as tmpdir: tmpdir = pathlib.Path(tmpdir) lib_dir = tmpdir / "Lib" lib_dir.mkdir() # Create a module that imports another module (lib_dir / "foo.py").write_text(""" import bar from baz import something """) # Create the imported modules (lib_dir / "bar.py").write_text("# bar module") (lib_dir / "baz.py").write_text("# baz module") soft_deps = get_soft_deps("foo", str(tmpdir)) self.assertEqual(soft_deps, {"bar", "baz"}) def test_skips_self(self): """Test that module doesn't include itself in soft_deps.""" with tempfile.TemporaryDirectory() as tmpdir: tmpdir = pathlib.Path(tmpdir) lib_dir = tmpdir / "Lib" lib_dir.mkdir() # Create a module that imports itself (circular) (lib_dir / "foo.py").write_text(""" import foo import bar """) (lib_dir / "bar.py").write_text("# bar module") soft_deps = get_soft_deps("foo", str(tmpdir)) self.assertNotIn("foo", soft_deps) self.assertIn("bar", soft_deps) def test_filters_nonexistent(self): """Test that nonexistent modules are filtered out.""" with tempfile.TemporaryDirectory() as tmpdir: tmpdir = pathlib.Path(tmpdir) lib_dir = tmpdir / "Lib" lib_dir.mkdir() # Create a module that imports nonexistent module (lib_dir / "foo.py").write_text(""" import bar import nonexistent """) (lib_dir / "bar.py").write_text("# bar module") # nonexistent.py is NOT created soft_deps = get_soft_deps("foo", str(tmpdir)) self.assertEqual(soft_deps, {"bar"}) class TestDircmpIsSame(unittest.TestCase): """Tests for _dircmp_is_same function.""" def test_identical_directories(self): """Test that identical directories return True.""" import filecmp from update_lib.deps import _dircmp_is_same with tempfile.TemporaryDirectory() as tmpdir: tmpdir = pathlib.Path(tmpdir) dir1 = tmpdir / "dir1" dir2 = tmpdir / "dir2" dir1.mkdir() dir2.mkdir() (dir1 / "file.py").write_text("content") (dir2 / "file.py").write_text("content") dcmp = filecmp.dircmp(dir1, dir2) self.assertTrue(_dircmp_is_same(dcmp)) def test_different_files(self): """Test that directories with different files return False.""" import filecmp from update_lib.deps import _dircmp_is_same with tempfile.TemporaryDirectory() as tmpdir: tmpdir = pathlib.Path(tmpdir) dir1 = tmpdir / "dir1" dir2 = tmpdir / "dir2" dir1.mkdir() dir2.mkdir() (dir1 / "file.py").write_text("content1") (dir2 / "file.py").write_text("content2") dcmp = filecmp.dircmp(dir1, dir2) self.assertFalse(_dircmp_is_same(dcmp)) def test_nested_identical(self): """Test that nested identical directories return True.""" import filecmp from update_lib.deps import _dircmp_is_same with tempfile.TemporaryDirectory() as tmpdir: tmpdir = pathlib.Path(tmpdir) dir1 = tmpdir / "dir1" dir2 = tmpdir / "dir2" (dir1 / "sub").mkdir(parents=True) (dir2 / "sub").mkdir(parents=True) (dir1 / "sub" / "file.py").write_text("content") (dir2 / "sub" / "file.py").write_text("content") dcmp = filecmp.dircmp(dir1, dir2) self.assertTrue(_dircmp_is_same(dcmp)) def test_nested_different(self): """Test that nested directories with differences return False.""" import filecmp from update_lib.deps import _dircmp_is_same with tempfile.TemporaryDirectory() as tmpdir: tmpdir = pathlib.Path(tmpdir) dir1 = tmpdir / "dir1" dir2 = tmpdir / "dir2" (dir1 / "sub").mkdir(parents=True) (dir2 / "sub").mkdir(parents=True) (dir1 / "sub" / "file.py").write_text("content1") (dir2 / "sub" / "file.py").write_text("content2") dcmp = filecmp.dircmp(dir1, dir2) self.assertFalse(_dircmp_is_same(dcmp)) if __name__ == "__main__": unittest.main()