[Python-checkins] r71465 - in python/trunk: Doc/library/test.rst Lib/test/test_heapq.py Lib/test/test_support.py Lib/test/test_warnings.py

nick.coghlan python-checkins at python.org
Sat Apr 11 15:31:32 CEST 2009


Author: nick.coghlan
Date: Sat Apr 11 15:31:31 2009
New Revision: 71465

Log:
Issue 5251: 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

Modified:
   python/trunk/Doc/library/test.rst
   python/trunk/Lib/test/test_heapq.py
   python/trunk/Lib/test/test_support.py
   python/trunk/Lib/test/test_warnings.py

Modified: python/trunk/Doc/library/test.rst
==============================================================================
--- python/trunk/Doc/library/test.rst	(original)
+++ python/trunk/Doc/library/test.rst	Sat Apr 11 15:31:31 2009
@@ -339,6 +339,30 @@
    .. 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])

Modified: python/trunk/Lib/test/test_heapq.py
==============================================================================
--- python/trunk/Lib/test/test_heapq.py	(original)
+++ python/trunk/Lib/test/test_heapq.py	Sat Apr 11 15:31:31 2009
@@ -7,23 +7,8 @@
 
 # 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 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 @@
         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'))
+
 
 #==============================================================================
 

Modified: python/trunk/Lib/test/test_support.py
==============================================================================
--- python/trunk/Lib/test/test_support.py	(original)
+++ python/trunk/Lib/test/test_support.py	Sat Apr 11 15:31:31 2009
@@ -42,22 +42,63 @@
     and unexpected skips.
     """
 
+ at 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."""

Modified: python/trunk/Lib/test/test_warnings.py
==============================================================================
--- python/trunk/Lib/test/test_warnings.py	(original)
+++ python/trunk/Lib/test/test_warnings.py	Sat Apr 11 15:31:31 2009
@@ -10,18 +10,8 @@
 
 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 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):
 


More information about the Python-checkins mailing list