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_module_repr(self):
83 import foo.one
84 with warnings.catch_warnings():
85 warnings.simplefilter("ignore")
86 self.assertEqual(foo.__spec__.loader.module_repr(foo),
87 "<module 'foo' (namespace)>")
88
89
90 class ESC[4;38;5;81mDynamicPathNamespacePackage(ESC[4;38;5;149mNamespacePackageTest):
91 paths = ['portion1']
92
93 def test_dynamic_path(self):
94 # Make sure only 'foo.one' can be imported
95 import foo.one
96 self.assertEqual(foo.one.attr, 'portion1 foo one')
97
98 with self.assertRaises(ImportError):
99 import foo.two
100
101 # Now modify sys.path
102 sys.path.append(os.path.join(self.root, 'portion2'))
103
104 # And make sure foo.two is now importable
105 import foo.two
106 self.assertEqual(foo.two.attr, 'portion2 foo two')
107
108
109 class ESC[4;38;5;81mCombinedNamespacePackages(ESC[4;38;5;149mNamespacePackageTest):
110 paths = ['both_portions']
111
112 def test_imports(self):
113 import foo.one
114 import foo.two
115 self.assertEqual(foo.one.attr, 'both_portions foo one')
116 self.assertEqual(foo.two.attr, 'both_portions foo two')
117
118
119 class ESC[4;38;5;81mSeparatedNamespacePackages(ESC[4;38;5;149mNamespacePackageTest):
120 paths = ['portion1', 'portion2']
121
122 def test_imports(self):
123 import foo.one
124 import foo.two
125 self.assertEqual(foo.one.attr, 'portion1 foo one')
126 self.assertEqual(foo.two.attr, 'portion2 foo two')
127
128
129 class ESC[4;38;5;81mSeparatedNamespacePackagesCreatedWhileRunning(ESC[4;38;5;149mNamespacePackageTest):
130 paths = ['portion1']
131
132 def test_invalidate_caches(self):
133 with tempfile.TemporaryDirectory() as temp_dir:
134 # we manipulate sys.path before anything is imported to avoid
135 # accidental cache invalidation when changing it
136 sys.path.append(temp_dir)
137
138 import foo.one
139 self.assertEqual(foo.one.attr, 'portion1 foo one')
140
141 # the module does not exist, so it cannot be imported
142 with self.assertRaises(ImportError):
143 import foo.just_created
144
145 # util.create_modules() manipulates sys.path
146 # so we must create the modules manually instead
147 namespace_path = os.path.join(temp_dir, 'foo')
148 os.mkdir(namespace_path)
149 module_path = os.path.join(namespace_path, 'just_created.py')
150 with open(module_path, 'w', encoding='utf-8') as file:
151 file.write('attr = "just_created foo"')
152
153 # the module is not known, so it cannot be imported yet
154 with self.assertRaises(ImportError):
155 import foo.just_created
156
157 # but after explicit cache invalidation, it is importable
158 importlib.invalidate_caches()
159 import foo.just_created
160 self.assertEqual(foo.just_created.attr, 'just_created foo')
161
162
163 class ESC[4;38;5;81mSeparatedOverlappingNamespacePackages(ESC[4;38;5;149mNamespacePackageTest):
164 paths = ['portion1', 'both_portions']
165
166 def test_first_path_wins(self):
167 import foo.one
168 import foo.two
169 self.assertEqual(foo.one.attr, 'portion1 foo one')
170 self.assertEqual(foo.two.attr, 'both_portions foo two')
171
172 def test_first_path_wins_again(self):
173 sys.path.reverse()
174 import foo.one
175 import foo.two
176 self.assertEqual(foo.one.attr, 'both_portions foo one')
177 self.assertEqual(foo.two.attr, 'both_portions foo two')
178
179 def test_first_path_wins_importing_second_first(self):
180 import foo.two
181 import foo.one
182 self.assertEqual(foo.one.attr, 'portion1 foo one')
183 self.assertEqual(foo.two.attr, 'both_portions foo two')
184
185
186 class ESC[4;38;5;81mSingleZipNamespacePackage(ESC[4;38;5;149mNamespacePackageTest):
187 paths = ['top_level_portion1.zip']
188
189 def test_simple_package(self):
190 import foo.one
191 self.assertEqual(foo.one.attr, 'portion1 foo one')
192
193 def test_cant_import_other(self):
194 with self.assertRaises(ImportError):
195 import foo.two
196
197
198 class ESC[4;38;5;81mSeparatedZipNamespacePackages(ESC[4;38;5;149mNamespacePackageTest):
199 paths = ['top_level_portion1.zip', 'portion2']
200
201 def test_imports(self):
202 import foo.one
203 import foo.two
204 self.assertEqual(foo.one.attr, 'portion1 foo one')
205 self.assertEqual(foo.two.attr, 'portion2 foo two')
206 self.assertIn('top_level_portion1.zip', foo.one.__file__)
207 self.assertNotIn('.zip', foo.two.__file__)
208
209
210 class ESC[4;38;5;81mSingleNestedZipNamespacePackage(ESC[4;38;5;149mNamespacePackageTest):
211 paths = ['nested_portion1.zip/nested_portion1']
212
213 def test_simple_package(self):
214 import foo.one
215 self.assertEqual(foo.one.attr, 'portion1 foo one')
216
217 def test_cant_import_other(self):
218 with self.assertRaises(ImportError):
219 import foo.two
220
221
222 class ESC[4;38;5;81mSeparatedNestedZipNamespacePackages(ESC[4;38;5;149mNamespacePackageTest):
223 paths = ['nested_portion1.zip/nested_portion1', 'portion2']
224
225 def test_imports(self):
226 import foo.one
227 import foo.two
228 self.assertEqual(foo.one.attr, 'portion1 foo one')
229 self.assertEqual(foo.two.attr, 'portion2 foo two')
230 fn = os.path.join('nested_portion1.zip', 'nested_portion1')
231 self.assertIn(fn, foo.one.__file__)
232 self.assertNotIn('.zip', foo.two.__file__)
233
234
235 class ESC[4;38;5;81mLegacySupport(ESC[4;38;5;149mNamespacePackageTest):
236 paths = ['not_a_namespace_pkg', 'portion1', 'portion2', 'both_portions']
237
238 def test_non_namespace_package_takes_precedence(self):
239 import foo.one
240 with self.assertRaises(ImportError):
241 import foo.two
242 self.assertIn('__init__', foo.__file__)
243 self.assertNotIn('namespace', str(foo.__loader__).lower())
244
245
246 class ESC[4;38;5;81mDynamicPathCalculation(ESC[4;38;5;149mNamespacePackageTest):
247 paths = ['project1', 'project2']
248
249 def test_project3_fails(self):
250 import parent.child.one
251 self.assertEqual(len(parent.__path__), 2)
252 self.assertEqual(len(parent.child.__path__), 2)
253 import parent.child.two
254 self.assertEqual(len(parent.__path__), 2)
255 self.assertEqual(len(parent.child.__path__), 2)
256
257 self.assertEqual(parent.child.one.attr, 'parent child one')
258 self.assertEqual(parent.child.two.attr, 'parent child two')
259
260 with self.assertRaises(ImportError):
261 import parent.child.three
262
263 self.assertEqual(len(parent.__path__), 2)
264 self.assertEqual(len(parent.child.__path__), 2)
265
266 def test_project3_succeeds(self):
267 import parent.child.one
268 self.assertEqual(len(parent.__path__), 2)
269 self.assertEqual(len(parent.child.__path__), 2)
270 import parent.child.two
271 self.assertEqual(len(parent.__path__), 2)
272 self.assertEqual(len(parent.child.__path__), 2)
273
274 self.assertEqual(parent.child.one.attr, 'parent child one')
275 self.assertEqual(parent.child.two.attr, 'parent child two')
276
277 with self.assertRaises(ImportError):
278 import parent.child.three
279
280 # now add project3
281 sys.path.append(os.path.join(self.root, 'project3'))
282 import parent.child.three
283
284 # the paths dynamically get longer, to include the new directories
285 self.assertEqual(len(parent.__path__), 3)
286 self.assertEqual(len(parent.child.__path__), 3)
287
288 self.assertEqual(parent.child.three.attr, 'parent child three')
289
290
291 class ESC[4;38;5;81mZipWithMissingDirectory(ESC[4;38;5;149mNamespacePackageTest):
292 paths = ['missing_directory.zip']
293
294 @unittest.expectedFailure
295 def test_missing_directory(self):
296 # This will fail because missing_directory.zip contains:
297 # Length Date Time Name
298 # --------- ---------- ----- ----
299 # 29 2012-05-03 18:13 foo/one.py
300 # 0 2012-05-03 20:57 bar/
301 # 38 2012-05-03 20:57 bar/two.py
302 # --------- -------
303 # 67 3 files
304
305 # Because there is no 'foo/', the zipimporter currently doesn't
306 # know that foo is a namespace package
307
308 import foo.one
309
310 def test_present_directory(self):
311 # This succeeds because there is a "bar/" in the zip file
312 import bar.two
313 self.assertEqual(bar.two.attr, 'missing_directory foo two')
314
315
316 class ESC[4;38;5;81mModuleAndNamespacePackageInSameDir(ESC[4;38;5;149mNamespacePackageTest):
317 paths = ['module_and_namespace_package']
318
319 def test_module_before_namespace_package(self):
320 # Make sure we find the module in preference to the
321 # namespace package.
322 import a_test
323 self.assertEqual(a_test.attr, 'in module')
324
325
326 class ESC[4;38;5;81mReloadTests(ESC[4;38;5;149mNamespacePackageTest):
327 paths = ['portion1']
328
329 def test_simple_package(self):
330 import foo.one
331 foo = importlib.reload(foo)
332 self.assertEqual(foo.one.attr, 'portion1 foo one')
333
334 def test_cant_import_other(self):
335 import foo
336 with self.assertRaises(ImportError):
337 import foo.two
338 foo = importlib.reload(foo)
339 with self.assertRaises(ImportError):
340 import foo.two
341
342 def test_dynamic_path(self):
343 import foo.one
344 with self.assertRaises(ImportError):
345 import foo.two
346
347 # Now modify sys.path and reload.
348 sys.path.append(os.path.join(self.root, 'portion2'))
349 foo = importlib.reload(foo)
350
351 # And make sure foo.two is now importable
352 import foo.two
353 self.assertEqual(foo.two.attr, 'portion2 foo two')
354
355
356 class ESC[4;38;5;81mLoaderTests(ESC[4;38;5;149mNamespacePackageTest):
357 paths = ['portion1']
358
359 def test_namespace_loader_consistency(self):
360 # bpo-32303
361 import foo
362 self.assertEqual(foo.__loader__, foo.__spec__.loader)
363 self.assertIsNotNone(foo.__loader__)
364
365 def test_namespace_origin_consistency(self):
366 # bpo-32305
367 import foo
368 self.assertIsNone(foo.__spec__.origin)
369 self.assertIsNone(foo.__file__)
370
371 def test_path_indexable(self):
372 # bpo-35843
373 import foo
374 expected_path = os.path.join(self.root, 'portion1', 'foo')
375 self.assertEqual(foo.__path__[0], expected_path)
376
377 def test_loader_abc(self):
378 import foo
379 self.assertTrue(isinstance(foo.__loader__, importlib.abc.Loader))
380 self.assertTrue(isinstance(foo.__loader__, importlib.machinery.NamespaceLoader))
381
382
383 if __name__ == "__main__":
384 unittest.main()