diff options
author | Eric V. Smith <eric@trueblade.com> | 2012-05-25 00:21:04 (GMT) |
---|---|---|
committer | Eric V. Smith <eric@trueblade.com> | 2012-05-25 00:21:04 (GMT) |
commit | 984b11f88fcace98e30decc19bbf9e281355e607 (patch) | |
tree | 613a0fb564da71c5fc84e9343813f87619591732 /Lib/test | |
parent | fa52cbd5e6210f257de40aab12d55d84d64bdb91 (diff) | |
download | cpython-984b11f88fcace98e30decc19bbf9e281355e607.zip cpython-984b11f88fcace98e30decc19bbf9e281355e607.tar.gz cpython-984b11f88fcace98e30decc19bbf9e281355e607.tar.bz2 |
issue 14660: Implement PEP 420, namespace packages.
Diffstat (limited to 'Lib/test')
-rw-r--r-- | Lib/test/namespace_pkgs/both_portions/foo/one.py | 1 | ||||
-rw-r--r-- | Lib/test/namespace_pkgs/both_portions/foo/two.py | 1 | ||||
-rw-r--r-- | Lib/test/namespace_pkgs/missing_directory.zip | bin | 0 -> 515 bytes | |||
-rw-r--r-- | Lib/test/namespace_pkgs/nested_portion1.zip | bin | 0 -> 556 bytes | |||
-rw-r--r-- | Lib/test/namespace_pkgs/not_a_namespace_pkg/foo/__init__.py | 0 | ||||
-rw-r--r-- | Lib/test/namespace_pkgs/not_a_namespace_pkg/foo/one.py | 1 | ||||
-rw-r--r-- | Lib/test/namespace_pkgs/portion1/foo/one.py | 1 | ||||
-rw-r--r-- | Lib/test/namespace_pkgs/portion2/foo/two.py | 1 | ||||
-rw-r--r-- | Lib/test/namespace_pkgs/project1/parent/child/one.py | 1 | ||||
-rw-r--r-- | Lib/test/namespace_pkgs/project2/parent/child/two.py | 1 | ||||
-rw-r--r-- | Lib/test/namespace_pkgs/project3/parent/child/three.py | 1 | ||||
-rw-r--r-- | Lib/test/namespace_pkgs/top_level_portion1.zip | bin | 0 -> 332 bytes | |||
-rw-r--r-- | Lib/test/test_frozen.py | 2 | ||||
-rw-r--r-- | Lib/test/test_import.py | 6 | ||||
-rw-r--r-- | Lib/test/test_module.py | 91 | ||||
-rw-r--r-- | Lib/test/test_namespace_pkgs.py | 239 | ||||
-rw-r--r-- | Lib/test/test_pkgutil.py | 52 |
17 files changed, 379 insertions, 19 deletions
diff --git a/Lib/test/namespace_pkgs/both_portions/foo/one.py b/Lib/test/namespace_pkgs/both_portions/foo/one.py new file mode 100644 index 0000000..3080f6f --- /dev/null +++ b/Lib/test/namespace_pkgs/both_portions/foo/one.py @@ -0,0 +1 @@ +attr = 'both_portions foo one' diff --git a/Lib/test/namespace_pkgs/both_portions/foo/two.py b/Lib/test/namespace_pkgs/both_portions/foo/two.py new file mode 100644 index 0000000..4131d3d --- /dev/null +++ b/Lib/test/namespace_pkgs/both_portions/foo/two.py @@ -0,0 +1 @@ +attr = 'both_portions foo two' diff --git a/Lib/test/namespace_pkgs/missing_directory.zip b/Lib/test/namespace_pkgs/missing_directory.zip Binary files differnew file mode 100644 index 0000000..836a910 --- /dev/null +++ b/Lib/test/namespace_pkgs/missing_directory.zip diff --git a/Lib/test/namespace_pkgs/nested_portion1.zip b/Lib/test/namespace_pkgs/nested_portion1.zip Binary files differnew file mode 100644 index 0000000..8d22406 --- /dev/null +++ b/Lib/test/namespace_pkgs/nested_portion1.zip diff --git a/Lib/test/namespace_pkgs/not_a_namespace_pkg/foo/__init__.py b/Lib/test/namespace_pkgs/not_a_namespace_pkg/foo/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Lib/test/namespace_pkgs/not_a_namespace_pkg/foo/__init__.py diff --git a/Lib/test/namespace_pkgs/not_a_namespace_pkg/foo/one.py b/Lib/test/namespace_pkgs/not_a_namespace_pkg/foo/one.py new file mode 100644 index 0000000..d8f5c83 --- /dev/null +++ b/Lib/test/namespace_pkgs/not_a_namespace_pkg/foo/one.py @@ -0,0 +1 @@ +attr = 'portion1 foo one' diff --git a/Lib/test/namespace_pkgs/portion1/foo/one.py b/Lib/test/namespace_pkgs/portion1/foo/one.py new file mode 100644 index 0000000..d8f5c83 --- /dev/null +++ b/Lib/test/namespace_pkgs/portion1/foo/one.py @@ -0,0 +1 @@ +attr = 'portion1 foo one' diff --git a/Lib/test/namespace_pkgs/portion2/foo/two.py b/Lib/test/namespace_pkgs/portion2/foo/two.py new file mode 100644 index 0000000..d092e1e --- /dev/null +++ b/Lib/test/namespace_pkgs/portion2/foo/two.py @@ -0,0 +1 @@ +attr = 'portion2 foo two' diff --git a/Lib/test/namespace_pkgs/project1/parent/child/one.py b/Lib/test/namespace_pkgs/project1/parent/child/one.py new file mode 100644 index 0000000..2776fcd --- /dev/null +++ b/Lib/test/namespace_pkgs/project1/parent/child/one.py @@ -0,0 +1 @@ +attr = 'parent child one' diff --git a/Lib/test/namespace_pkgs/project2/parent/child/two.py b/Lib/test/namespace_pkgs/project2/parent/child/two.py new file mode 100644 index 0000000..8b037bc --- /dev/null +++ b/Lib/test/namespace_pkgs/project2/parent/child/two.py @@ -0,0 +1 @@ +attr = 'parent child two' diff --git a/Lib/test/namespace_pkgs/project3/parent/child/three.py b/Lib/test/namespace_pkgs/project3/parent/child/three.py new file mode 100644 index 0000000..f8abfe1 --- /dev/null +++ b/Lib/test/namespace_pkgs/project3/parent/child/three.py @@ -0,0 +1 @@ +attr = 'parent child three' diff --git a/Lib/test/namespace_pkgs/top_level_portion1.zip b/Lib/test/namespace_pkgs/top_level_portion1.zip Binary files differnew file mode 100644 index 0000000..3b866c9 --- /dev/null +++ b/Lib/test/namespace_pkgs/top_level_portion1.zip diff --git a/Lib/test/test_frozen.py b/Lib/test/test_frozen.py index dbd229b..fd6761c 100644 --- a/Lib/test/test_frozen.py +++ b/Lib/test/test_frozen.py @@ -7,7 +7,7 @@ import sys class FrozenTests(unittest.TestCase): module_attrs = frozenset(['__builtins__', '__cached__', '__doc__', - '__file__', '__loader__', '__name__', + '__loader__', '__name__', '__package__']) package_attrs = frozenset(list(module_attrs) + ['__path__']) diff --git a/Lib/test/test_import.py b/Lib/test/test_import.py index 890041f..a90e627 100644 --- a/Lib/test/test_import.py +++ b/Lib/test/test_import.py @@ -286,12 +286,6 @@ class ImportTests(unittest.TestCase): import test.support as y self.assertIs(y, test.support, y.__name__) - def test_import_initless_directory_warning(self): - with check_warnings(('', ImportWarning)): - # Just a random non-package directory we always expect to be - # somewhere in sys.path... - self.assertRaises(ImportError, __import__, "site-packages") - def test_import_by_filename(self): path = os.path.abspath(TESTFN) encoding = sys.getfilesystemencoding() diff --git a/Lib/test/test_module.py b/Lib/test/test_module.py index 5617789..e5a2525 100644 --- a/Lib/test/test_module.py +++ b/Lib/test/test_module.py @@ -5,6 +5,15 @@ from test.support import run_unittest, gc_collect import sys ModuleType = type(sys) +class FullLoader: + @classmethod + def module_repr(cls, m): + return "<module '{}' (crafted)>".format(m.__name__) + +class BareLoader: + pass + + class ModuleTests(unittest.TestCase): def test_uninitialized(self): # An uninitialized module has no __dict__ or __name__, @@ -80,8 +89,90 @@ a = A(destroyed)""" gc_collect() self.assertEqual(destroyed, [1]) + def test_module_repr_minimal(self): + # reprs when modules have no __file__, __name__, or __loader__ + m = ModuleType('foo') + del m.__name__ + self.assertEqual(repr(m), "<module '?'>") + + def test_module_repr_with_name(self): + m = ModuleType('foo') + self.assertEqual(repr(m), "<module 'foo'>") + + def test_module_repr_with_name_and_filename(self): + m = ModuleType('foo') + m.__file__ = '/tmp/foo.py' + self.assertEqual(repr(m), "<module 'foo' from '/tmp/foo.py'>") + + def test_module_repr_with_filename_only(self): + m = ModuleType('foo') + del m.__name__ + m.__file__ = '/tmp/foo.py' + self.assertEqual(repr(m), "<module '?' from '/tmp/foo.py'>") + + def test_module_repr_with_bare_loader_but_no_name(self): + m = ModuleType('foo') + del m.__name__ + # Yes, a class not an instance. + m.__loader__ = BareLoader + self.assertEqual( + repr(m), "<module '?' (<class 'test.test_module.BareLoader'>)>") + + def test_module_repr_with_full_loader_but_no_name(self): + # m.__loader__.module_repr() will fail because the module has no + # m.__name__. This exception will get suppressed and instead the + # loader's repr will be used. + m = ModuleType('foo') + del m.__name__ + # Yes, a class not an instance. + m.__loader__ = FullLoader + self.assertEqual( + repr(m), "<module '?' (<class 'test.test_module.FullLoader'>)>") + + def test_module_repr_with_bare_loader(self): + m = ModuleType('foo') + # Yes, a class not an instance. + m.__loader__ = BareLoader + self.assertEqual( + repr(m), "<module 'foo' (<class 'test.test_module.BareLoader'>)>") + + def test_module_repr_with_full_loader(self): + m = ModuleType('foo') + # Yes, a class not an instance. + m.__loader__ = FullLoader + self.assertEqual( + repr(m), "<module 'foo' (crafted)>") + + def test_module_repr_with_bare_loader_and_filename(self): + # Because the loader has no module_repr(), use the file name. + m = ModuleType('foo') + # Yes, a class not an instance. + m.__loader__ = BareLoader + m.__file__ = '/tmp/foo.py' + self.assertEqual(repr(m), "<module 'foo' from '/tmp/foo.py'>") + + def test_module_repr_with_full_loader_and_filename(self): + # Even though the module has an __file__, use __loader__.module_repr() + m = ModuleType('foo') + # Yes, a class not an instance. + m.__loader__ = FullLoader + m.__file__ = '/tmp/foo.py' + self.assertEqual(repr(m), "<module 'foo' (crafted)>") + + def test_module_repr_builtin(self): + self.assertEqual(repr(sys), "<module 'sys' (built-in)>") + + def test_module_repr_source(self): + r = repr(unittest) + self.assertEqual(r[:25], "<module 'unittest' from '") + self.assertEqual(r[-13:], "__init__.py'>") + + # frozen and namespace module reprs are tested in importlib. + + def test_main(): run_unittest(ModuleTests) + if __name__ == '__main__': test_main() diff --git a/Lib/test/test_namespace_pkgs.py b/Lib/test/test_namespace_pkgs.py new file mode 100644 index 0000000..176ddd3 --- /dev/null +++ b/Lib/test/test_namespace_pkgs.py @@ -0,0 +1,239 @@ +import sys +import contextlib +import unittest +import os + +import importlib.test.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 = importlib.test.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 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') + + +def test_main(): + run_unittest(*NamespacePackageTest.__subclasses__()) + + +if __name__ == "__main__": + test_main() diff --git a/Lib/test/test_pkgutil.py b/Lib/test/test_pkgutil.py index 6025bcd..a41b5f5 100644 --- a/Lib/test/test_pkgutil.py +++ b/Lib/test/test_pkgutil.py @@ -138,10 +138,11 @@ class PkgutilPEP302Tests(unittest.TestCase): del sys.modules['foo'] +# These tests, especially the setup and cleanup, are hideous. They +# need to be cleaned up once issue 14715 is addressed. class ExtendPathTests(unittest.TestCase): def create_init(self, pkgname): dirname = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, dirname) sys.path.insert(0, dirname) pkgdir = os.path.join(dirname, pkgname) @@ -156,22 +157,40 @@ class ExtendPathTests(unittest.TestCase): with open(module_name, 'w') as fl: print('value={}'.format(value), file=fl) - def setUp(self): - # Create 2 directories on sys.path - self.pkgname = 'foo' - self.dirname_0 = self.create_init(self.pkgname) - self.dirname_1 = self.create_init(self.pkgname) + def test_simple(self): + pkgname = 'foo' + dirname_0 = self.create_init(pkgname) + dirname_1 = self.create_init(pkgname) + self.create_submodule(dirname_0, pkgname, 'bar', 0) + self.create_submodule(dirname_1, pkgname, 'baz', 1) + import foo.bar + import foo.baz + # Ensure we read the expected values + self.assertEqual(foo.bar.value, 0) + self.assertEqual(foo.baz.value, 1) - def tearDown(self): + # Ensure the path is set up correctly + self.assertEqual(sorted(foo.__path__), + sorted([os.path.join(dirname_0, pkgname), + os.path.join(dirname_1, pkgname)])) + + # Cleanup + shutil.rmtree(dirname_0) + shutil.rmtree(dirname_1) del sys.path[0] del sys.path[0] del sys.modules['foo'] del sys.modules['foo.bar'] del sys.modules['foo.baz'] - def test_simple(self): - self.create_submodule(self.dirname_0, self.pkgname, 'bar', 0) - self.create_submodule(self.dirname_1, self.pkgname, 'baz', 1) + def test_mixed_namespace(self): + pkgname = 'foo' + dirname_0 = self.create_init(pkgname) + dirname_1 = self.create_init(pkgname) + self.create_submodule(dirname_0, pkgname, 'bar', 0) + # Turn this into a PEP 420 namespace package + os.unlink(os.path.join(dirname_0, pkgname, '__init__.py')) + self.create_submodule(dirname_1, pkgname, 'baz', 1) import foo.bar import foo.baz # Ensure we read the expected values @@ -180,8 +199,17 @@ class ExtendPathTests(unittest.TestCase): # Ensure the path is set up correctly self.assertEqual(sorted(foo.__path__), - sorted([os.path.join(self.dirname_0, self.pkgname), - os.path.join(self.dirname_1, self.pkgname)])) + sorted([os.path.join(dirname_0, pkgname), + os.path.join(dirname_1, pkgname)])) + + # Cleanup + shutil.rmtree(dirname_0) + shutil.rmtree(dirname_1) + del sys.path[0] + del sys.path[0] + del sys.modules['foo'] + del sys.modules['foo.bar'] + del sys.modules['foo.baz'] # XXX: test .pkg files |