blob: 6639612631c8d6880c38af87898fb4faf6ccbf42 [file] [log] [blame]
Eric V. Smith984b11f2012-05-24 20:21:04 -04001import contextlib
Brett Cannon13d8ff92013-06-16 14:56:58 -04002import importlib.abc
3import importlib.machinery
Eric V. Smith984b11f2012-05-24 20:21:04 -04004import os
Brett Cannon13d8ff92013-06-16 14:56:58 -04005import sys
6import types
7import unittest
Eric V. Smith984b11f2012-05-24 20:21:04 -04008
Brett Cannoned19fce2012-07-20 15:42:34 -04009from test.test_importlib import util
Eric V. Smith984b11f2012-05-24 20:21:04 -040010from test.support import run_unittest
11
12# needed tests:
13#
14# need to test when nested, so that the top-level path isn't sys.path
15# need to test dynamic path detection, both at top-level and nested
16# with dynamic path, check when a loader is returned on path reload (that is,
17# trying to switch from a namespace package to a regular package)
18
19
20@contextlib.contextmanager
21def sys_modules_context():
22 """
23 Make sure sys.modules is the same object and has the same content
24 when exiting the context as when entering.
25
26 Similar to importlib.test.util.uncache, but doesn't require explicit
27 names.
28 """
29 sys_modules_saved = sys.modules
30 sys_modules_copy = sys.modules.copy()
31 try:
32 yield
33 finally:
34 sys.modules = sys_modules_saved
35 sys.modules.clear()
36 sys.modules.update(sys_modules_copy)
37
38
39@contextlib.contextmanager
40def namespace_tree_context(**kwargs):
41 """
42 Save import state and sys.modules cache and restore it on exit.
43 Typical usage:
44
45 >>> with namespace_tree_context(path=['/tmp/xxyy/portion1',
46 ... '/tmp/xxyy/portion2']):
47 ... pass
48 """
49 # use default meta_path and path_hooks unless specified otherwise
50 kwargs.setdefault('meta_path', sys.meta_path)
51 kwargs.setdefault('path_hooks', sys.path_hooks)
Brett Cannoned19fce2012-07-20 15:42:34 -040052 import_context = util.import_state(**kwargs)
Eric V. Smith984b11f2012-05-24 20:21:04 -040053 with import_context, sys_modules_context():
54 yield
55
56class NamespacePackageTest(unittest.TestCase):
57 """
58 Subclasses should define self.root and self.paths (under that root)
59 to be added to sys.path.
60 """
61 root = os.path.join(os.path.dirname(__file__), 'namespace_pkgs')
62
63 def setUp(self):
64 self.resolved_paths = [
65 os.path.join(self.root, path) for path in self.paths
66 ]
67 self.ctx = namespace_tree_context(path=self.resolved_paths)
68 self.ctx.__enter__()
69
70 def tearDown(self):
71 # TODO: will we ever want to pass exc_info to __exit__?
72 self.ctx.__exit__(None, None, None)
73
74class SingleNamespacePackage(NamespacePackageTest):
75 paths = ['portion1']
76
77 def test_simple_package(self):
78 import foo.one
79 self.assertEqual(foo.one.attr, 'portion1 foo one')
80
81 def test_cant_import_other(self):
82 with self.assertRaises(ImportError):
83 import foo.two
84
85 def test_module_repr(self):
86 import foo.one
87 self.assertEqual(repr(foo), "<module 'foo' (namespace)>")
88
89
90class DynamicPatheNamespacePackage(NamespacePackageTest):
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
109class CombinedNamespacePackages(NamespacePackageTest):
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
119class SeparatedNamespacePackages(NamespacePackageTest):
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
129class SeparatedOverlappingNamespacePackages(NamespacePackageTest):
130 paths = ['portion1', 'both_portions']
131
132 def test_first_path_wins(self):
133 import foo.one
134 import foo.two
135 self.assertEqual(foo.one.attr, 'portion1 foo one')
136 self.assertEqual(foo.two.attr, 'both_portions foo two')
137
138 def test_first_path_wins_again(self):
139 sys.path.reverse()
140 import foo.one
141 import foo.two
142 self.assertEqual(foo.one.attr, 'both_portions foo one')
143 self.assertEqual(foo.two.attr, 'both_portions foo two')
144
145 def test_first_path_wins_importing_second_first(self):
146 import foo.two
147 import foo.one
148 self.assertEqual(foo.one.attr, 'portion1 foo one')
149 self.assertEqual(foo.two.attr, 'both_portions foo two')
150
151
152class SingleZipNamespacePackage(NamespacePackageTest):
153 paths = ['top_level_portion1.zip']
154
155 def test_simple_package(self):
156 import foo.one
157 self.assertEqual(foo.one.attr, 'portion1 foo one')
158
159 def test_cant_import_other(self):
160 with self.assertRaises(ImportError):
161 import foo.two
162
163
164class SeparatedZipNamespacePackages(NamespacePackageTest):
165 paths = ['top_level_portion1.zip', 'portion2']
166
167 def test_imports(self):
168 import foo.one
169 import foo.two
170 self.assertEqual(foo.one.attr, 'portion1 foo one')
171 self.assertEqual(foo.two.attr, 'portion2 foo two')
172 self.assertIn('top_level_portion1.zip', foo.one.__file__)
173 self.assertNotIn('.zip', foo.two.__file__)
174
175
176class SingleNestedZipNamespacePackage(NamespacePackageTest):
177 paths = ['nested_portion1.zip/nested_portion1']
178
179 def test_simple_package(self):
180 import foo.one
181 self.assertEqual(foo.one.attr, 'portion1 foo one')
182
183 def test_cant_import_other(self):
184 with self.assertRaises(ImportError):
185 import foo.two
186
187
188class SeparatedNestedZipNamespacePackages(NamespacePackageTest):
189 paths = ['nested_portion1.zip/nested_portion1', 'portion2']
190
191 def test_imports(self):
192 import foo.one
193 import foo.two
194 self.assertEqual(foo.one.attr, 'portion1 foo one')
195 self.assertEqual(foo.two.attr, 'portion2 foo two')
196 fn = os.path.join('nested_portion1.zip', 'nested_portion1')
197 self.assertIn(fn, foo.one.__file__)
198 self.assertNotIn('.zip', foo.two.__file__)
199
200
201class LegacySupport(NamespacePackageTest):
202 paths = ['not_a_namespace_pkg', 'portion1', 'portion2', 'both_portions']
203
204 def test_non_namespace_package_takes_precedence(self):
205 import foo.one
206 with self.assertRaises(ImportError):
207 import foo.two
208 self.assertIn('__init__', foo.__file__)
209 self.assertNotIn('namespace', str(foo.__loader__).lower())
210
211
Eric V. Smithf879e322012-05-25 11:25:27 -0400212class DynamicPathCalculation(NamespacePackageTest):
213 paths = ['project1', 'project2']
214
215 def test_project3_fails(self):
216 import parent.child.one
217 self.assertEqual(len(parent.__path__), 2)
218 self.assertEqual(len(parent.child.__path__), 2)
219 import parent.child.two
220 self.assertEqual(len(parent.__path__), 2)
221 self.assertEqual(len(parent.child.__path__), 2)
222
223 self.assertEqual(parent.child.one.attr, 'parent child one')
224 self.assertEqual(parent.child.two.attr, 'parent child two')
225
226 with self.assertRaises(ImportError):
227 import parent.child.three
228
229 self.assertEqual(len(parent.__path__), 2)
230 self.assertEqual(len(parent.child.__path__), 2)
231
232 def test_project3_succeeds(self):
233 import parent.child.one
234 self.assertEqual(len(parent.__path__), 2)
235 self.assertEqual(len(parent.child.__path__), 2)
236 import parent.child.two
237 self.assertEqual(len(parent.__path__), 2)
238 self.assertEqual(len(parent.child.__path__), 2)
239
240 self.assertEqual(parent.child.one.attr, 'parent child one')
241 self.assertEqual(parent.child.two.attr, 'parent child two')
242
243 with self.assertRaises(ImportError):
244 import parent.child.three
245
246 # now add project3
247 sys.path.append(os.path.join(self.root, 'project3'))
248 import parent.child.three
249
250 # the paths dynamically get longer, to include the new directories
251 self.assertEqual(len(parent.__path__), 3)
252 self.assertEqual(len(parent.child.__path__), 3)
253
254 self.assertEqual(parent.child.three.attr, 'parent child three')
255
256
Eric V. Smith984b11f2012-05-24 20:21:04 -0400257class ZipWithMissingDirectory(NamespacePackageTest):
258 paths = ['missing_directory.zip']
259
260 @unittest.expectedFailure
261 def test_missing_directory(self):
262 # This will fail because missing_directory.zip contains:
263 # Length Date Time Name
264 # --------- ---------- ----- ----
265 # 29 2012-05-03 18:13 foo/one.py
266 # 0 2012-05-03 20:57 bar/
267 # 38 2012-05-03 20:57 bar/two.py
268 # --------- -------
269 # 67 3 files
270
271 # Because there is no 'foo/', the zipimporter currently doesn't
272 # know that foo is a namespace package
273
274 import foo.one
275
276 def test_present_directory(self):
277 # This succeeds because there is a "bar/" in the zip file
278 import bar.two
279 self.assertEqual(bar.two.attr, 'missing_directory foo two')
280
281
Eric V. Smith714370f2012-06-24 19:55:18 -0400282class ModuleAndNamespacePackageInSameDir(NamespacePackageTest):
283 paths = ['module_and_namespace_package']
Eric V. Smithe51a3692012-06-24 19:13:55 -0400284
285 def test_module_before_namespace_package(self):
Eric V. Smith714370f2012-06-24 19:55:18 -0400286 # Make sure we find the module in preference to the
287 # namespace package.
Eric V. Smithe51a3692012-06-24 19:13:55 -0400288 import a_test
289 self.assertEqual(a_test.attr, 'in module')
290
291
Eric V. Smith984b11f2012-05-24 20:21:04 -0400292if __name__ == "__main__":
Brett Cannon13d8ff92013-06-16 14:56:58 -0400293 unittest.main()