diff --git a/Lib/importlib/NOTES b/Lib/importlib/NOTES index 02eecf9fab1..8351f1e22d1 100644 --- a/Lib/importlib/NOTES +++ b/Lib/importlib/NOTES @@ -3,9 +3,6 @@ to do * Reorganize support code. - + Separate general support code and importer-specific (e.g. source) support - code. - - Create support modules for each subdirectory (as needed). + Add a file loader mock that returns monotonically increasing mtime. - Use in source/test_reload. - Use in source/test_load_module_mixed. diff --git a/Lib/importlib/test/source/test_case_sensitivity.py b/Lib/importlib/test/source/test_case_sensitivity.py index 6649f377e19..955d6ec4e17 100644 --- a/Lib/importlib/test/source/test_case_sensitivity.py +++ b/Lib/importlib/test/source/test_case_sensitivity.py @@ -1,6 +1,7 @@ """Test case-sensitivity (PEP 235).""" import importlib from .. import support +from . import util as source_util import os import sys from test import support as test_support @@ -25,7 +26,8 @@ class CaseSensitivityTest(unittest.TestCase): """Look for a module with matching and non-matching sensitivity.""" sensitive_pkg = 'sensitive.{0}'.format(self.name) insensitive_pkg = 'insensitive.{0}'.format(self.name.lower()) - with support.create_modules(insensitive_pkg, sensitive_pkg) as mapping: + context = source_util.create_modules(insensitive_pkg, sensitive_pkg) + with context as mapping: sensitive_path = os.path.join(mapping['.root'], 'sensitive') insensitive_path = os.path.join(mapping['.root'], 'insensitive') return self.find(sensitive_path), self.find(insensitive_path) diff --git a/Lib/importlib/test/source/test_finder.py b/Lib/importlib/test/source/test_finder.py index 63cb4363a1f..a84d9140713 100644 --- a/Lib/importlib/test/source/test_finder.py +++ b/Lib/importlib/test/source/test_finder.py @@ -1,6 +1,7 @@ import importlib from .. import abc from .. import support +from . import util as source_util import os import py_compile import unittest @@ -45,7 +46,7 @@ class FinderTests(abc.FinderTests): """ if create is None: create = {test} - with support.create_modules(*create) as mapping: + with source_util.create_modules(*create) as mapping: if compile_: for name in compile_: py_compile.compile(mapping[name]) @@ -76,14 +77,14 @@ class FinderTests(abc.FinderTests): # [sub module] def test_module_in_package(self): - with support.create_modules('pkg.__init__', 'pkg.sub') as mapping: + with source_util.create_modules('pkg.__init__', 'pkg.sub') as mapping: pkg_dir = os.path.dirname(mapping['pkg.__init__']) loader = self.import_(pkg_dir, 'pkg.sub') self.assert_(hasattr(loader, 'load_module')) # [sub package] def test_package_in_package(self): - context = support.create_modules('pkg.__init__', 'pkg.sub.__init__') + context = source_util.create_modules('pkg.__init__', 'pkg.sub.__init__') with context as mapping: pkg_dir = os.path.dirname(mapping['pkg.__init__']) loader = self.import_(pkg_dir, 'pkg.sub') @@ -91,7 +92,7 @@ class FinderTests(abc.FinderTests): # [sub empty] def test_empty_sub_directory(self): - context = support.create_modules('pkg.__init__', 'pkg.sub.__init__') + context = source_util.create_modules('pkg.__init__', 'pkg.sub.__init__') with warnings.catch_warnings(): warnings.simplefilter("error", ImportWarning) with context as mapping: @@ -109,7 +110,7 @@ class FinderTests(abc.FinderTests): def test_failure(self): - with support.create_modules('blah') as mapping: + with source_util.create_modules('blah') as mapping: nothing = self.import_(mapping['.root'], 'sdfsadsadf') self.assert_(nothing is None) diff --git a/Lib/importlib/test/source/test_loader.py b/Lib/importlib/test/source/test_loader.py index c59dd2c174b..67930fcb3fd 100644 --- a/Lib/importlib/test/source/test_loader.py +++ b/Lib/importlib/test/source/test_loader.py @@ -1,6 +1,7 @@ import importlib from .. import abc from .. import support +from . import util as source_util import imp import os @@ -18,7 +19,7 @@ class SimpleTest(unittest.TestCase): # [basic] def test_module(self): - with support.create_modules('_temp') as mapping: + with source_util.create_modules('_temp') as mapping: loader = importlib._PyFileLoader('_temp', mapping['_temp'], False) module = loader.load_module('_temp') self.assert_('_temp' in sys.modules) @@ -28,7 +29,7 @@ class SimpleTest(unittest.TestCase): self.assertEqual(getattr(module, attr), value) def test_package(self): - with support.create_modules('_pkg.__init__') as mapping: + with source_util.create_modules('_pkg.__init__') as mapping: loader = importlib._PyFileLoader('_pkg', mapping['_pkg.__init__'], True) module = loader.load_module('_pkg') @@ -41,7 +42,7 @@ class SimpleTest(unittest.TestCase): def test_lacking_parent(self): - with support.create_modules('_pkg.__init__', '_pkg.mod')as mapping: + with source_util.create_modules('_pkg.__init__', '_pkg.mod')as mapping: loader = importlib._PyFileLoader('_pkg.mod', mapping['_pkg.mod'], False) module = loader.load_module('_pkg.mod') @@ -56,7 +57,7 @@ class SimpleTest(unittest.TestCase): return lambda name: fxn(name) + 1 def test_module_reuse(self): - with support.create_modules('_temp') as mapping: + with source_util.create_modules('_temp') as mapping: loader = importlib._PyFileLoader('_temp', mapping['_temp'], False) module = loader.load_module('_temp') module_id = id(module) @@ -81,7 +82,7 @@ class SimpleTest(unittest.TestCase): attributes = ('__file__', '__path__', '__package__') value = '' name = '_temp' - with support.create_modules(name) as mapping: + with source_util.create_modules(name) as mapping: orig_module = imp.new_module(name) for attr in attributes: setattr(orig_module, attr, value) @@ -94,7 +95,7 @@ class SimpleTest(unittest.TestCase): # [syntax error] def test_bad_syntax(self): - with support.create_modules('_temp') as mapping: + with source_util.create_modules('_temp') as mapping: with open(mapping['_temp'], 'w') as file: file.write('=') loader = importlib._PyFileLoader('_temp', mapping['_temp'], False) @@ -109,12 +110,12 @@ class DontWriteBytecodeTest(unittest.TestCase): def tearDown(self): sys.dont_write_bytecode = False - @support.writes_bytecode + @source_util.writes_bytecode def run_test(self, assertion): - with support.create_modules('_temp') as mapping: + with source_util.create_modules('_temp') as mapping: loader = importlib._PyFileLoader('_temp', mapping['_temp'], False) loader.load_module('_temp') - bytecode_path = support.bytecode_path(mapping['_temp']) + bytecode_path = source_util.bytecode_path(mapping['_temp']) assertion(bytecode_path) def test_bytecode_written(self): @@ -137,10 +138,10 @@ class BadDataTest(unittest.TestCase): # [bad magic] def test_bad_magic(self): - with support.create_modules('_temp') as mapping: + with source_util.create_modules('_temp') as mapping: py_compile.compile(mapping['_temp']) os.unlink(mapping['_temp']) - bytecode_path = support.bytecode_path(mapping['_temp']) + bytecode_path = source_util.bytecode_path(mapping['_temp']) with open(bytecode_path, 'r+b') as file: file.seek(0) file.write(b'\x00\x00\x00\x00') @@ -164,7 +165,7 @@ class SourceBytecodeInteraction(unittest.TestCase): def run_test(self, test, *create, pkg=False): create += (test,) - with support.create_modules(*create) as mapping: + with source_util.create_modules(*create) as mapping: for name in create: py_compile.compile(mapping[name]) if pkg: @@ -217,11 +218,11 @@ class BadBytecodeTest(unittest.TestCase): self.assert_(module_name in sys.modules) # [bad magic] - @support.writes_bytecode + @source_util.writes_bytecode def test_bad_magic(self): - with support.create_modules('_temp') as mapping: + with source_util.create_modules('_temp') as mapping: py_compile.compile(mapping['_temp']) - bytecode_path = support.bytecode_path(mapping['_temp']) + bytecode_path = source_util.bytecode_path(mapping['_temp']) with open(bytecode_path, 'r+b') as bytecode_file: bytecode_file.seek(0) bytecode_file.write(b'\x00\x00\x00\x00') @@ -230,12 +231,12 @@ class BadBytecodeTest(unittest.TestCase): self.assertEqual(bytecode_file.read(4), imp.get_magic()) # [bad timestamp] - @support.writes_bytecode + @source_util.writes_bytecode def test_bad_bytecode(self): zeros = b'\x00\x00\x00\x00' - with support.create_modules('_temp') as mapping: + with source_util.create_modules('_temp') as mapping: py_compile.compile(mapping['_temp']) - bytecode_path = support.bytecode_path(mapping['_temp']) + bytecode_path = source_util.bytecode_path(mapping['_temp']) with open(bytecode_path, 'r+b') as bytecode_file: bytecode_file.seek(4) bytecode_file.write(zeros) @@ -248,8 +249,8 @@ class BadBytecodeTest(unittest.TestCase): # [bad marshal] def test_bad_marshal(self): - with support.create_modules('_temp') as mapping: - bytecode_path = support.bytecode_path(mapping['_temp']) + with source_util.create_modules('_temp') as mapping: + bytecode_path = source_util.bytecode_path(mapping['_temp']) source_mtime = os.path.getmtime(mapping['_temp']) source_timestamp = importlib._w_long(source_mtime) with open(bytecode_path, 'wb') as bytecode_file: diff --git a/Lib/importlib/test/source/test_path_hook.py b/Lib/importlib/test/source/test_path_hook.py index ef410779c01..5fa3fd909cc 100644 --- a/Lib/importlib/test/source/test_path_hook.py +++ b/Lib/importlib/test/source/test_path_hook.py @@ -1,5 +1,5 @@ import importlib -from .. import support +from . import util import unittest @@ -9,7 +9,7 @@ class PathHookTest(unittest.TestCase): def test_success(self): # XXX Only work on existing directories? - with support.create_modules('dummy') as mapping: + with util.create_modules('dummy') as mapping: self.assert_(hasattr(importlib.FileImporter(mapping['.root']), 'find_module')) diff --git a/Lib/importlib/test/source/test_source_encoding.py b/Lib/importlib/test/source/test_source_encoding.py index 97096df3981..7c9a57b580f 100644 --- a/Lib/importlib/test/source/test_source_encoding.py +++ b/Lib/importlib/test/source/test_source_encoding.py @@ -1,5 +1,6 @@ import importlib from .. import support +from . import util as source_util import codecs import re @@ -32,7 +33,7 @@ class EncodingTest(unittest.TestCase): module_name = '_temp' def run_test(self, source): - with support.create_modules(self.module_name) as mapping: + with source_util.create_modules(self.module_name) as mapping: with open(mapping[self.module_name], 'wb')as file: file.write(source) loader = importlib._PyFileLoader(self.module_name, @@ -93,7 +94,7 @@ class LineEndingTest(unittest.TestCase): module_name = '_temp' source_lines = [b"a = 42", b"b = -13", b''] source = line_ending.join(source_lines) - with support.create_modules(module_name) as mapping: + with source_util.create_modules(module_name) as mapping: with open(mapping[module_name], 'wb') as file: file.write(source) loader = importlib._PyFileLoader(module_name, mapping[module_name], diff --git a/Lib/importlib/test/source/util.py b/Lib/importlib/test/source/util.py new file mode 100644 index 00000000000..5400c82e77b --- /dev/null +++ b/Lib/importlib/test/source/util.py @@ -0,0 +1,88 @@ +from .. import support as util +import contextlib +import imp +import os +import os.path +import sys +import tempfile +from test import support as support + + +def writes_bytecode(fxn): + """Decorator that returns the function if writing bytecode is enabled, else + a stub function that accepts anything and simply returns None.""" + if sys.dont_write_bytecode: + return lambda *args, **kwargs: None + else: + return fxn + + +def bytecode_path(source_path): + for suffix, _, type_ in imp.get_suffixes(): + if type_ == imp.PY_COMPILED: + bc_suffix = suffix + break + else: + raise ValueError("no bytecode suffix is defined") + return os.path.splitext(source_path)[0] + bc_suffix + + +@contextlib.contextmanager +def create_modules(*names): + """Temporarily create each named module with an attribute (named 'attr') + that contains the name passed into the context manager that caused the + creation of the module. + + All files are created in a temporary directory specified by + tempfile.gettempdir(). This directory is inserted at the beginning of + sys.path. When the context manager exits all created files (source and + bytecode) are explicitly deleted. + + No magic is performed when creating packages! This means that if you create + a module within a package you must also create the package's __init__ as + well. + + """ + source = 'attr = {0!r}' + created_paths = [] + mapping = {} + try: + temp_dir = tempfile.gettempdir() + mapping['.root'] = temp_dir + import_names = set() + for name in names: + if not name.endswith('__init__'): + import_name = name + else: + import_name = name[:-len('.__init__')] + import_names.add(import_name) + if import_name in sys.modules: + del sys.modules[import_name] + name_parts = name.split('.') + file_path = temp_dir + for directory in name_parts[:-1]: + file_path = os.path.join(file_path, directory) + if not os.path.exists(file_path): + os.mkdir(file_path) + created_paths.append(file_path) + file_path = os.path.join(file_path, name_parts[-1] + '.py') + with open(file_path, 'w') as file: + file.write(source.format(name)) + created_paths.append(file_path) + mapping[name] = file_path + uncache_manager = util.uncache(*import_names) + uncache_manager.__enter__() + state_manager = util.import_state(path=[temp_dir]) + state_manager.__enter__() + yield mapping + finally: + state_manager.__exit__(None, None, None) + uncache_manager.__exit__(None, None, None) + # Reverse the order for path removal to unroll directory creation. + for path in reversed(created_paths): + if file_path.endswith('.py'): + support.unlink(path) + support.unlink(path + 'c') + support.unlink(path + 'o') + else: + os.rmdir(path) diff --git a/Lib/importlib/test/support.py b/Lib/importlib/test/support.py index 3097811093d..1518d74b71e 100644 --- a/Lib/importlib/test/support.py +++ b/Lib/importlib/test/support.py @@ -6,7 +6,6 @@ import imp import os.path from test.support import unlink import sys -from tempfile import gettempdir using___import__ = False @@ -28,14 +27,6 @@ def importlib_only(fxn): update_wrapper(inner, fxn) return inner -def writes_bytecode(fxn): - """Decorator that returns the function if writing bytecode is enabled, else - a stub function that accepts anything and simply returns None.""" - if sys.dont_write_bytecode: - return lambda *args, **kwargs: None - else: - return fxn - def case_insensitive_tests(class_): """Class decorator that nullifies tests that require a case-insensitive @@ -102,67 +93,6 @@ def import_state(**kwargs): setattr(sys, attr, value) -@contextmanager -def create_modules(*names): - """Temporarily create each named module with an attribute (named 'attr') - that contains the name passed into the context manager that caused the - creation of the module. - - All files are created in a temporary directory specified by - tempfile.gettempdir(). This directory is inserted at the beginning of - sys.path. When the context manager exits all created files (source and - bytecode) are explicitly deleted. - - No magic is performed when creating packages! This means that if you create - a module within a package you must also create the package's __init__ as - well. - - """ - source = 'attr = {0!r}' - created_paths = [] - mapping = {} - try: - temp_dir = gettempdir() - mapping['.root'] = temp_dir - import_names = set() - for name in names: - if not name.endswith('__init__'): - import_name = name - else: - import_name = name[:-len('.__init__')] - import_names.add(import_name) - if import_name in sys.modules: - del sys.modules[import_name] - name_parts = name.split('.') - file_path = temp_dir - for directory in name_parts[:-1]: - file_path = os.path.join(file_path, directory) - if not os.path.exists(file_path): - os.mkdir(file_path) - created_paths.append(file_path) - file_path = os.path.join(file_path, name_parts[-1] + '.py') - with open(file_path, 'w') as file: - file.write(source.format(name)) - created_paths.append(file_path) - mapping[name] = file_path - uncache_manager = uncache(*import_names) - uncache_manager.__enter__() - state_manager = import_state(path=[temp_dir]) - state_manager.__enter__() - yield mapping - finally: - state_manager.__exit__(None, None, None) - uncache_manager.__exit__(None, None, None) - # Reverse the order for path removal to unroll directory creation. - for path in reversed(created_paths): - if file_path.endswith('.py'): - unlink(path) - unlink(path + 'c') - unlink(path + 'o') - else: - os.rmdir(path) - - class mock_modules: """A mock importer/loader.""" @@ -221,13 +151,3 @@ def mock_path_hook(*entries, importer): raise ImportError return importer return hook - - -def bytecode_path(source_path): - for suffix, _, type_ in imp.get_suffixes(): - if type_ == imp.PY_COMPILED: - bc_suffix = suffix - break - else: - raise ValueError("no bytecode suffix is defined") - return os.path.splitext(source_path)[0] + bc_suffix