diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index af8936155..e9def9adb 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -79,9 +79,7 @@ __all__ = [ # exceptions "Error", "TestFailed", "TestDidNotRun", "ResourceDenied", # imports - "import_module", "import_fresh_module", "CleanImport", # modules - "unload", "forget", # io "record_original_stdout", "get_original_stdout", "captured_stdout", "captured_stdin", "captured_stderr", @@ -198,21 +196,6 @@ class ResourceDenied(unittest.SkipTest): and unexpected skips. """ -@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 ignore_warnings(*, category): """Decorator to suppress deprecation warnings. @@ -230,52 +213,6 @@ def ignore_warnings(*, category): return decorator -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_module(name, orig_modules): - """Helper function to save and remove a module from sys.modules - - Raise ImportError if the module can't be imported. - """ - # try to import the module and raise an error if it can't be imported - if name not in sys.modules: - __import__(name) - del sys.modules[name] - for modname in list(sys.modules): - if modname == name or modname.startswith(name + '.'): - orig_modules[modname] = sys.modules[modname] - del sys.modules[modname] - -def _save_and_block_module(name, orig_modules): - """Helper function to save and block a module in sys.modules - - Return True if the module was in sys.modules, False otherwise. - """ - saved = True - try: - orig_modules[name] = sys.modules[name] - except KeyError: - saved = False - sys.modules[name] = None - return saved - - def anticipate_failure(condition): """Decorator to mark a test that is known to be broken in some cases @@ -304,56 +241,6 @@ def load_package_tests(pkg_dir, loader, standard_tests, pattern): return standard_tests -def import_fresh_module(name, fresh=(), blocked=(), deprecated=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. - - *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. - """ - # 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 - orig_modules = {} - names_to_remove = [] - _save_and_remove_module(name, orig_modules) - try: - for fresh_name in fresh: - _save_and_remove_module(fresh_name, orig_modules) - for blocked_name in blocked: - if not _save_and_block_module(blocked_name, orig_modules): - names_to_remove.append(blocked_name) - fresh_module = importlib.import_module(name) - except ImportError: - fresh_module = None - finally: - for orig_name, module in orig_modules.items(): - sys.modules[orig_name] = module - for name_to_remove in names_to_remove: - del sys.modules[name_to_remove] - return fresh_module - - def get_attribute(obj, name): """Get an attribute, raising SkipTest if AttributeError is raised.""" try: @@ -382,12 +269,6 @@ def record_original_stdout(stdout): def get_original_stdout(): return _original_stdout or sys.stdout -def unload(name): - try: - del sys.modules[name] - except KeyError: - pass - def _force_run(path, func, *args): try: return func(*args) @@ -528,34 +409,6 @@ def rmtree(path): except FileNotFoundError: pass -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') - os.rename(pyc_file, legacy_pyc) - return legacy_pyc - -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)) - # Check whether a gui is actually available def _is_gui_available(): if hasattr(_is_gui_available, 'result'): @@ -1225,62 +1078,6 @@ def check_no_resource_warning(testcase): yield -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 - """ - - def __init__(self, *module_names): - 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] - - def __enter__(self): - return self - - def __exit__(self, *ignore_exc): - sys.modules.update(self.original_modules) - - -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 - class TransientResource(object): @@ -2024,25 +1821,6 @@ def print_warning(msg): # log warnings even if sys.stderr is captured temporarily by a test. print_warning.orig_stderr = sys.stderr -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) - #======================================================================= # Threading support to prevent reporting refleaks when running regrtest.py -R @@ -2075,8 +1853,7 @@ def threading_cleanup(*original_values): if values == original_values: break - if not count: - # Display a warning at the first iteration + if not count: # Display a warning at the first iteration environment_altered = True dangling_threads = values[1] print_warning(f"threading_cleanup() failed to cleanup " diff --git a/Lib/test/support/import_helper.py b/Lib/test/support/import_helper.py new file mode 100644 index 000000000..e2f1def9a --- /dev/null +++ b/Lib/test/support/import_helper.py @@ -0,0 +1,355 @@ +import contextlib +# TODO: RUSTPYTHON +# import _imp +import importlib +import importlib.util +import os +import sys +import unittest +import warnings + +from .os_helper import unlink + + +@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') + os.rename(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 + + +# XXX RUSTPYTHON: need _imp._override_frozen_modules_for_tests +# @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) + + +# XXX RUSTPYTHON: new implementation needs fronzen_modules +# 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) + + +# TODO RUSTPYTHON: old implementation +def _save_and_remove_module(name, orig_modules): + """Helper function to save and remove a module from sys.modules + Raise ImportError if the module can't be imported. + """ + # try to import the module and raise an error if it can't be imported + if name not in sys.modules: + __import__(name) + del sys.modules[name] + for modname in list(sys.modules): + if modname == name or modname.startswith(name + '.'): + orig_modules[modname] = sys.modules[modname] + del sys.modules[modname] + + +# TODO RUSTPYTHON: old implementation +def _save_and_block_module(name, orig_modules): + """Helper function to save and block a module in sys.modules + + Return True if the module was in sys.modules, False otherwise. + """ + saved = True + try: + orig_modules[name] = sys.modules[name] + except KeyError: + saved = False + sys.modules[name] = None + return saved + + +# TODO RUSTPYTHON: old implementation +def import_fresh_module(name, fresh=(), blocked=(), deprecated=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. + *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. + """ + # 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 + orig_modules = {} + names_to_remove = [] + _save_and_remove_module(name, orig_modules) + try: + for fresh_name in fresh: + _save_and_remove_module(fresh_name, orig_modules) + for blocked_name in blocked: + if not _save_and_block_module(blocked_name, orig_modules): + names_to_remove.append(blocked_name) + fresh_module = importlib.import_module(name) + except ImportError: + fresh_module = None + finally: + for orig_name, module in orig_modules.items(): + sys.modules[orig_name] = module + for name_to_remove in names_to_remove: + del sys.modules[name_to_remove] + return fresh_module + +# TODO RUSTPYTHON: new implementation needs fronzen_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) + + +# TODO RUSTPYTHON: old implementation +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 + """ + + def __init__(self, *module_names): + 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] + + def __enter__(self): + return self + + def __exit__(self, *ignore_exc): + sys.modules.update(self.original_modules) + + +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)