mirror of https://github.com/python/cpython
312 lines
10 KiB
Python
312 lines
10 KiB
Python
import contextlib
|
|
import _imp
|
|
import importlib
|
|
import importlib.util
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import unittest
|
|
import warnings
|
|
|
|
from .os_helper import unlink, temp_dir
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _ignore_deprecated_imports(ignore=True):
|
|
"""Context manager to suppress package and module deprecation
|
|
warnings when importing them.
|
|
|
|
If ignore is False, this context manager has no effect.
|
|
"""
|
|
if ignore:
|
|
with warnings.catch_warnings():
|
|
warnings.filterwarnings("ignore", ".+ (module|package)",
|
|
DeprecationWarning)
|
|
yield
|
|
else:
|
|
yield
|
|
|
|
|
|
def unload(name):
|
|
try:
|
|
del sys.modules[name]
|
|
except KeyError:
|
|
pass
|
|
|
|
|
|
def forget(modname):
|
|
"""'Forget' a module was ever imported.
|
|
|
|
This removes the module from sys.modules and deletes any PEP 3147/488 or
|
|
legacy .pyc files.
|
|
"""
|
|
unload(modname)
|
|
for dirname in sys.path:
|
|
source = os.path.join(dirname, modname + '.py')
|
|
# It doesn't matter if they exist or not, unlink all possible
|
|
# combinations of PEP 3147/488 and legacy pyc files.
|
|
unlink(source + 'c')
|
|
for opt in ('', 1, 2):
|
|
unlink(importlib.util.cache_from_source(source, optimization=opt))
|
|
|
|
|
|
def make_legacy_pyc(source):
|
|
"""Move a PEP 3147/488 pyc file to its legacy pyc location.
|
|
|
|
:param source: The file system path to the source file. The source file
|
|
does not need to exist, however the PEP 3147/488 pyc file must exist.
|
|
:return: The file system path to the legacy pyc file.
|
|
"""
|
|
pyc_file = importlib.util.cache_from_source(source)
|
|
assert source.endswith('.py')
|
|
legacy_pyc = source + 'c'
|
|
shutil.move(pyc_file, legacy_pyc)
|
|
return legacy_pyc
|
|
|
|
|
|
def import_module(name, deprecated=False, *, required_on=()):
|
|
"""Import and return the module to be tested, raising SkipTest if
|
|
it is not available.
|
|
|
|
If deprecated is True, any module or package deprecation messages
|
|
will be suppressed. If a module is required on a platform but optional for
|
|
others, set required_on to an iterable of platform prefixes which will be
|
|
compared against sys.platform.
|
|
"""
|
|
with _ignore_deprecated_imports(deprecated):
|
|
try:
|
|
return importlib.import_module(name)
|
|
except ImportError as msg:
|
|
if sys.platform.startswith(tuple(required_on)):
|
|
raise
|
|
raise unittest.SkipTest(str(msg))
|
|
|
|
|
|
def _save_and_remove_modules(names):
|
|
orig_modules = {}
|
|
prefixes = tuple(name + '.' for name in names)
|
|
for modname in list(sys.modules):
|
|
if modname in names or modname.startswith(prefixes):
|
|
orig_modules[modname] = sys.modules.pop(modname)
|
|
return orig_modules
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def frozen_modules(enabled=True):
|
|
"""Force frozen modules to be used (or not).
|
|
|
|
This only applies to modules that haven't been imported yet.
|
|
Also, some essential modules will always be imported frozen.
|
|
"""
|
|
_imp._override_frozen_modules_for_tests(1 if enabled else -1)
|
|
try:
|
|
yield
|
|
finally:
|
|
_imp._override_frozen_modules_for_tests(0)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def multi_interp_extensions_check(enabled=True):
|
|
"""Force legacy modules to be allowed in subinterpreters (or not).
|
|
|
|
("legacy" == single-phase init)
|
|
|
|
This only applies to modules that haven't been imported yet.
|
|
It overrides the PyInterpreterConfig.check_multi_interp_extensions
|
|
setting (see support.run_in_subinterp_with_config() and
|
|
_interpreters.create()).
|
|
|
|
Also see importlib.utils.allowing_all_extensions().
|
|
"""
|
|
old = _imp._override_multi_interp_extensions_check(1 if enabled else -1)
|
|
try:
|
|
yield
|
|
finally:
|
|
_imp._override_multi_interp_extensions_check(old)
|
|
|
|
|
|
def import_fresh_module(name, fresh=(), blocked=(), *,
|
|
deprecated=False,
|
|
usefrozen=False,
|
|
):
|
|
"""Import and return a module, deliberately bypassing sys.modules.
|
|
|
|
This function imports and returns a fresh copy of the named Python module
|
|
by removing the named module from sys.modules before doing the import.
|
|
Note that unlike reload, the original module is not affected by
|
|
this operation.
|
|
|
|
*fresh* is an iterable of additional module names that are also removed
|
|
from the sys.modules cache before doing the import. If one of these
|
|
modules can't be imported, None is returned.
|
|
|
|
*blocked* is an iterable of module names that are replaced with None
|
|
in the module cache during the import to ensure that attempts to import
|
|
them raise ImportError.
|
|
|
|
The named module and any modules named in the *fresh* and *blocked*
|
|
parameters are saved before starting the import and then reinserted into
|
|
sys.modules when the fresh import is complete.
|
|
|
|
Module and package deprecation messages are suppressed during this import
|
|
if *deprecated* is True.
|
|
|
|
This function will raise ImportError if the named module cannot be
|
|
imported.
|
|
|
|
If "usefrozen" is False (the default) then the frozen importer is
|
|
disabled (except for essential modules like importlib._bootstrap).
|
|
"""
|
|
# NOTE: test_heapq, test_json and test_warnings include extra sanity checks
|
|
# to make sure that this utility function is working as expected
|
|
with _ignore_deprecated_imports(deprecated):
|
|
# Keep track of modules saved for later restoration as well
|
|
# as those which just need a blocking entry removed
|
|
fresh = list(fresh)
|
|
blocked = list(blocked)
|
|
names = {name, *fresh, *blocked}
|
|
orig_modules = _save_and_remove_modules(names)
|
|
for modname in blocked:
|
|
sys.modules[modname] = None
|
|
|
|
try:
|
|
with frozen_modules(usefrozen):
|
|
# Return None when one of the "fresh" modules can not be imported.
|
|
try:
|
|
for modname in fresh:
|
|
__import__(modname)
|
|
except ImportError:
|
|
return None
|
|
return importlib.import_module(name)
|
|
finally:
|
|
_save_and_remove_modules(names)
|
|
sys.modules.update(orig_modules)
|
|
|
|
|
|
class CleanImport(object):
|
|
"""Context manager to force import to return a new module reference.
|
|
|
|
This is useful for testing module-level behaviours, such as
|
|
the emission of a DeprecationWarning on import.
|
|
|
|
Use like this:
|
|
|
|
with CleanImport("foo"):
|
|
importlib.import_module("foo") # new reference
|
|
|
|
If "usefrozen" is False (the default) then the frozen importer is
|
|
disabled (except for essential modules like importlib._bootstrap).
|
|
"""
|
|
|
|
def __init__(self, *module_names, usefrozen=False):
|
|
self.original_modules = sys.modules.copy()
|
|
for module_name in module_names:
|
|
if module_name in sys.modules:
|
|
module = sys.modules[module_name]
|
|
# It is possible that module_name is just an alias for
|
|
# another module (e.g. stub for modules renamed in 3.x).
|
|
# In that case, we also need delete the real module to clear
|
|
# the import cache.
|
|
if module.__name__ != module_name:
|
|
del sys.modules[module.__name__]
|
|
del sys.modules[module_name]
|
|
self._frozen_modules = frozen_modules(usefrozen)
|
|
|
|
def __enter__(self):
|
|
self._frozen_modules.__enter__()
|
|
return self
|
|
|
|
def __exit__(self, *ignore_exc):
|
|
sys.modules.update(self.original_modules)
|
|
self._frozen_modules.__exit__(*ignore_exc)
|
|
|
|
|
|
class DirsOnSysPath(object):
|
|
"""Context manager to temporarily add directories to sys.path.
|
|
|
|
This makes a copy of sys.path, appends any directories given
|
|
as positional arguments, then reverts sys.path to the copied
|
|
settings when the context ends.
|
|
|
|
Note that *all* sys.path modifications in the body of the
|
|
context manager, including replacement of the object,
|
|
will be reverted at the end of the block.
|
|
"""
|
|
|
|
def __init__(self, *paths):
|
|
self.original_value = sys.path[:]
|
|
self.original_object = sys.path
|
|
sys.path.extend(paths)
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *ignore_exc):
|
|
sys.path = self.original_object
|
|
sys.path[:] = self.original_value
|
|
|
|
|
|
def modules_setup():
|
|
return sys.modules.copy(),
|
|
|
|
|
|
def modules_cleanup(oldmodules):
|
|
# Encoders/decoders are registered permanently within the internal
|
|
# codec cache. If we destroy the corresponding modules their
|
|
# globals will be set to None which will trip up the cached functions.
|
|
encodings = [(k, v) for k, v in sys.modules.items()
|
|
if k.startswith('encodings.')]
|
|
sys.modules.clear()
|
|
sys.modules.update(encodings)
|
|
# XXX: This kind of problem can affect more than just encodings.
|
|
# In particular extension modules (such as _ssl) don't cope
|
|
# with reloading properly. Really, test modules should be cleaning
|
|
# out the test specific modules they know they added (ala test_runpy)
|
|
# rather than relying on this function (as test_importhooks and test_pkg
|
|
# do currently). Implicitly imported *real* modules should be left alone
|
|
# (see issue 10556).
|
|
sys.modules.update(oldmodules)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def isolated_modules():
|
|
"""
|
|
Save modules on entry and cleanup on exit.
|
|
"""
|
|
(saved,) = modules_setup()
|
|
try:
|
|
yield
|
|
finally:
|
|
modules_cleanup(saved)
|
|
|
|
|
|
def mock_register_at_fork(func):
|
|
# bpo-30599: Mock os.register_at_fork() when importing the random module,
|
|
# since this function doesn't allow to unregister callbacks and would leak
|
|
# memory.
|
|
from unittest import mock
|
|
return mock.patch('os.register_at_fork', create=True)(func)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def ready_to_import(name=None, source=""):
|
|
from test.support import script_helper
|
|
|
|
# 1. Sets up a temporary directory and removes it afterwards
|
|
# 2. Creates the module file
|
|
# 3. Temporarily clears the module from sys.modules (if any)
|
|
# 4. Reverts or removes the module when cleaning up
|
|
name = name or "spam"
|
|
with 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
|
|
else:
|
|
sys.modules.pop(name, None)
|