• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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