Merged revisions 71465 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk

........
  r71465 | nick.coghlan | 2009-04-11 23:31:31 +1000 (Sat, 11 Apr 2009) | 1 line

  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
........
diff --git a/Lib/test/support.py b/Lib/test/support.py
index 9cd50a0..28823ae 100644
--- a/Lib/test/support.py
+++ b/Lib/test/support.py
@@ -41,22 +41,63 @@
     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 as 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_heapq.py b/Lib/test/test_heapq.py
index e1e7e9c..bbb22bd 100644
--- a/Lib/test/test_heapq.py
+++ b/Lib/test/test_heapq.py
@@ -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 = support.import_fresh_module('heapq', ['_heapq'])
 
 class TestHeap(unittest.TestCase):
     module = None
@@ -194,6 +179,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, '__code__'))
+
+
 class TestHeapC(TestHeap):
     module = c_heapq
 
@@ -219,6 +211,12 @@
         self.assertEqual(hsort(data, LT), target)
         self.assertRaises(TypeError, data, LE)
 
+    # 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, '__code__'))
+
 
 #==============================================================================
 
diff --git a/Lib/test/test_warnings.py b/Lib/test/test_warnings.py
index d04c3dd..1f377ad 100644
--- a/Lib/test/test_warnings.py
+++ b/Lib/test/test_warnings.py
@@ -10,18 +10,14 @@
 
 import warnings as original_warnings
 
-sys.modules['_warnings'] = 0
-del sys.modules['warnings']
-
-import warnings as py_warnings
-
+py_warnings = support.import_fresh_module('warnings', ['_warnings'])
+# XXX (ncoghlan 20090412):
+# Something in Py3k doesn't like sharing the same instance of
+# _warnings between original_warnings and c_warnings
+# Will leave issue 5354 open until I understand why 3.x breaks
+# without the next line, while 2.x doesn't care
 del sys.modules['_warnings']
-del sys.modules['warnings']
-
-import warnings as c_warnings
-
-sys.modules['warnings'] = original_warnings
-
+c_warnings = support.import_fresh_module('warnings')
 
 @contextmanager
 def warnings_state(module):
@@ -351,9 +347,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, '__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, '__code__'))
+
 
 class WCmdLineTests(unittest.TestCase):