mirror of https://github.com/python/cpython
Adding unittest.removeHandler function / decorator for removing the signal.SIGINT signal handler. With tests and docs.
This commit is contained in:
parent
adbcf1f4a8
commit
5c322ece96
|
@ -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):
|
||||
...
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue