1# Copyright 2023 The Bazel Authors. All rights reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import os 16import pathlib 17import shutil 18import tempfile 19import unittest 20from typing import Optional, Set 21 22from python.private.pypi.whl_installer import namespace_pkgs 23 24 25class TempDir: 26 def __init__(self) -> None: 27 self.dir = tempfile.mkdtemp() 28 29 def root(self) -> str: 30 return self.dir 31 32 def add_dir(self, rel_path: str) -> None: 33 d = pathlib.Path(self.dir, rel_path) 34 d.mkdir(parents=True) 35 36 def add_file(self, rel_path: str, contents: Optional[str] = None) -> None: 37 f = pathlib.Path(self.dir, rel_path) 38 f.parent.mkdir(parents=True, exist_ok=True) 39 if contents: 40 with open(str(f), "w") as writeable_f: 41 writeable_f.write(contents) 42 else: 43 f.touch() 44 45 def remove(self) -> None: 46 shutil.rmtree(self.dir) 47 48 49class TestImplicitNamespacePackages(unittest.TestCase): 50 def assertPathsEqual(self, actual: Set[pathlib.Path], expected: Set[str]) -> None: 51 self.assertEqual(actual, {pathlib.Path(p) for p in expected}) 52 53 def test_in_current_directory(self) -> None: 54 directory = TempDir() 55 directory.add_file("foo/bar/biz.py") 56 directory.add_file("foo/bee/boo.py") 57 directory.add_file("foo/buu/__init__.py") 58 directory.add_file("foo/buu/bii.py") 59 cwd = os.getcwd() 60 os.chdir(directory.root()) 61 expected = { 62 "foo", 63 "foo/bar", 64 "foo/bee", 65 } 66 try: 67 actual = namespace_pkgs.implicit_namespace_packages(".") 68 self.assertPathsEqual(actual, expected) 69 finally: 70 os.chdir(cwd) 71 directory.remove() 72 73 def test_finds_correct_namespace_packages(self) -> None: 74 directory = TempDir() 75 directory.add_file("foo/bar/biz.py") 76 directory.add_file("foo/bee/boo.py") 77 directory.add_file("foo/buu/__init__.py") 78 directory.add_file("foo/buu/bii.py") 79 80 expected = { 81 directory.root() + "/foo", 82 directory.root() + "/foo/bar", 83 directory.root() + "/foo/bee", 84 } 85 actual = namespace_pkgs.implicit_namespace_packages(directory.root()) 86 self.assertPathsEqual(actual, expected) 87 88 def test_ignores_empty_directories(self) -> None: 89 directory = TempDir() 90 directory.add_file("foo/bar/biz.py") 91 directory.add_dir("foo/cat") 92 93 expected = { 94 directory.root() + "/foo", 95 directory.root() + "/foo/bar", 96 } 97 actual = namespace_pkgs.implicit_namespace_packages(directory.root()) 98 self.assertPathsEqual(actual, expected) 99 100 def test_empty_case(self) -> None: 101 directory = TempDir() 102 directory.add_file("foo/__init__.py") 103 directory.add_file("foo/bar/__init__.py") 104 directory.add_file("foo/bar/biz.py") 105 106 actual = namespace_pkgs.implicit_namespace_packages(directory.root()) 107 self.assertEqual(actual, set()) 108 109 def test_ignores_non_module_files_in_directories(self) -> None: 110 directory = TempDir() 111 directory.add_file("foo/__init__.pyi") 112 directory.add_file("foo/py.typed") 113 114 actual = namespace_pkgs.implicit_namespace_packages(directory.root()) 115 self.assertEqual(actual, set()) 116 117 def test_parent_child_relationship_of_namespace_pkgs(self): 118 directory = TempDir() 119 directory.add_file("foo/bar/biff/my_module.py") 120 directory.add_file("foo/bar/biff/another_module.py") 121 122 expected = { 123 directory.root() + "/foo", 124 directory.root() + "/foo/bar", 125 directory.root() + "/foo/bar/biff", 126 } 127 actual = namespace_pkgs.implicit_namespace_packages(directory.root()) 128 self.assertPathsEqual(actual, expected) 129 130 def test_parent_child_relationship_of_namespace_and_standard_pkgs(self): 131 directory = TempDir() 132 directory.add_file("foo/bar/biff/__init__.py") 133 directory.add_file("foo/bar/biff/another_module.py") 134 135 expected = { 136 directory.root() + "/foo", 137 directory.root() + "/foo/bar", 138 } 139 actual = namespace_pkgs.implicit_namespace_packages(directory.root()) 140 self.assertPathsEqual(actual, expected) 141 142 def test_parent_child_relationship_of_namespace_and_nested_standard_pkgs(self): 143 directory = TempDir() 144 directory.add_file("foo/bar/__init__.py") 145 directory.add_file("foo/bar/biff/another_module.py") 146 directory.add_file("foo/bar/biff/__init__.py") 147 directory.add_file("foo/bar/boof/big_module.py") 148 directory.add_file("foo/bar/boof/__init__.py") 149 directory.add_file("fim/in_a_ns_pkg.py") 150 151 expected = { 152 directory.root() + "/foo", 153 directory.root() + "/fim", 154 } 155 actual = namespace_pkgs.implicit_namespace_packages(directory.root()) 156 self.assertPathsEqual(actual, expected) 157 158 def test_recognized_all_nonstandard_module_types(self): 159 directory = TempDir() 160 directory.add_file("ayy/my_module.pyc") 161 directory.add_file("bee/ccc/dee/eee.so") 162 directory.add_file("eff/jee/aych.pyd") 163 164 expected = { 165 directory.root() + "/ayy", 166 directory.root() + "/bee", 167 directory.root() + "/bee/ccc", 168 directory.root() + "/bee/ccc/dee", 169 directory.root() + "/eff", 170 directory.root() + "/eff/jee", 171 } 172 actual = namespace_pkgs.implicit_namespace_packages(directory.root()) 173 self.assertPathsEqual(actual, expected) 174 175 def test_skips_ignored_directories(self): 176 directory = TempDir() 177 directory.add_file("foo/boo/my_module.py") 178 directory.add_file("foo/bar/another_module.py") 179 180 expected = { 181 directory.root() + "/foo", 182 directory.root() + "/foo/bar", 183 } 184 actual = namespace_pkgs.implicit_namespace_packages( 185 directory.root(), 186 ignored_dirnames=[directory.root() + "/foo/boo"], 187 ) 188 self.assertPathsEqual(actual, expected) 189 190 191if __name__ == "__main__": 192 unittest.main() 193