Issue 5354: Provide a standardised testing mechanism for doing fresh imports of modules, including the ability to block extension modules in order to test the pure Python fallbacks

This commit is contained in:
Nick Coghlan 2009-04-11 13:31:31 +00:00
parent b524825788
commit cd2e7042ae
4 changed files with 100 additions and 35 deletions

View File

@ -339,6 +339,30 @@ The :mod:`test.test_support` module defines the following functions:
.. versionadded:: 2.6 .. versionadded:: 2.6
.. function:: import_module(name, deprecated=False)
This function imports and returns the named module. Unlike a normal
import, this function raises :exc:`unittest.SkipTest` if the module
cannot be imported.
Module and package deprecation messages are suppressed during this import
if *deprecated* is :const:`True`.
.. versionadded:: 2.7
.. function:: import_fresh_module(name, blocked_names=None, deprecated=False)
This function imports and returns a fresh copy of the named Python module. The
``sys.modules`` cache is bypassed temporarily, and the ability to import the
modules named in *blocked_names* is suppressed for the duration of the import.
Module and package deprecation messages are suppressed during this import
if *deprecated* is :const:`True`.
.. versionadded:: 2.7
The :mod:`test.test_support` module defines the following classes: The :mod:`test.test_support` module defines the following classes:
.. class:: TransientResource(exc[, **kwargs]) .. class:: TransientResource(exc[, **kwargs])

View File

@ -7,23 +7,8 @@ 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.
# Make it impossible to import the C implementation anymore.
sys.modules['_heapq'] = 0
# We must also handle the case that heapq was imported before.
if 'heapq' in sys.modules:
del sys.modules['heapq']
# Now we can import the module and get the pure Python implementation.
import heapq as py_heapq
# Restore everything to normal.
del sys.modules['_heapq']
del sys.modules['heapq']
# This is now the module with the C implementation.
import heapq as c_heapq import heapq as c_heapq
py_heapq = test_support.import_fresh_module('heapq', ['_heapq'])
class TestHeap(unittest.TestCase): class TestHeap(unittest.TestCase):
module = None module = None
@ -193,6 +178,13 @@ class TestHeap(unittest.TestCase):
class TestHeapPython(TestHeap): class TestHeapPython(TestHeap):
module = py_heapq module = py_heapq
# As an early adopter, we sanity check the
# test_support.import_fresh_module utility function
def test_pure_python(self):
self.assertFalse(sys.modules['heapq'] is self.module)
self.assertTrue(hasattr(self.module.heapify, 'func_code'))
class TestHeapC(TestHeap): class TestHeapC(TestHeap):
module = c_heapq module = c_heapq
@ -217,6 +209,12 @@ class TestHeapC(TestHeap):
self.assertEqual(hsort(data, LT), target) self.assertEqual(hsort(data, LT), target)
self.assertEqual(hsort(data, LE), target) self.assertEqual(hsort(data, LE), target)
# As an early adopter, we sanity check the
# test_support.import_fresh_module utility function
def test_accelerated(self):
self.assertTrue(sys.modules['heapq'] is self.module)
self.assertFalse(hasattr(self.module.heapify, 'func_code'))
#============================================================================== #==============================================================================

View File

@ -42,22 +42,63 @@ class ResourceDenied(unittest.SkipTest):
and unexpected skips. 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 import_module(name, deprecated=False): def import_module(name, deprecated=False):
"""Import and return the module to be tested, raising SkipTest if """Import and return the module to be tested, raising SkipTest if
it is not available. it is not available.
If deprecated is True, any module or package deprecation messages If deprecated is True, any module or package deprecation messages
will be suppressed.""" will be suppressed."""
with warnings.catch_warnings(): with _ignore_deprecated_imports(deprecated):
if deprecated:
warnings.filterwarnings("ignore", ".+ (module|package)",
DeprecationWarning)
try: try:
module = importlib.import_module(name) return importlib.import_module(name)
except ImportError, msg: except ImportError, msg:
raise unittest.SkipTest(str(msg)) raise unittest.SkipTest(str(msg))
else:
return module
def import_fresh_module(name, blocked_names=None, deprecated=False):
"""Imports and returns a module, deliberately bypassing the sys.modules cache
and importing a fresh copy of the module. Once the import is complete,
the sys.modules cache is restored to its original state.
Importing of modules named in blocked_names is prevented while the fresh import
takes place.
If deprecated is True, any module or package deprecation messages
will be suppressed."""
# NOTE: test_heapq and test_warnings include extra sanity checks to make
# sure that this utility function is working as expected
with _ignore_deprecated_imports(deprecated):
if blocked_names is None:
blocked_names = ()
orig_modules = {}
if name in sys.modules:
orig_modules[name] = sys.modules[name]
del sys.modules[name]
try:
for blocked in blocked_names:
orig_modules[blocked] = sys.modules[blocked]
sys.modules[blocked] = 0
py_module = importlib.import_module(name)
finally:
for blocked, module in orig_modules.items():
sys.modules[blocked] = module
return py_module
def get_attribute(obj, name): def get_attribute(obj, name):
"""Get an attribute, raising SkipTest if AttributeError is raised.""" """Get an attribute, raising SkipTest if AttributeError is raised."""

View File

@ -10,18 +10,8 @@ import warning_tests
import warnings as original_warnings import warnings as original_warnings
sys.modules['_warnings'] = 0 py_warnings = test_support.import_fresh_module('warnings', ['_warnings'])
del sys.modules['warnings'] c_warnings = test_support.import_fresh_module('warnings')
import warnings as py_warnings
del sys.modules['_warnings']
del sys.modules['warnings']
import warnings as c_warnings
sys.modules['warnings'] = original_warnings
@contextmanager @contextmanager
def warnings_state(module): def warnings_state(module):
@ -341,9 +331,21 @@ class WarnTests(unittest.TestCase):
class CWarnTests(BaseTest, WarnTests): class CWarnTests(BaseTest, WarnTests):
module = c_warnings module = c_warnings
# As an early adopter, we sanity check the
# test_support.import_fresh_module utility function
def test_accelerated(self):
self.assertFalse(original_warnings is self.module)
self.assertFalse(hasattr(self.module.warn, 'func_code'))
class PyWarnTests(BaseTest, WarnTests): class PyWarnTests(BaseTest, WarnTests):
module = py_warnings module = py_warnings
# As an early adopter, we sanity check the
# test_support.import_fresh_module utility function
def test_pure_python(self):
self.assertFalse(original_warnings is self.module)
self.assertTrue(hasattr(self.module.warn, 'func_code'))
class WCmdLineTests(unittest.TestCase): class WCmdLineTests(unittest.TestCase):