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