Adding unittest.removeHandler function / decorator for removing the signal.SIGINT signal handler. With tests and docs.

This commit is contained in:
Michael Foord 2010-04-25 19:02:46 +00:00
parent adbcf1f4a8
commit 5c322ece96
4 changed files with 67 additions and 4 deletions

View File

@ -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):
...

View File

@ -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

View File

@ -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)

View File

@ -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)