diff --git a/Doc/library/test.rst b/Doc/library/test.rst index 0a2814bb0f8..ccca2991c51 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -339,6 +339,30 @@ The :mod:`test.test_support` module defines the following functions: .. 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: .. class:: TransientResource(exc[, **kwargs]) diff --git a/Lib/test/test_heapq.py b/Lib/test/test_heapq.py index f91d86db0d2..625852dabc9 100644 --- a/Lib/test/test_heapq.py +++ b/Lib/test/test_heapq.py @@ -7,23 +7,8 @@ import sys # We do a bit of trickery here to be able to test both the C implementation # 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 - +py_heapq = test_support.import_fresh_module('heapq', ['_heapq']) class TestHeap(unittest.TestCase): module = None @@ -193,6 +178,13 @@ class TestHeap(unittest.TestCase): class TestHeapPython(TestHeap): 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): module = c_heapq @@ -217,6 +209,12 @@ class TestHeapC(TestHeap): self.assertEqual(hsort(data, LT), 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')) + #============================================================================== diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index 0693850519d..f77df703715 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -42,22 +42,63 @@ 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 import_module(name, deprecated=False): """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.""" - with warnings.catch_warnings(): - if deprecated: - warnings.filterwarnings("ignore", ".+ (module|package)", - DeprecationWarning) + with _ignore_deprecated_imports(deprecated): try: - module = importlib.import_module(name) + return importlib.import_module(name) except ImportError, 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): """Get an attribute, raising SkipTest if AttributeError is raised.""" diff --git a/Lib/test/test_warnings.py b/Lib/test/test_warnings.py index bb2fb5ff250..4c3b43aec4b 100644 --- a/Lib/test/test_warnings.py +++ b/Lib/test/test_warnings.py @@ -10,18 +10,8 @@ import warning_tests import warnings as original_warnings -sys.modules['_warnings'] = 0 -del sys.modules['warnings'] - -import warnings as py_warnings - -del sys.modules['_warnings'] -del sys.modules['warnings'] - -import warnings as c_warnings - -sys.modules['warnings'] = original_warnings - +py_warnings = test_support.import_fresh_module('warnings', ['_warnings']) +c_warnings = test_support.import_fresh_module('warnings') @contextmanager def warnings_state(module): @@ -341,9 +331,21 @@ class WarnTests(unittest.TestCase): class CWarnTests(BaseTest, WarnTests): 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): 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):