Issue 5354: Change API for import_fresh_module() to better support test_warnings use case (also fixes some bugs in the original implementation)

This commit is contained in:
Nick Coghlan 2009-04-22 15:26:04 +00:00
parent aca19e6a74
commit 5533ff6a2e
5 changed files with 85 additions and 21 deletions

View File

@ -351,15 +351,39 @@ The :mod:`test.test_support` module defines the following functions:
.. versionadded:: 2.7 .. versionadded:: 2.7
.. function:: import_fresh_module(name, blocked_names=None, deprecated=False) .. function:: import_fresh_module(name, fresh=(), blocked=(), deprecated=False)
This function imports and returns a fresh copy of the named Python module. The This function imports and returns a fresh copy of the named Python module
``sys.modules`` cache is bypassed temporarily, and the ability to import the by removing the named module from ``sys.modules`` before doing the import.
modules named in *blocked_names* is suppressed for the duration of the import. Note that unlike :func:`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 :const:`0`
in the module cache during the import to ensure that attempts to import
them raise :exc:`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 Module and package deprecation messages are suppressed during this import
if *deprecated* is :const:`True`. if *deprecated* is :const:`True`.
This function will raise :exc:`unittest.SkipTest` is the named module
cannot be imported.
Example use::
# Get copies of the warnings module for testing without
# affecting the version being used by the rest of the test suite
# One copy uses the C implementation, the other is forced to use
# the pure Python fallback implementation
py_warnings = import_fresh_module('warnings', blocked=['_warnings'])
c_warnings = import_fresh_module('warnings', fresh=['_warnings'])
.. versionadded:: 2.7 .. versionadded:: 2.7

View File

@ -8,7 +8,7 @@ import sys
# We do a bit of trickery here to be able to test both the C implementation # We do a bit of trickery here to be able to test both the C implementation
# and the Python implementation of the module. # and the Python implementation of the module.
import heapq as c_heapq import heapq as c_heapq
py_heapq = test_support.import_fresh_module('heapq', ['_heapq']) py_heapq = test_support.import_fresh_module('heapq', blocked=['_heapq'])
class TestHeap(unittest.TestCase): class TestHeap(unittest.TestCase):
module = None module = None

View File

@ -70,12 +70,43 @@ def import_module(name, deprecated=False):
raise unittest.SkipTest(str(msg)) raise unittest.SkipTest(str(msg))
def import_fresh_module(name, blocked_names=None, deprecated=False): def _save_and_remove_module(name, orig_modules):
"""Helper function to save and remove a module from sys.modules
Return value is True if the module was in sys.modules and
False otherwise."""
saved = True
try:
orig_modules[name] = sys.modules[name]
except KeyError:
saved = False
else:
del sys.modules[name]
return saved
def _save_and_block_module(name, orig_modules):
"""Helper function to save and block a module in sys.modules
Return value is True if the module was in sys.modules and
False otherwise."""
saved = True
try:
orig_modules[name] = sys.modules[name]
except KeyError:
saved = False
sys.modules[name] = 0
return saved
def import_fresh_module(name, fresh=(), blocked=(), deprecated=False):
"""Imports and returns a module, deliberately bypassing the sys.modules cache """Imports and returns a module, deliberately bypassing the sys.modules cache
and importing a fresh copy of the module. Once the import is complete, and importing a fresh copy of the module. Once the import is complete,
the sys.modules cache is restored to its original state. the sys.modules cache is restored to its original state.
Importing of modules named in blocked_names is prevented while the fresh import Modules named in fresh are also imported anew if needed by the import.
Importing of modules named in blocked is prevented while the fresh import
takes place. takes place.
If deprecated is True, any module or package deprecation messages If deprecated is True, any module or package deprecation messages
@ -83,21 +114,24 @@ def import_fresh_module(name, blocked_names=None, deprecated=False):
# NOTE: test_heapq and test_warnings include extra sanity checks to make # NOTE: test_heapq and test_warnings include extra sanity checks to make
# sure that this utility function is working as expected # sure that this utility function is working as expected
with _ignore_deprecated_imports(deprecated): with _ignore_deprecated_imports(deprecated):
if blocked_names is None: # Keep track of modules saved for later restoration as well
blocked_names = () # as those which just need a blocking entry removed
orig_modules = {} orig_modules = {}
if name in sys.modules: names_to_remove = []
orig_modules[name] = sys.modules[name] _save_and_remove_module(name, orig_modules)
del sys.modules[name]
try: try:
for blocked in blocked_names: for fresh_name in fresh:
orig_modules[blocked] = sys.modules[blocked] _save_and_remove_module(fresh_name, orig_modules)
sys.modules[blocked] = 0 for blocked_name in blocked:
py_module = importlib.import_module(name) if not _save_and_block_module(blocked_name, orig_modules):
names_to_remove.append(blocked_name)
fresh_module = importlib.import_module(name)
finally: finally:
for blocked, module in orig_modules.items(): for orig_name, module in orig_modules.items():
sys.modules[blocked] = module sys.modules[orig_name] = module
return py_module for name_to_remove in names_to_remove:
del sys.modules[name_to_remove]
return fresh_module
def get_attribute(obj, name): def get_attribute(obj, name):

View File

@ -10,8 +10,8 @@ import warning_tests
import warnings as original_warnings import warnings as original_warnings
py_warnings = test_support.import_fresh_module('warnings', ['_warnings']) py_warnings = test_support.import_fresh_module('warnings', blocked=['_warnings'])
c_warnings = test_support.import_fresh_module('warnings') c_warnings = test_support.import_fresh_module('warnings', fresh=['_warnings'])
@contextmanager @contextmanager
def warnings_state(module): def warnings_state(module):

View File

@ -839,6 +839,12 @@ Extension Modules
Tests Tests
----- -----
- Issue #5354: New test support function import_fresh_module() makes
it easy to import both normal and optimised versions of modules.
test_heapq and test_warnings have been adjusted to use it, tests for
other modules with both C and Python implementations in the stdlib
can be adjusted to use it over time.
- Fix test_warnings to no longer reset the warnings filter. - Fix test_warnings to no longer reset the warnings filter.
- Fix test_logging to no longer reset the warnings filter. - Fix test_logging to no longer reset the warnings filter.