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