mirror of https://github.com/python/cpython
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:
parent
b524825788
commit
cd2e7042ae
|
@ -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])
|
||||||
|
|
|
@ -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'))
|
||||||
|
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
|
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue