summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Foord <fuzzyman@voidspace.org.uk>2010-04-25 19:02:46 (GMT)
committerMichael Foord <fuzzyman@voidspace.org.uk>2010-04-25 19:02:46 (GMT)
commit5c322ece9601da2a4113f489d44762abffe6b8cd (patch)
tree2718070eecc6d3101673bf5bd673f3a3d2ec382c
parentadbcf1f4a804b1a1497fd33ee2c4c28d264197aa (diff)
downloadcpython-5c322ece9601da2a4113f489d44762abffe6b8cd.zip
cpython-5c322ece9601da2a4113f489d44762abffe6b8cd.tar.gz
cpython-5c322ece9601da2a4113f489d44762abffe6b8cd.tar.bz2
Adding unittest.removeHandler function / decorator for removing the signal.SIGINT signal handler. With tests and docs.
-rw-r--r--Doc/library/unittest.rst27
-rw-r--r--Lib/unittest/__init__.py4
-rw-r--r--Lib/unittest/signals.py19
-rw-r--r--Lib/unittest/test/test_break.py21
4 files changed, 67 insertions, 4 deletions
diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst
index f28f4f0..66ac57c 100644
--- a/Doc/library/unittest.rst
+++ b/Doc/library/unittest.rst
@@ -1855,8 +1855,17 @@ allow the currently running test to complete, and the test run will then end
and report all the results so far. A second control-c will raise a
``KeyboardInterrupt`` in the usual way.
-There are a few utility functions for framework authors to enable this
-functionality within test frameworks.
+The control-c handling signal handler attempts to remain compatible with code or
+tests that install their own :const:`signal.SIGINT` handler. If the ``unittest``
+handler is called but *isn't* the installed :const:`signal.SIGINT` handler,
+i.e. it has been replaced by the system under test and delegated to, then it
+calls the default handler. This will normally be the expected behavior by code
+that replaces an installed handler and delegates to it. For individual tests
+that need ``unittest`` control-c handling disabled the :func:`removeHandler`
+decorator can be used.
+
+There are a few utility functions for framework authors to enable control-c
+handling functionality within test frameworks.
.. function:: installHandler()
@@ -1870,9 +1879,23 @@ functionality within test frameworks.
result stores a weak reference to it, so it doesn't prevent the result from
being garbage collected.
+ Registering a :class:`TestResult` object has no side-effects if control-c
+ handling is not enabled, so test frameworks can unconditionally register
+ all results they create independently of whether or not handling is enabled.
+
.. function:: removeResult(result)
Remove a registered result. Once a result has been removed then
:meth:`~TestResult.stop` will no longer be called on that result object in
response to a control-c.
+.. function:: removeHandler(function=None)
+
+ When called without arguments this function removes the control-c handler
+ if it has been installed. This function can also be used as a test decorator
+ to temporarily remove the handler whilst the test is being executed::
+
+ @unittest.removeHandler
+ def test_signal_handling(self):
+ ...
+
diff --git a/Lib/unittest/__init__.py b/Lib/unittest/__init__.py
index e84299e..201a3f0 100644
--- a/Lib/unittest/__init__.py
+++ b/Lib/unittest/__init__.py
@@ -48,7 +48,7 @@ __all__ = ['TestResult', 'TestCase', 'TestSuite',
'TextTestRunner', 'TestLoader', 'FunctionTestCase', 'main',
'defaultTestLoader', 'SkipTest', 'skip', 'skipIf', 'skipUnless',
'expectedFailure', 'TextTestResult', 'installHandler',
- 'registerResult', 'removeResult']
+ 'registerResult', 'removeResult', 'removeHandler']
# Expose obsolete functions for backwards compatibility
__all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases'])
@@ -63,7 +63,7 @@ from .loader import (TestLoader, defaultTestLoader, makeSuite, getTestCaseNames,
findTestCases)
from .main import TestProgram, main
from .runner import TextTestRunner, TextTestResult
-from .signals import installHandler, registerResult, removeResult
+from .signals import installHandler, registerResult, removeResult, removeHandler
# deprecated
_TextTestResult = TextTestResult
diff --git a/Lib/unittest/signals.py b/Lib/unittest/signals.py
index 0651cf2..fc31043 100644
--- a/Lib/unittest/signals.py
+++ b/Lib/unittest/signals.py
@@ -1,6 +1,8 @@
import signal
import weakref
+from functools import wraps
+
__unittest = True
@@ -36,3 +38,20 @@ def installHandler():
default_handler = signal.getsignal(signal.SIGINT)
_interrupt_handler = _InterruptHandler(default_handler)
signal.signal(signal.SIGINT, _interrupt_handler)
+
+
+def removeHandler(method=None):
+ if method is not None:
+ @wraps(method)
+ def inner(*args, **kwargs):
+ initial = signal.getsignal(signal.SIGINT)
+ removeHandler()
+ try:
+ return method(*args, **kwargs)
+ finally:
+ signal.signal(signal.SIGINT, initial)
+ return inner
+
+ global _interrupt_handler
+ if _interrupt_handler is not None:
+ signal.signal(signal.SIGINT, _interrupt_handler.default_handler)
diff --git a/Lib/unittest/test/test_break.py b/Lib/unittest/test/test_break.py
index cc3e282..5600011 100644
--- a/Lib/unittest/test/test_break.py
+++ b/Lib/unittest/test/test_break.py
@@ -229,3 +229,24 @@ class TestBreak(unittest.TestCase):
self.assertEqual(p.result, result)
self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler)
+
+ def testRemoveHandler(self):
+ default_handler = signal.getsignal(signal.SIGINT)
+ unittest.installHandler()
+ unittest.removeHandler()
+ self.assertEqual(signal.getsignal(signal.SIGINT), default_handler)
+
+ # check that calling removeHandler multiple times has no ill-effect
+ unittest.removeHandler()
+ self.assertEqual(signal.getsignal(signal.SIGINT), default_handler)
+
+ def testRemoveHandlerAsDecorator(self):
+ default_handler = signal.getsignal(signal.SIGINT)
+ unittest.installHandler()
+
+ @unittest.removeHandler
+ def test():
+ self.assertEqual(signal.getsignal(signal.SIGINT), default_handler)
+
+ test()
+ self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler)