python (3.12.0)
1 import contextlib
2 import importlib
3 import importlib.abc
4 import importlib.machinery
5 import os
6 import sys
7 import tempfile
8 import unittest
9 import warnings
10
11 from test.test_importlib import util
12
13 # needed tests:
14 #
15 # need to test when nested, so that the top-level path isn't sys.path
16 # need to test dynamic path detection, both at top-level and nested
17 # with dynamic path, check when a loader is returned on path reload (that is,
18 # trying to switch from a namespace package to a regular package)
19
20
21 @contextlib.contextmanager
22 def sys_modules_context():
23 """
24 Make sure sys.modules is the same object and has the same content
25 when exiting the context as when entering.
26
27 Similar to importlib.test.util.uncache, but doesn't require explicit
28 names.
29 """
30 sys_modules_saved = sys.modules
31 sys_modules_copy = sys.modules.copy()
32 try:
33 yield
34 finally:
35 sys.modules = sys_modules_saved
36 sys.modules.clear()
37 sys.modules.update(sys_modules_copy)
38
39
40 @contextlib.contextmanager
41 def namespace_tree_context(**kwargs):
42 """
43 Save import state and sys.modules cache and restore it on exit.
44 Typical usage:
45
46 >>> with namespace_tree_context(path=['/tmp/xxyy/portion1',
47 ... '/tmp/xxyy/portion2']):
48 ... pass
49 """
50 # use default meta_path and path_hooks unless specified otherwise
51 kwargs.setdefault('meta_path', sys.meta_path)
52 kwargs.setdefault('path_hooks', sys.path_hooks)
53 import_context = util.import_state(**kwargs)
54 with import_context, sys_modules_context():
55 yield
56
57 class ESC[4;38;5;81mNamespacePackageTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
58 """
59 Subclasses should define self.root and self.paths (under that root)
60 to be added to sys.path.
61 """
62 root = os.path.join(os.path.dirname(__file__), 'namespace_pkgs')
63
64 def setUp(self):
65 self.resolved_paths = [
66 os.path.join(self.root, path) for path in self.paths
67 ]
68 self.enterContext(namespace_tree_context(path=self.resolved_paths))
69
70
71 class ESC[4;38;5;81mSingleNamespacePackage(ESC[4;38;5;149mNamespacePackageTest):
72 paths = ['portion1']
73
74 def test_simple_package(self):
75 import foo.one
76 self.assertEqual(foo.one.attr, 'portion1 foo one')
77
78 def test_cant_import_other(self):
79 with self.assertRaises(ImportError):
80 import foo.two
81
82 def test_simple_repr(self):
83 import foo.one
84 assert repr(foo).startswith("<module 'foo' (namespace) from [")
85
86
87 class ESC[4;38;5;81mDynamicPathNamespacePackage(ESC[4;38;5;149mNamespacePackageTest):
88 paths = ['portion1']
89
90 def test_dynamic_path(self):
91 # Make sure only 'foo.one' can be imported
92 import foo.one
93 self.assertEqual(foo.one.attr, 'portion1 foo one')
94
95 with self.assertRaises(ImportError):
96 import foo.two
97
98 # Now modify sys.path
99 sys.path.append(os.path.join(self.root, 'portion2'))
100
101 # And make sure foo.two is now importable
102 import foo.two
103 self.assertEqual(foo.two.attr, 'portion2 foo two')
104
105
106 class ESC[4;38;5;81mCombinedNamespacePackages(ESC[4;38;5;149mNamespacePackageTest):
107 paths = ['both_portions']
108
109 def test_imports(self):
110 import foo.one
111 import foo.two
112 self.assertEqual(foo.one.attr, 'both_portions foo one')
113 self.assertEqual(foo.two.attr, 'both_portions foo two')
114
115
116 class ESC[4;38;5;81mSeparatedNamespacePackages(ESC[4;38;5;149mNamespacePackageTest):
117 paths = ['portion1', 'portion2']
118
119 def test_imports(self):
120 import foo.one
121 import foo.two
122 self.assertEqual(foo.one.attr, 'portion1 foo one')
123 self.assertEqual(foo.two.attr, 'portion2 foo two')
124
125
126 class ESC[4;38;5;81mSeparatedNamespacePackagesCreatedWhileRunning(ESC[4;38;5;149mNamespacePackageTest):
127 paths = ['portion1']
128
129 def test_invalidate_caches(self):
130 with tempfile.TemporaryDirectory() as temp_dir:
131 # we manipulate sys.path before anything is imported to avoid
132 # accidental cache invalidation when changing it
133 sys.path.append(temp_dir)
134
135 import foo.one
136 self.assertEqual(foo.one.attr, 'portion1 foo one')
137
138 # the module does not exist, so it cannot be imported
139 with self.assertRaises(ImportError):
140 import foo.just_created
141
142 # util.create_modules() manipulates sys.path
143 # so we must create the modules manually instead
144 namespace_path = os.path.join(temp_dir, 'foo')
145 os.mkdir(namespace_path)
146 module_path = os.path.join(namespace_path, 'just_created.py')
147 with open(module_path, 'w', encoding='utf-8') as file:
148 file.write('attr = "just_created foo"')
149
150 # the module is not known, so it cannot be imported yet
151 with self.assertRaises(ImportError):
152 import foo.just_created
153
154 # but after explicit cache invalidation, it is importable
155 importlib.invalidate_caches()
156 import foo.just_created
157 self.assertEqual(foo.just_created.attr, 'just_created foo')
158
159
160 class ESC[4;38;5;81mSeparatedOverlappingNamespacePackages(ESC[4;38;5;149mNamespacePackageTest):
161 paths = ['portion1', 'both_portions']
162
163 def test_first_path_wins(self):
164 import foo.one
165 import foo.two
166 self.assertEqual(foo.one.attr, 'portion1 foo one')
167 self.assertEqual(foo.two.attr, 'both_portions foo two')
168
169 def test_first_path_wins_again(self):
170 sys.path.reverse()
171 import foo.one
172 import foo.two
173 self.assertEqual(foo.one.attr, 'both_portions foo one')
174 self.assertEqual(foo.two.attr, 'both_portions foo two')
175
176 def test_first_path_wins_importing_second_first(self):
177 import foo.two
178 import foo.one
179 self.assertEqual(foo.one.attr, 'portion1 foo one')
180 self.assertEqual(foo.two.attr, 'both_portions foo two')
181
182
183 class ESC[4;38;5;81mSingleZipNamespacePackage(ESC[4;38;5;149mNamespacePackageTest):
184 paths = ['top_level_portion1.zip']
185
186 def test_simple_package(self):
187 import foo.one
188 self.assertEqual(foo.one.attr, 'portion1 foo one')
189
190 def test_cant_import_other(self):
191 with self.assertRaises(ImportError):
192 import foo.two
193
194
195 class ESC[4;38;5;81mSeparatedZipNamespacePackages(ESC[4;38;5;149mNamespacePackageTest):
196 paths = ['top_level_portion1.zip', 'portion2']
197
198 def test_imports(self):
199 import foo.one
200 import foo.two
201 self.assertEqual(foo.one.attr, 'portion1 foo one')
202 self.assertEqual(foo.two.attr, 'portion2 foo two')
203 self.assertIn('top_level_portion1.zip', foo.one.__file__)
204 self.assertNotIn('.zip', foo.two.__file__)
205
206
207 class ESC[4;38;5;81mSingleNestedZipNamespacePackage(ESC[4;38;5;149mNamespacePackageTest):
208 paths = ['nested_portion1.zip/nested_portion1']
209
210 def test_simple_package(self):
211 import foo.one
212 self.assertEqual(foo.one.attr, 'portion1 foo one')
213
214 def test_cant_import_other(self):
215 with self.assertRaises(ImportError):
216 import foo.two
217
218
219 class ESC[4;38;5;81mSeparatedNestedZipNamespacePackages(ESC[4;38;5;149mNamespacePackageTest):
220 paths = ['nested_portion1.zip/nested_portion1', 'portion2']
221
222 def test_imports(self):
223 import foo.one
224 import foo.two
225 self.assertEqual(foo.one.attr, 'portion1 foo one')
226 self.assertEqual(foo.two.attr, 'portion2 foo two')
227 fn = os.path.join('nested_portion1.zip', 'nested_portion1')
228 self.assertIn(fn, foo.one.__file__)
229 self.assertNotIn('.zip', foo.two.__file__)
230
231
232 class ESC[4;38;5;81mLegacySupport(ESC[4;38;5;149mNamespacePackageTest):
233 paths = ['not_a_namespace_pkg', 'portion1', 'portion2', 'both_portions']
234
235 def test_non_namespace_package_takes_precedence(self):
236 import foo.one
237 with self.assertRaises(ImportError):
238 import foo.two
239 self.assertIn('__init__', foo.__file__)
240 self.assertNotIn('namespace', str(foo.__loader__).lower())
241
242
243 class ESC[4;38;5;81mDynamicPathCalculation(ESC[4;38;5;149mNamespacePackageTest):
244 paths = ['project1', 'project2']
245
246 def test_project3_fails(self):
247 import parent.child.one
248 self.assertEqual(len(parent.__path__), 2)
249 self.assertEqual(len(parent.child.__path__), 2)
250 import parent.child.two
251 self.assertEqual(len(parent.__path__), 2)
252 self.assertEqual(len(parent.child.__path__), 2)
253
254 self.assertEqual(parent.child.one.attr, 'parent child one')
255 self.assertEqual(parent.child.two.attr, 'parent child two')
256
257 with self.assertRaises(ImportError):
258 import parent.child.three
259
260 self.assertEqual(len(parent.__path__), 2)
261 self.assertEqual(len(parent.child.__path__), 2)
262
263 def test_project3_succeeds(self):
264 import parent.child.one
265 self.assertEqual(len(parent.__path__), 2)
266 self.assertEqual(len(parent.child.__path__), 2)
267 import parent.child.two
268 self.assertEqual(len(parent.__path__), 2)
269 self.assertEqual(len(parent.child.__path__), 2)
270
271 self.assertEqual(parent.child.one.attr, 'parent child one')
272 self.assertEqual(parent.child.two.attr, 'parent child two')
273
274 with self.assertRaises(ImportError):
275 import parent.child.three
276
277 # now add project3
278 sys.path.append(os.path.join(self.root, 'project3'))
279 import parent.child.three
280
281 # the paths dynamically get longer, to include the new directories
282 self.assertEqual(len(parent.__path__), 3)
283 self.assertEqual(len(parent.child.__path__), 3)
284
285 self.assertEqual(parent.child.three.attr, 'parent child three')
286
287
288 class ESC[4;38;5;81mZipWithMissingDirectory(ESC[4;38;5;149mNamespacePackageTest):
289 paths = ['missing_directory.zip']
290
291 @unittest.expectedFailure
292 def test_missing_directory(self):
293 # This will fail because missing_directory.zip contains:
294 # Length Date Time Name
295 # --------- ---------- ----- ----
296 # 29 2012-05-03 18:13 foo/one.py
297 # 0 2012-05-03 20:57 bar/
298 # 38 2012-05-03 20:57 bar/two.py
299 # --------- -------
300 # 67 3 files
301
302 # Because there is no 'foo/', the zipimporter currently doesn't
303 # know that foo is a namespace package
304
305 import foo.one
306
307 def test_present_directory(self):
308 # This succeeds because there is a "bar/" in the zip file
309 import bar.two
310 self.assertEqual(bar.two.attr, 'missing_directory foo two')
311
312
313 class ESC[4;38;5;81mModuleAndNamespacePackageInSameDir(ESC[4;38;5;149mNamespacePackageTest):
314 paths = ['module_and_namespace_package']
315
316 def test_module_before_namespace_package(self):
317 # Make sure we find the module in preference to the
318 # namespace package.
319 import a_test
320 self.assertEqual(a_test.attr, 'in module')
321
322
323 class ESC[4;38;5;81mReloadTests(ESC[4;38;5;149mNamespacePackageTest):
324 paths = ['portion1']
325
326 def test_simple_package(self):
327 import foo.one
328 foo = importlib.reload(foo)
329 self.assertEqual(foo.one.attr, 'portion1 foo one')
330
331 def test_cant_import_other(self):
332 import foo
333 with self.assertRaises(ImportError):
334 import foo.two
335 foo = importlib.reload(foo)
336 with self.assertRaises(ImportError):
337 import foo.two
338
339 def test_dynamic_path(self):
340 import foo.one
341 with self.assertRaises(ImportError):
342 import foo.two
343
344 # Now modify sys.path and reload.
345 sys.path.append(os.path.join(self.root, 'portion2'))
346 foo = importlib.reload(foo)
347
348 # And make sure foo.two is now importable
349 import foo.two
350 self.assertEqual(foo.two.attr, 'portion2 foo two')
351
352
353 class ESC[4;38;5;81mLoaderTests(ESC[4;38;5;149mNamespacePackageTest):
354 paths = ['portion1']
355
356 def test_namespace_loader_consistency(self):
357 # bpo-32303
358 import foo
359 self.assertEqual(foo.__loader__, foo.__spec__.loader)
360 self.assertIsNotNone(foo.__loader__)
361
362 def test_namespace_origin_consistency(self):
363 # bpo-32305
364 import foo
365 self.assertIsNone(foo.__spec__.origin)
366 self.assertIsNone(foo.__file__)
367
368 def test_path_indexable(self):
369 # bpo-35843
370 import foo
371 expected_path = os.path.join(self.root, 'portion1', 'foo')
372 self.assertEqual(foo.__path__[0], expected_path)
373
374 def test_loader_abc(self):
375 import foo
376 self.assertTrue(isinstance(foo.__loader__, importlib.abc.Loader))
377 self.assertTrue(isinstance(foo.__loader__, importlib.machinery.NamespaceLoader))
378
379
380 if __name__ == "__main__":
381 unittest.main()