diff options
Diffstat (limited to 'Lib/test/test_namespace_pkgs.py')
| -rw-r--r-- | Lib/test/test_namespace_pkgs.py | 294 | 
1 files changed, 294 insertions, 0 deletions
diff --git a/Lib/test/test_namespace_pkgs.py b/Lib/test/test_namespace_pkgs.py new file mode 100644 index 0000000..7067b12 --- /dev/null +++ b/Lib/test/test_namespace_pkgs.py @@ -0,0 +1,294 @@ +import sys +import contextlib +import unittest +import os + +from test.test_importlib import util +from test.support import run_unittest + +# 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.ctx = namespace_tree_context(path=self.resolved_paths) +        self.ctx.__enter__() + +    def tearDown(self): +        # TODO: will we ever want to pass exc_info to __exit__? +        self.ctx.__exit__(None, None, None) + +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_module_repr(self): +        import foo.one +        self.assertEqual(repr(foo), "<module 'foo' (namespace)>") + + +class DynamicPatheNamespacePackage(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 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'] + +    @unittest.expectedFailure +    def test_missing_directory(self): +        # This will fail because 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 + +        # Because there is no 'foo/', the zipimporter currently doesn't +        #  know that foo is a namespace package + +        import foo.one + +    def test_present_directory(self): +        # This succeeds because there is a "bar/" in the zip file +        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') + + +def test_main(): +    run_unittest(*NamespacePackageTest.__subclasses__()) + + +if __name__ == "__main__": +    test_main()  | 
