mirror of
				https://github.com/python/cpython.git
				synced 2025-10-28 20:25:04 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			379 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			379 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import contextlib
 | |
| import importlib
 | |
| import importlib.abc
 | |
| import importlib.machinery
 | |
| import os
 | |
| import sys
 | |
| import tempfile
 | |
| import unittest
 | |
| 
 | |
| from test.test_importlib import util
 | |
| 
 | |
| # needed tests:
 | |
| #
 | |
| # need to test when nested, so that the top-level path isn't sys.path
 | |
| # need to test dynamic path detection, both at top-level and nested
 | |
| # with dynamic path, check when a loader is returned on path reload (that is,
 | |
| #  trying to switch from a namespace package to a regular package)
 | |
| 
 | |
| 
 | |
| @contextlib.contextmanager
 | |
| def sys_modules_context():
 | |
|     """
 | |
|     Make sure sys.modules is the same object and has the same content
 | |
|     when exiting the context as when entering.
 | |
| 
 | |
|     Similar to importlib.test.util.uncache, but doesn't require explicit
 | |
|     names.
 | |
|     """
 | |
|     sys_modules_saved = sys.modules
 | |
|     sys_modules_copy = sys.modules.copy()
 | |
|     try:
 | |
|         yield
 | |
|     finally:
 | |
|         sys.modules = sys_modules_saved
 | |
|         sys.modules.clear()
 | |
|         sys.modules.update(sys_modules_copy)
 | |
| 
 | |
| 
 | |
| @contextlib.contextmanager
 | |
| def namespace_tree_context(**kwargs):
 | |
|     """
 | |
|     Save import state and sys.modules cache and restore it on exit.
 | |
|     Typical usage:
 | |
| 
 | |
|     >>> with namespace_tree_context(path=['/tmp/xxyy/portion1',
 | |
|     ...         '/tmp/xxyy/portion2']):
 | |
|     ...     pass
 | |
|     """
 | |
|     # use default meta_path and path_hooks unless specified otherwise
 | |
|     kwargs.setdefault('meta_path', sys.meta_path)
 | |
|     kwargs.setdefault('path_hooks', sys.path_hooks)
 | |
|     import_context = util.import_state(**kwargs)
 | |
|     with import_context, sys_modules_context():
 | |
|         yield
 | |
| 
 | |
| class NamespacePackageTest(unittest.TestCase):
 | |
|     """
 | |
|     Subclasses should define self.root and self.paths (under that root)
 | |
|     to be added to sys.path.
 | |
|     """
 | |
|     root = os.path.join(os.path.dirname(__file__), 'namespace_pkgs')
 | |
| 
 | |
|     def setUp(self):
 | |
|         self.resolved_paths = [
 | |
|             os.path.join(self.root, path) for path in self.paths
 | |
|         ]
 | |
|         self.enterContext(namespace_tree_context(path=self.resolved_paths))
 | |
| 
 | |
| 
 | |
| class SingleNamespacePackage(NamespacePackageTest):
 | |
|     paths = ['portion1']
 | |
| 
 | |
|     def test_simple_package(self):
 | |
|         import foo.one
 | |
|         self.assertEqual(foo.one.attr, 'portion1 foo one')
 | |
| 
 | |
|     def test_cant_import_other(self):
 | |
|         with self.assertRaises(ImportError):
 | |
|             import foo.two
 | |
| 
 | |
|     def test_simple_repr(self):
 | |
|         import foo.one
 | |
|         self.assertTrue(repr(foo).startswith("<module 'foo' (namespace) from ["))
 | |
| 
 | |
| 
 | |
| class DynamicPathNamespacePackage(NamespacePackageTest):
 | |
|     paths = ['portion1']
 | |
| 
 | |
|     def test_dynamic_path(self):
 | |
|         # Make sure only 'foo.one' can be imported
 | |
|         import foo.one
 | |
|         self.assertEqual(foo.one.attr, 'portion1 foo one')
 | |
| 
 | |
|         with self.assertRaises(ImportError):
 | |
|             import foo.two
 | |
| 
 | |
|         # Now modify sys.path
 | |
|         sys.path.append(os.path.join(self.root, 'portion2'))
 | |
| 
 | |
|         # And make sure foo.two is now importable
 | |
|         import foo.two
 | |
|         self.assertEqual(foo.two.attr, 'portion2 foo two')
 | |
| 
 | |
| 
 | |
| class CombinedNamespacePackages(NamespacePackageTest):
 | |
|     paths = ['both_portions']
 | |
| 
 | |
|     def test_imports(self):
 | |
|         import foo.one
 | |
|         import foo.two
 | |
|         self.assertEqual(foo.one.attr, 'both_portions foo one')
 | |
|         self.assertEqual(foo.two.attr, 'both_portions foo two')
 | |
| 
 | |
| 
 | |
| class SeparatedNamespacePackages(NamespacePackageTest):
 | |
|     paths = ['portion1', 'portion2']
 | |
| 
 | |
|     def test_imports(self):
 | |
|         import foo.one
 | |
|         import foo.two
 | |
|         self.assertEqual(foo.one.attr, 'portion1 foo one')
 | |
|         self.assertEqual(foo.two.attr, 'portion2 foo two')
 | |
| 
 | |
| 
 | |
| class SeparatedNamespacePackagesCreatedWhileRunning(NamespacePackageTest):
 | |
|     paths = ['portion1']
 | |
| 
 | |
|     def test_invalidate_caches(self):
 | |
|         with tempfile.TemporaryDirectory() as temp_dir:
 | |
|             # we manipulate sys.path before anything is imported to avoid
 | |
|             # accidental cache invalidation when changing it
 | |
|             sys.path.append(temp_dir)
 | |
| 
 | |
|             import foo.one
 | |
|             self.assertEqual(foo.one.attr, 'portion1 foo one')
 | |
| 
 | |
|             # the module does not exist, so it cannot be imported
 | |
|             with self.assertRaises(ImportError):
 | |
|                 import foo.just_created
 | |
| 
 | |
|             # util.create_modules() manipulates sys.path
 | |
|             # so we must create the modules manually instead
 | |
|             namespace_path = os.path.join(temp_dir, 'foo')
 | |
|             os.mkdir(namespace_path)
 | |
|             module_path = os.path.join(namespace_path, 'just_created.py')
 | |
|             with open(module_path, 'w', encoding='utf-8') as file:
 | |
|                 file.write('attr = "just_created foo"')
 | |
| 
 | |
|             # the module is not known, so it cannot be imported yet
 | |
|             with self.assertRaises(ImportError):
 | |
|                 import foo.just_created
 | |
| 
 | |
|             # but after explicit cache invalidation, it is importable
 | |
|             importlib.invalidate_caches()
 | |
|             import foo.just_created
 | |
|             self.assertEqual(foo.just_created.attr, 'just_created foo')
 | |
| 
 | |
| 
 | |
| class SeparatedOverlappingNamespacePackages(NamespacePackageTest):
 | |
|     paths = ['portion1', 'both_portions']
 | |
| 
 | |
|     def test_first_path_wins(self):
 | |
|         import foo.one
 | |
|         import foo.two
 | |
|         self.assertEqual(foo.one.attr, 'portion1 foo one')
 | |
|         self.assertEqual(foo.two.attr, 'both_portions foo two')
 | |
| 
 | |
|     def test_first_path_wins_again(self):
 | |
|         sys.path.reverse()
 | |
|         import foo.one
 | |
|         import foo.two
 | |
|         self.assertEqual(foo.one.attr, 'both_portions foo one')
 | |
|         self.assertEqual(foo.two.attr, 'both_portions foo two')
 | |
| 
 | |
|     def test_first_path_wins_importing_second_first(self):
 | |
|         import foo.two
 | |
|         import foo.one
 | |
|         self.assertEqual(foo.one.attr, 'portion1 foo one')
 | |
|         self.assertEqual(foo.two.attr, 'both_portions foo two')
 | |
| 
 | |
| 
 | |
| class SingleZipNamespacePackage(NamespacePackageTest):
 | |
|     paths = ['top_level_portion1.zip']
 | |
| 
 | |
|     def test_simple_package(self):
 | |
|         import foo.one
 | |
|         self.assertEqual(foo.one.attr, 'portion1 foo one')
 | |
| 
 | |
|     def test_cant_import_other(self):
 | |
|         with self.assertRaises(ImportError):
 | |
|             import foo.two
 | |
| 
 | |
| 
 | |
| class SeparatedZipNamespacePackages(NamespacePackageTest):
 | |
|     paths = ['top_level_portion1.zip', 'portion2']
 | |
| 
 | |
|     def test_imports(self):
 | |
|         import foo.one
 | |
|         import foo.two
 | |
|         self.assertEqual(foo.one.attr, 'portion1 foo one')
 | |
|         self.assertEqual(foo.two.attr, 'portion2 foo two')
 | |
|         self.assertIn('top_level_portion1.zip', foo.one.__file__)
 | |
|         self.assertNotIn('.zip', foo.two.__file__)
 | |
| 
 | |
| 
 | |
| class SingleNestedZipNamespacePackage(NamespacePackageTest):
 | |
|     paths = ['nested_portion1.zip/nested_portion1']
 | |
| 
 | |
|     def test_simple_package(self):
 | |
|         import foo.one
 | |
|         self.assertEqual(foo.one.attr, 'portion1 foo one')
 | |
| 
 | |
|     def test_cant_import_other(self):
 | |
|         with self.assertRaises(ImportError):
 | |
|             import foo.two
 | |
| 
 | |
| 
 | |
| class SeparatedNestedZipNamespacePackages(NamespacePackageTest):
 | |
|     paths = ['nested_portion1.zip/nested_portion1', 'portion2']
 | |
| 
 | |
|     def test_imports(self):
 | |
|         import foo.one
 | |
|         import foo.two
 | |
|         self.assertEqual(foo.one.attr, 'portion1 foo one')
 | |
|         self.assertEqual(foo.two.attr, 'portion2 foo two')
 | |
|         fn = os.path.join('nested_portion1.zip', 'nested_portion1')
 | |
|         self.assertIn(fn, foo.one.__file__)
 | |
|         self.assertNotIn('.zip', foo.two.__file__)
 | |
| 
 | |
| 
 | |
| class LegacySupport(NamespacePackageTest):
 | |
|     paths = ['not_a_namespace_pkg', 'portion1', 'portion2', 'both_portions']
 | |
| 
 | |
|     def test_non_namespace_package_takes_precedence(self):
 | |
|         import foo.one
 | |
|         with self.assertRaises(ImportError):
 | |
|             import foo.two
 | |
|         self.assertIn('__init__', foo.__file__)
 | |
|         self.assertNotIn('namespace', str(foo.__loader__).lower())
 | |
| 
 | |
| 
 | |
| class DynamicPathCalculation(NamespacePackageTest):
 | |
|     paths = ['project1', 'project2']
 | |
| 
 | |
|     def test_project3_fails(self):
 | |
|         import parent.child.one
 | |
|         self.assertEqual(len(parent.__path__), 2)
 | |
|         self.assertEqual(len(parent.child.__path__), 2)
 | |
|         import parent.child.two
 | |
|         self.assertEqual(len(parent.__path__), 2)
 | |
|         self.assertEqual(len(parent.child.__path__), 2)
 | |
| 
 | |
|         self.assertEqual(parent.child.one.attr, 'parent child one')
 | |
|         self.assertEqual(parent.child.two.attr, 'parent child two')
 | |
| 
 | |
|         with self.assertRaises(ImportError):
 | |
|             import parent.child.three
 | |
| 
 | |
|         self.assertEqual(len(parent.__path__), 2)
 | |
|         self.assertEqual(len(parent.child.__path__), 2)
 | |
| 
 | |
|     def test_project3_succeeds(self):
 | |
|         import parent.child.one
 | |
|         self.assertEqual(len(parent.__path__), 2)
 | |
|         self.assertEqual(len(parent.child.__path__), 2)
 | |
|         import parent.child.two
 | |
|         self.assertEqual(len(parent.__path__), 2)
 | |
|         self.assertEqual(len(parent.child.__path__), 2)
 | |
| 
 | |
|         self.assertEqual(parent.child.one.attr, 'parent child one')
 | |
|         self.assertEqual(parent.child.two.attr, 'parent child two')
 | |
| 
 | |
|         with self.assertRaises(ImportError):
 | |
|             import parent.child.three
 | |
| 
 | |
|         # now add project3
 | |
|         sys.path.append(os.path.join(self.root, 'project3'))
 | |
|         import parent.child.three
 | |
| 
 | |
|         # the paths dynamically get longer, to include the new directories
 | |
|         self.assertEqual(len(parent.__path__), 3)
 | |
|         self.assertEqual(len(parent.child.__path__), 3)
 | |
| 
 | |
|         self.assertEqual(parent.child.three.attr, 'parent child three')
 | |
| 
 | |
| 
 | |
| class ZipWithMissingDirectory(NamespacePackageTest):
 | |
|     paths = ['missing_directory.zip']
 | |
|     # missing_directory.zip contains:
 | |
|     #   Length      Date    Time    Name
 | |
|     # ---------  ---------- -----   ----
 | |
|     #        29  2012-05-03 18:13   foo/one.py
 | |
|     #         0  2012-05-03 20:57   bar/
 | |
|     #        38  2012-05-03 20:57   bar/two.py
 | |
|     # ---------                     -------
 | |
|     #        67                     3 files
 | |
| 
 | |
|     def test_missing_directory(self):
 | |
|         import foo.one
 | |
|         self.assertEqual(foo.one.attr, 'portion1 foo one')
 | |
| 
 | |
|     def test_missing_directory2(self):
 | |
|         import foo
 | |
|         self.assertFalse(hasattr(foo, 'one'))
 | |
| 
 | |
|     def test_present_directory(self):
 | |
|         import bar.two
 | |
|         self.assertEqual(bar.two.attr, 'missing_directory foo two')
 | |
| 
 | |
| 
 | |
| class ModuleAndNamespacePackageInSameDir(NamespacePackageTest):
 | |
|     paths = ['module_and_namespace_package']
 | |
| 
 | |
|     def test_module_before_namespace_package(self):
 | |
|         # Make sure we find the module in preference to the
 | |
|         #  namespace package.
 | |
|         import a_test
 | |
|         self.assertEqual(a_test.attr, 'in module')
 | |
| 
 | |
| 
 | |
| class ReloadTests(NamespacePackageTest):
 | |
|     paths = ['portion1']
 | |
| 
 | |
|     def test_simple_package(self):
 | |
|         import foo.one
 | |
|         foo = importlib.reload(foo)
 | |
|         self.assertEqual(foo.one.attr, 'portion1 foo one')
 | |
| 
 | |
|     def test_cant_import_other(self):
 | |
|         import foo
 | |
|         with self.assertRaises(ImportError):
 | |
|             import foo.two
 | |
|         foo = importlib.reload(foo)
 | |
|         with self.assertRaises(ImportError):
 | |
|             import foo.two
 | |
| 
 | |
|     def test_dynamic_path(self):
 | |
|         import foo.one
 | |
|         with self.assertRaises(ImportError):
 | |
|             import foo.two
 | |
| 
 | |
|         # Now modify sys.path and reload.
 | |
|         sys.path.append(os.path.join(self.root, 'portion2'))
 | |
|         foo = importlib.reload(foo)
 | |
| 
 | |
|         # And make sure foo.two is now importable
 | |
|         import foo.two
 | |
|         self.assertEqual(foo.two.attr, 'portion2 foo two')
 | |
| 
 | |
| 
 | |
| class LoaderTests(NamespacePackageTest):
 | |
|     paths = ['portion1']
 | |
| 
 | |
|     def test_namespace_loader_consistency(self):
 | |
|         # bpo-32303
 | |
|         import foo
 | |
|         self.assertEqual(foo.__loader__, foo.__spec__.loader)
 | |
|         self.assertIsNotNone(foo.__loader__)
 | |
| 
 | |
|     def test_namespace_origin_consistency(self):
 | |
|         # bpo-32305
 | |
|         import foo
 | |
|         self.assertIsNone(foo.__spec__.origin)
 | |
|         self.assertIsNone(foo.__file__)
 | |
| 
 | |
|     def test_path_indexable(self):
 | |
|         # bpo-35843
 | |
|         import foo
 | |
|         expected_path = os.path.join(self.root, 'portion1', 'foo')
 | |
|         self.assertEqual(foo.__path__[0], expected_path)
 | |
| 
 | |
|     def test_loader_abc(self):
 | |
|         import foo
 | |
|         self.assertTrue(isinstance(foo.__loader__, importlib.abc.Loader))
 | |
|         self.assertTrue(isinstance(foo.__loader__, importlib.machinery.NamespaceLoader))
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     unittest.main()
 | 
