summaryrefslogtreecommitdiff
path: root/Lib/test/test_importlib/util.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/test/test_importlib/util.py')
-rw-r--r--Lib/test/test_importlib/util.py185
1 files changed, 170 insertions, 15 deletions
diff --git a/Lib/test/test_importlib/util.py b/Lib/test/test_importlib/util.py
index 885cec3b29..aa4cd7e861 100644
--- a/Lib/test/test_importlib/util.py
+++ b/Lib/test/test_importlib/util.py
@@ -1,31 +1,85 @@
-from contextlib import contextmanager
-from importlib import util, invalidate_caches
+import builtins
+import contextlib
+import errno
+import functools
+import importlib
+from importlib import machinery, util, invalidate_caches
+import os
import os.path
from test import support
import unittest
import sys
+import tempfile
import types
+BUILTINS = types.SimpleNamespace()
+BUILTINS.good_name = None
+BUILTINS.bad_name = None
+if 'errno' in sys.builtin_module_names:
+ BUILTINS.good_name = 'errno'
+if 'importlib' not in sys.builtin_module_names:
+ BUILTINS.bad_name = 'importlib'
+
+EXTENSIONS = types.SimpleNamespace()
+EXTENSIONS.path = None
+EXTENSIONS.ext = None
+EXTENSIONS.filename = None
+EXTENSIONS.file_path = None
+EXTENSIONS.name = '_testcapi'
+
+def _extension_details():
+ global EXTENSIONS
+ for path in sys.path:
+ for ext in machinery.EXTENSION_SUFFIXES:
+ filename = EXTENSIONS.name + ext
+ file_path = os.path.join(path, filename)
+ if os.path.exists(file_path):
+ EXTENSIONS.path = path
+ EXTENSIONS.ext = ext
+ EXTENSIONS.filename = filename
+ EXTENSIONS.file_path = file_path
+ return
+
+_extension_details()
+
+
def import_importlib(module_name):
"""Import a module from importlib both w/ and w/o _frozen_importlib."""
fresh = ('importlib',) if '.' in module_name else ()
frozen = support.import_fresh_module(module_name)
source = support.import_fresh_module(module_name, fresh=fresh,
blocked=('_frozen_importlib',))
+ return {'Frozen': frozen, 'Source': source}
+
+
+def specialize_class(cls, kind, base=None, **kwargs):
+ # XXX Support passing in submodule names--load (and cache) them?
+ # That would clean up the test modules a bit more.
+ if base is None:
+ base = unittest.TestCase
+ elif not isinstance(base, type):
+ base = base[kind]
+ name = '{}_{}'.format(kind, cls.__name__)
+ bases = (cls, base)
+ specialized = types.new_class(name, bases)
+ specialized.__module__ = cls.__module__
+ specialized._NAME = cls.__name__
+ specialized._KIND = kind
+ for attr, values in kwargs.items():
+ value = values[kind]
+ setattr(specialized, attr, value)
+ return specialized
+
+
+def split_frozen(cls, base=None, **kwargs):
+ frozen = specialize_class(cls, 'Frozen', base, **kwargs)
+ source = specialize_class(cls, 'Source', base, **kwargs)
return frozen, source
-def test_both(test_class, **kwargs):
- frozen_tests = types.new_class('Frozen_'+test_class.__name__,
- (test_class, unittest.TestCase))
- source_tests = types.new_class('Source_'+test_class.__name__,
- (test_class, unittest.TestCase))
- frozen_tests.__module__ = source_tests.__module__ = test_class.__module__
- for attr, (frozen_value, source_value) in kwargs.items():
- setattr(frozen_tests, attr, frozen_value)
- setattr(source_tests, attr, source_value)
- return frozen_tests, source_tests
+def test_both(test_class, base=None, **kwargs):
+ return split_frozen(test_class, base, **kwargs)
CASE_INSENSITIVE_FS = True
@@ -38,6 +92,10 @@ if sys.platform not in ('win32', 'cygwin'):
if not os.path.exists(changed_name):
CASE_INSENSITIVE_FS = False
+source_importlib = import_importlib('importlib')['Source']
+__import__ = {'Frozen': staticmethod(builtins.__import__),
+ 'Source': staticmethod(source_importlib.__import__)}
+
def case_insensitive_tests(test):
"""Class decorator that nullifies tests requiring a case-insensitive
@@ -53,7 +111,7 @@ def submodule(parent, name, pkg_dir, content=''):
return '{}.{}'.format(parent, name), path
-@contextmanager
+@contextlib.contextmanager
def uncache(*names):
"""Uncache a module from sys.modules.
@@ -79,7 +137,7 @@ def uncache(*names):
pass
-@contextmanager
+@contextlib.contextmanager
def temp_module(name, content='', *, pkg=False):
conflicts = [n for n in sys.modules if n.partition('.')[0] == name]
with support.temp_cwd(None) as cwd:
@@ -103,7 +161,7 @@ def temp_module(name, content='', *, pkg=False):
yield location
-@contextmanager
+@contextlib.contextmanager
def import_state(**kwargs):
"""Context manager to manage the various importers and stored state in the
sys module.
@@ -198,6 +256,7 @@ class mock_modules(_ImporterMock):
raise
return self.modules[fullname]
+
class mock_spec(_ImporterMock):
"""Importer mock using PEP 451 APIs."""
@@ -223,3 +282,99 @@ class mock_spec(_ImporterMock):
self.module_code[module.__spec__.name]()
except KeyError:
pass
+
+
+def writes_bytecode_files(fxn):
+ """Decorator to protect sys.dont_write_bytecode from mutation and to skip
+ tests that require it to be set to False."""
+ if sys.dont_write_bytecode:
+ return lambda *args, **kwargs: None
+ @functools.wraps(fxn)
+ def wrapper(*args, **kwargs):
+ original = sys.dont_write_bytecode
+ sys.dont_write_bytecode = False
+ try:
+ to_return = fxn(*args, **kwargs)
+ finally:
+ sys.dont_write_bytecode = original
+ return to_return
+ return wrapper
+
+
+def ensure_bytecode_path(bytecode_path):
+ """Ensure that the __pycache__ directory for PEP 3147 pyc file exists.
+
+ :param bytecode_path: File system path to PEP 3147 pyc file.
+ """
+ try:
+ os.mkdir(os.path.dirname(bytecode_path))
+ except OSError as error:
+ if error.errno != errno.EEXIST:
+ raise
+
+
+@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 returned by
+ tempfile.mkdtemp(). 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 = {}
+ state_manager = None
+ uncache_manager = None
+ try:
+ temp_dir = tempfile.mkdtemp()
+ 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:
+ if state_manager is not None:
+ state_manager.__exit__(None, None, None)
+ if uncache_manager is not None:
+ uncache_manager.__exit__(None, None, None)
+ support.rmtree(temp_dir)
+
+
+def mock_path_hook(*entries, importer):
+ """A mock sys.path_hooks entry."""
+ def hook(entry):
+ if entry not in entries:
+ raise ImportError
+ return importer
+ return hook