mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 07:31:38 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			311 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			311 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)
 | 
						|
    up_one = os.path.dirname(os.path.abspath(source))
 | 
						|
    legacy_pyc = os.path.join(up_one, 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)
 |