diff options
Diffstat (limited to 'Lib/test/test_import.py')
-rw-r--r-- | Lib/test/test_import.py | 528 |
1 files changed, 395 insertions, 133 deletions
diff --git a/Lib/test/test_import.py b/Lib/test/test_import.py index b10f350..b0e5a95 100644 --- a/Lib/test/test_import.py +++ b/Lib/test/test_import.py @@ -1,7 +1,8 @@ +# We import importlib *ASAP* in order to test #15386 +import importlib import builtins import imp -from importlib.test.import_ import test_relative_imports -from importlib.test.import_ import util as importlib_util +from test.test_importlib.import_ import util as importlib_util import marshal import os import platform @@ -12,39 +13,50 @@ import sys import unittest import textwrap import errno +import shutil +import contextlib +import test.support from test.support import ( EnvironmentVarGuard, TESTFN, check_warnings, forget, is_jython, make_legacy_pyc, rmtree, run_unittest, swap_attr, swap_item, temp_umask, - unlink, unload) + unlink, unload, create_empty_file, cpython_only) from test import script_helper -def _files(name): - return (name + os.extsep + "py", - name + os.extsep + "pyc", - name + os.extsep + "pyo", - name + os.extsep + "pyw", - name + "$py.class") - -def chmod_files(name): - for f in _files(name): - try: - os.chmod(f, 0o600) - except OSError as exc: - if exc.errno != errno.ENOENT: - raise - def remove_files(name): - for f in _files(name): + for f in (name + ".py", + name + ".pyc", + name + ".pyo", + name + ".pyw", + name + "$py.class"): unlink(f) rmtree('__pycache__') +@contextlib.contextmanager +def _ready_to_import(name=None, source=""): + # sets up a temporary directory and removes it + # creates the module file + # temporarily clears the module from sys.modules (if any) + name = name or "spam" + with script_helper.temp_dir() as tempdir: + path = script_helper.make_script(tempdir, name, source) + old_module = sys.modules.pop(name, None) + try: + sys.path.insert(0, tempdir) + yield name, path + sys.path.remove(tempdir) + finally: + if old_module is not None: + sys.modules[name] = old_module + + class ImportTests(unittest.TestCase): def setUp(self): remove_files(TESTFN) + importlib.invalidate_caches() def tearDown(self): unload(TESTFN) @@ -82,6 +94,7 @@ class ImportTests(unittest.TestCase): if TESTFN in sys.modules: del sys.modules[TESTFN] + importlib.invalidate_caches() try: try: mod = __import__(TESTFN) @@ -107,90 +120,6 @@ class ImportTests(unittest.TestCase): finally: del sys.path[0] - @unittest.skipUnless(os.name == 'posix', - "test meaningful only on posix systems") - def test_execute_bit_not_copied(self): - # Issue 6070: under posix .pyc files got their execute bit set if - # the .py file had the execute bit set, but they aren't executable. - with temp_umask(0o022): - sys.path.insert(0, os.curdir) - try: - fname = TESTFN + os.extsep + "py" - open(fname, 'w').close() - os.chmod(fname, (stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | - stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)) - fn = imp.cache_from_source(fname) - unlink(fn) - __import__(TESTFN) - if not os.path.exists(fn): - self.fail("__import__ did not result in creation of " - "either a .pyc or .pyo file") - s = os.stat(fn) - self.assertEqual(stat.S_IMODE(s.st_mode), - stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) - finally: - del sys.path[0] - remove_files(TESTFN) - unload(TESTFN) - - def test_rewrite_pyc_with_read_only_source(self): - # Issue 6074: a long time ago on posix, and more recently on Windows, - # a read only source file resulted in a read only pyc file, which - # led to problems with updating it later - sys.path.insert(0, os.curdir) - fname = TESTFN + os.extsep + "py" - try: - # Write a Python file, make it read-only and import it - with open(fname, 'w') as f: - f.write("x = 'original'\n") - # Tweak the mtime of the source to ensure pyc gets updated later - s = os.stat(fname) - os.utime(fname, (s.st_atime, s.st_mtime-100000000)) - os.chmod(fname, 0o400) - m1 = __import__(TESTFN) - self.assertEqual(m1.x, 'original') - # Change the file and then reimport it - os.chmod(fname, 0o600) - with open(fname, 'w') as f: - f.write("x = 'rewritten'\n") - unload(TESTFN) - m2 = __import__(TESTFN) - self.assertEqual(m2.x, 'rewritten') - # Now delete the source file and check the pyc was rewritten - unlink(fname) - unload(TESTFN) - if __debug__: - bytecode_name = fname + "c" - else: - bytecode_name = fname + "o" - os.rename(imp.cache_from_source(fname), bytecode_name) - m3 = __import__(TESTFN) - self.assertEqual(m3.x, 'rewritten') - finally: - chmod_files(TESTFN) - remove_files(TESTFN) - unload(TESTFN) - del sys.path[0] - - def test_imp_module(self): - # Verify that the imp module can correctly load and find .py files - # XXX (ncoghlan): It would be nice to use support.CleanImport - # here, but that breaks because the os module registers some - # handlers in copy_reg on import. Since CleanImport doesn't - # revert that registration, the module is left in a broken - # state after reversion. Reinitialising the module contents - # and just reverting os.environ to its previous state is an OK - # workaround - orig_path = os.path - orig_getenv = os.getenv - with EnvironmentVarGuard(): - x = imp.find_module("os") - self.addCleanup(x[0].close) - new_os = imp.load_module("os", *x) - self.assertIs(os, new_os) - self.assertIs(orig_path, new_os.path) - self.assertIsNot(orig_getenv, new_os.getenv) - def test_bug7732(self): source = TESTFN + '.py' os.mkdir(source) @@ -220,6 +149,7 @@ class ImportTests(unittest.TestCase): # Need to be able to load from current dir. sys.path.append('') + importlib.invalidate_caches() try: make_legacy_pyc(filename) @@ -239,6 +169,7 @@ class ImportTests(unittest.TestCase): # New in 2.4, we shouldn't be able to import that no matter how often # we try. sys.path.insert(0, os.curdir) + importlib.invalidate_caches() if TESTFN in sys.modules: del sys.modules[TESTFN] try: @@ -312,6 +243,7 @@ class ImportTests(unittest.TestCase): os.remove(source) del sys.modules[TESTFN] make_legacy_pyc(source) + importlib.invalidate_caches() mod = __import__(TESTFN) base, ext = os.path.splitext(mod.__file__) self.assertIn(ext, ('.pyc', '.pyo')) @@ -332,12 +264,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() @@ -347,8 +273,6 @@ class ImportTests(unittest.TestCase): self.skipTest('path is not encodable to {}'.format(encoding)) with self.assertRaises(ImportError) as c: __import__(path) - self.assertEqual("Import by filename is not supported.", - c.exception.args[0]) def test_import_in_del_does_not_crash(self): # Issue 4236 @@ -385,6 +309,98 @@ class ImportTests(unittest.TestCase): del sys.path[0] remove_files(TESTFN) + def test_bogus_fromlist(self): + try: + __import__('http', fromlist=['blah']) + except ImportError: + self.fail("fromlist must allow bogus names") + + +class FilePermissionTests(unittest.TestCase): + # tests for file mode on cached .pyc/.pyo files + + @unittest.skipUnless(os.name == 'posix', + "test meaningful only on posix systems") + def test_creation_mode(self): + mask = 0o022 + with temp_umask(mask), _ready_to_import() as (name, path): + cached_path = imp.cache_from_source(path) + module = __import__(name) + if not os.path.exists(cached_path): + self.fail("__import__ did not result in creation of " + "either a .pyc or .pyo file") + stat_info = os.stat(cached_path) + + # Check that the umask is respected, and the executable bits + # aren't set. + self.assertEqual(oct(stat.S_IMODE(stat_info.st_mode)), + oct(0o666 & ~mask)) + + @unittest.skipUnless(os.name == 'posix', + "test meaningful only on posix systems") + def test_cached_mode_issue_2051(self): + # permissions of .pyc should match those of .py, regardless of mask + mode = 0o600 + with temp_umask(0o022), _ready_to_import() as (name, path): + cached_path = imp.cache_from_source(path) + os.chmod(path, mode) + __import__(name) + if not os.path.exists(cached_path): + self.fail("__import__ did not result in creation of " + "either a .pyc or .pyo file") + stat_info = os.stat(cached_path) + + self.assertEqual(oct(stat.S_IMODE(stat_info.st_mode)), oct(mode)) + + @unittest.skipUnless(os.name == 'posix', + "test meaningful only on posix systems") + def test_cached_readonly(self): + mode = 0o400 + with temp_umask(0o022), _ready_to_import() as (name, path): + cached_path = imp.cache_from_source(path) + os.chmod(path, mode) + __import__(name) + if not os.path.exists(cached_path): + self.fail("__import__ did not result in creation of " + "either a .pyc or .pyo file") + stat_info = os.stat(cached_path) + + expected = mode | 0o200 # Account for fix for issue #6074 + self.assertEqual(oct(stat.S_IMODE(stat_info.st_mode)), oct(expected)) + + def test_pyc_always_writable(self): + # Initially read-only .pyc files on Windows used to cause problems + # with later updates, see issue #6074 for details + with _ready_to_import() as (name, path): + # Write a Python file, make it read-only and import it + with open(path, 'w') as f: + f.write("x = 'original'\n") + # Tweak the mtime of the source to ensure pyc gets updated later + s = os.stat(path) + os.utime(path, (s.st_atime, s.st_mtime-100000000)) + os.chmod(path, 0o400) + m = __import__(name) + self.assertEqual(m.x, 'original') + # Change the file and then reimport it + os.chmod(path, 0o600) + with open(path, 'w') as f: + f.write("x = 'rewritten'\n") + unload(name) + importlib.invalidate_caches() + m = __import__(name) + self.assertEqual(m.x, 'rewritten') + # Now delete the source file and check the pyc was rewritten + unlink(path) + unload(name) + importlib.invalidate_caches() + if __debug__: + bytecode_only = path + "c" + else: + bytecode_only = path + "o" + os.rename(imp.cache_from_source(path), bytecode_only) + m = __import__(name) + self.assertEqual(m.x, 'rewritten') + class PycRewritingTests(unittest.TestCase): # Test that the `co_filename` attribute on code objects always points @@ -412,6 +428,7 @@ func_filename = func.__code__.co_filename with open(self.file_name, "w") as f: f.write(self.module_source) sys.path.insert(0, self.dir_name) + importlib.invalidate_caches() def tearDown(self): sys.path[:] = self.sys_path @@ -451,6 +468,7 @@ func_filename = func.__code__.co_filename py_compile.compile(self.file_name, dfile=target) os.remove(self.file_name) pyc_file = make_legacy_pyc(self.file_name) + importlib.invalidate_caches() mod = self.import_module() self.assertEqual(mod.module_filename, pyc_file) self.assertEqual(mod.code_filename, target) @@ -459,7 +477,7 @@ func_filename = func.__code__.co_filename def test_foreign_code(self): py_compile.compile(self.file_name) with open(self.compiled_name, "rb") as f: - header = f.read(8) + header = f.read(12) code = marshal.load(f) constants = list(code.co_consts) foreign_code = test_main.__code__ @@ -501,9 +519,11 @@ class PathsTests(unittest.TestCase): unload("test_trailing_slash") # Regression test for http://bugs.python.org/issue3677. - def _test_UNC_path(self): - with open(os.path.join(self.path, 'test_trailing_slash.py'), 'w') as f: - f.write("testdata = 'test_trailing_slash'") + @unittest.skipUnless(sys.platform == 'win32', 'Windows-specific') + def test_UNC_path(self): + with open(os.path.join(self.path, 'test_unc_path.py'), 'w') as f: + f.write("testdata = 'test_unc_path'") + importlib.invalidate_caches() # Create the UNC path, like \\myhost\c$\foo\bar. path = os.path.abspath(self.path) import socket @@ -518,13 +538,15 @@ class PathsTests(unittest.TestCase): # See issue #15338 self.skipTest("cannot access administrative share %r" % (unc,)) raise - sys.path.append(path) - mod = __import__("test_trailing_slash") - self.assertEqual(mod.testdata, 'test_trailing_slash') - unload("test_trailing_slash") - - if sys.platform == "win32": - test_UNC_path = _test_UNC_path + sys.path.insert(0, unc) + try: + mod = __import__("test_unc_path") + except ImportError as e: + self.fail("could not import 'test_unc_path' from %r: %r" + % (unc, e)) + self.assertEqual(mod.testdata, 'test_unc_path') + self.assertTrue(mod.__file__.startswith(unc), mod.__file__) + unload("test_unc_path") class RelativeImportTests(unittest.TestCase): @@ -565,7 +587,7 @@ class RelativeImportTests(unittest.TestCase): # Check relative import fails with package set to a non-string ns = dict(__package__=object()) - self.assertRaises(ValueError, check_relative) + self.assertRaises(TypeError, check_relative) def test_absolute_import_without_future(self): # If explicit relative import syntax is used, then do not try @@ -613,6 +635,7 @@ class PycacheTests(unittest.TestCase): with open(self.source, 'w') as fp: print('# This is a test file written by test_import.py', file=fp) sys.path.insert(0, os.curdir) + importlib.invalidate_caches() def tearDown(self): assert sys.path[0] == os.curdir, 'Unexpected sys.path[0]' @@ -660,6 +683,7 @@ class PycacheTests(unittest.TestCase): pyc_file = make_legacy_pyc(self.source) os.remove(self.source) unload(TESTFN) + importlib.invalidate_caches() m = __import__(TESTFN) self.assertEqual(m.__file__, os.path.join(os.curdir, os.path.relpath(pyc_file))) @@ -680,6 +704,7 @@ class PycacheTests(unittest.TestCase): pyc_file = make_legacy_pyc(self.source) os.remove(self.source) unload(TESTFN) + importlib.invalidate_caches() m = __import__(TESTFN) self.assertEqual(m.__cached__, os.path.join(os.curdir, os.path.relpath(pyc_file))) @@ -688,6 +713,8 @@ class PycacheTests(unittest.TestCase): # Like test___cached__ but for packages. def cleanup(): rmtree('pep3147') + unload('pep3147.foo') + unload('pep3147') os.mkdir('pep3147') self.addCleanup(cleanup) # Touch the __init__.py @@ -695,8 +722,7 @@ class PycacheTests(unittest.TestCase): pass with open(os.path.join('pep3147', 'foo.py'), 'w'): pass - unload('pep3147.foo') - unload('pep3147') + importlib.invalidate_caches() m = __import__('pep3147.foo') init_pyc = imp.cache_from_source( os.path.join('pep3147', '__init__.py')) @@ -710,18 +736,20 @@ class PycacheTests(unittest.TestCase): # PEP 3147 pyc file. def cleanup(): rmtree('pep3147') + unload('pep3147.foo') + unload('pep3147') os.mkdir('pep3147') self.addCleanup(cleanup) - unload('pep3147.foo') - unload('pep3147') # Touch the __init__.py with open(os.path.join('pep3147', '__init__.py'), 'w'): pass with open(os.path.join('pep3147', 'foo.py'), 'w'): pass + importlib.invalidate_caches() m = __import__('pep3147.foo') unload('pep3147.foo') unload('pep3147') + importlib.invalidate_caches() m = __import__('pep3147.foo') init_pyc = imp.cache_from_source( os.path.join('pep3147', '__init__.py')) @@ -730,22 +758,256 @@ class PycacheTests(unittest.TestCase): self.assertEqual(sys.modules['pep3147.foo'].__cached__, os.path.join(os.curdir, foo_pyc)) + def test_recompute_pyc_same_second(self): + # Even when the source file doesn't change timestamp, a change in + # source size is enough to trigger recomputation of the pyc file. + __import__(TESTFN) + unload(TESTFN) + with open(self.source, 'a') as fp: + print("x = 5", file=fp) + m = __import__(TESTFN) + self.assertEqual(m.x, 5) + -class RelativeImportFromImportlibTests(test_relative_imports.RelativeImports): +class TestSymbolicallyLinkedPackage(unittest.TestCase): + package_name = 'sample' + tagged = package_name + '-tagged' def setUp(self): - self._importlib_util_flag = importlib_util.using___import__ - importlib_util.using___import__ = True + test.support.rmtree(self.tagged) + test.support.rmtree(self.package_name) + self.orig_sys_path = sys.path[:] + + # create a sample package; imagine you have a package with a tag and + # you want to symbolically link it from its untagged name. + os.mkdir(self.tagged) + self.addCleanup(test.support.rmtree, self.tagged) + init_file = os.path.join(self.tagged, '__init__.py') + test.support.create_empty_file(init_file) + assert os.path.exists(init_file) + + # now create a symlink to the tagged package + # sample -> sample-tagged + os.symlink(self.tagged, self.package_name, target_is_directory=True) + self.addCleanup(test.support.unlink, self.package_name) + importlib.invalidate_caches() + + self.assertEqual(os.path.isdir(self.package_name), True) + + assert os.path.isfile(os.path.join(self.package_name, '__init__.py')) def tearDown(self): - importlib_util.using___import__ = self._importlib_util_flag + sys.path[:] = self.orig_sys_path + + # regression test for issue6727 + @unittest.skipUnless( + not hasattr(sys, 'getwindowsversion') + or sys.getwindowsversion() >= (6, 0), + "Windows Vista or later required") + @test.support.skip_unless_symlink + def test_symlinked_dir_importable(self): + # make sure sample can only be imported from the current directory. + sys.path[:] = ['.'] + assert os.path.exists(self.package_name) + assert os.path.exists(os.path.join(self.package_name, '__init__.py')) + + # Try to import the package + importlib.import_module(self.package_name) + + +@cpython_only +class ImportlibBootstrapTests(unittest.TestCase): + # These tests check that importlib is bootstrapped. + + def test_frozen_importlib(self): + mod = sys.modules['_frozen_importlib'] + self.assertTrue(mod) + + def test_frozen_importlib_is_bootstrap(self): + from importlib import _bootstrap + mod = sys.modules['_frozen_importlib'] + self.assertIs(mod, _bootstrap) + self.assertEqual(mod.__name__, 'importlib._bootstrap') + self.assertEqual(mod.__package__, 'importlib') + self.assertTrue(mod.__file__.endswith('_bootstrap.py'), mod.__file__) + + def test_there_can_be_only_one(self): + # Issue #15386 revealed a tricky loophole in the bootstrapping + # This test is technically redundant, since the bug caused importing + # this test module to crash completely, but it helps prove the point + from importlib import machinery + mod = sys.modules['_frozen_importlib'] + self.assertIs(machinery.FileFinder, mod.FileFinder) + self.assertIs(imp.new_module, mod.new_module) + + +class ImportTracebackTests(unittest.TestCase): + + def setUp(self): + os.mkdir(TESTFN) + self.old_path = sys.path[:] + sys.path.insert(0, TESTFN) + + def tearDown(self): + sys.path[:] = self.old_path + rmtree(TESTFN) + + def create_module(self, mod, contents, ext=".py"): + fname = os.path.join(TESTFN, mod + ext) + with open(fname, "w") as f: + f.write(contents) + self.addCleanup(unload, mod) + importlib.invalidate_caches() + return fname + + def assert_traceback(self, tb, files): + deduped_files = [] + while tb: + code = tb.tb_frame.f_code + fn = code.co_filename + if not deduped_files or fn != deduped_files[-1]: + deduped_files.append(fn) + tb = tb.tb_next + self.assertEqual(len(deduped_files), len(files), deduped_files) + for fn, pat in zip(deduped_files, files): + self.assertIn(pat, fn) + + def test_nonexistent_module(self): + try: + # assertRaises() clears __traceback__ + import nonexistent_xyzzy + except ImportError as e: + tb = e.__traceback__ + else: + self.fail("ImportError should have been raised") + self.assert_traceback(tb, [__file__]) + + def test_nonexistent_module_nested(self): + self.create_module("foo", "import nonexistent_xyzzy") + try: + import foo + except ImportError as e: + tb = e.__traceback__ + else: + self.fail("ImportError should have been raised") + self.assert_traceback(tb, [__file__, 'foo.py']) + + def test_exec_failure(self): + self.create_module("foo", "1/0") + try: + import foo + except ZeroDivisionError as e: + tb = e.__traceback__ + else: + self.fail("ZeroDivisionError should have been raised") + self.assert_traceback(tb, [__file__, 'foo.py']) + + def test_exec_failure_nested(self): + self.create_module("foo", "import bar") + self.create_module("bar", "1/0") + try: + import foo + except ZeroDivisionError as e: + tb = e.__traceback__ + else: + self.fail("ZeroDivisionError should have been raised") + self.assert_traceback(tb, [__file__, 'foo.py', 'bar.py']) + + # A few more examples from issue #15425 + def test_syntax_error(self): + self.create_module("foo", "invalid syntax is invalid") + try: + import foo + except SyntaxError as e: + tb = e.__traceback__ + else: + self.fail("SyntaxError should have been raised") + self.assert_traceback(tb, [__file__]) + + def _setup_broken_package(self, parent, child): + pkg_name = "_parent_foo" + self.addCleanup(unload, pkg_name) + pkg_path = os.path.join(TESTFN, pkg_name) + os.mkdir(pkg_path) + # Touch the __init__.py + init_path = os.path.join(pkg_path, '__init__.py') + with open(init_path, 'w') as f: + f.write(parent) + bar_path = os.path.join(pkg_path, 'bar.py') + with open(bar_path, 'w') as f: + f.write(child) + importlib.invalidate_caches() + return init_path, bar_path + + def test_broken_submodule(self): + init_path, bar_path = self._setup_broken_package("", "1/0") + try: + import _parent_foo.bar + except ZeroDivisionError as e: + tb = e.__traceback__ + else: + self.fail("ZeroDivisionError should have been raised") + self.assert_traceback(tb, [__file__, bar_path]) + + def test_broken_from(self): + init_path, bar_path = self._setup_broken_package("", "1/0") + try: + from _parent_foo import bar + except ZeroDivisionError as e: + tb = e.__traceback__ + else: + self.fail("ImportError should have been raised") + self.assert_traceback(tb, [__file__, bar_path]) + + def test_broken_parent(self): + init_path, bar_path = self._setup_broken_package("1/0", "") + try: + import _parent_foo.bar + except ZeroDivisionError as e: + tb = e.__traceback__ + else: + self.fail("ZeroDivisionError should have been raised") + self.assert_traceback(tb, [__file__, init_path]) + + def test_broken_parent_from(self): + init_path, bar_path = self._setup_broken_package("1/0", "") + try: + from _parent_foo import bar + except ZeroDivisionError as e: + tb = e.__traceback__ + else: + self.fail("ZeroDivisionError should have been raised") + self.assert_traceback(tb, [__file__, init_path]) + + @cpython_only + def test_import_bug(self): + # We simulate a bug in importlib and check that it's not stripped + # away from the traceback. + self.create_module("foo", "") + importlib = sys.modules['_frozen_importlib'] + old_load_module = importlib.SourceLoader.load_module + try: + def load_module(*args): + 1/0 + importlib.SourceLoader.load_module = load_module + try: + import foo + except ZeroDivisionError as e: + tb = e.__traceback__ + else: + self.fail("ZeroDivisionError should have been raised") + self.assert_traceback(tb, [__file__, '<frozen importlib', __file__]) + finally: + importlib.SourceLoader.load_module = old_load_module def test_main(verbose=None): - run_unittest(ImportTests, PycacheTests, + run_unittest(ImportTests, PycacheTests, FilePermissionTests, PycRewritingTests, PathsTests, RelativeImportTests, OverridingImportBuiltinTests, - RelativeImportFromImportlibTests) + ImportlibBootstrapTests, + TestSymbolicallyLinkedPackage, + ImportTracebackTests) if __name__ == '__main__': |