mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 03:44:55 +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()
 |