Skip to content

Commit de4ceab

Browse files
author
Michael Foord
committed
Merged revisions 80476 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk ........ r80476 | michael.foord | 2010-04-25 20:02:46 +0100 (Sun, 25 Apr 2010) | 1 line Adding unittest.removeHandler function / decorator for removing the signal.SIGINT signal handler. With tests and docs. ........
1 parent af30c5d commit de4ceab

File tree

4 files changed

+67
-38
lines changed

4 files changed

+67
-38
lines changed

Doc/library/unittest.rst

Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -95,40 +95,6 @@ need to derive from a specific class.
9595
A special-interest-group for discussion of testing, and testing tools,
9696
in Python.
9797

98-
.. _unittest-test-discovery:
99-
100-
Test Discovery
101-
--------------
102-
103-
.. versionadded:: 3.2
104-
105-
unittest supports simple test discovery. For a project's tests to be
106-
compatible with test discovery they must all be importable from the top level
107-
directory of the project; i.e. they must all be in Python packages.
108-
109-
Test discovery is implemented in :meth:`TestLoader.discover`, but can also be
110-
used from the command line. The basic command line usage is::
111-
112-
cd project_directory
113-
python -m unittest discover
114-
115-
The ``discover`` sub-command has the following options:
116-
117-
-v, --verbose Verbose output
118-
-s directory Directory to start discovery ('.' default)
119-
-p pattern Pattern to match test files ('test*.py' default)
120-
-t directory Top level directory of project (default to
121-
start directory)
122-
123-
The -s, -p, & -t options can be passsed in as positional arguments. The
124-
following two command lines are equivalent::
125-
126-
python -m unittest discover -s project_directory -p '*_test.py'
127-
python -m unittest discover project_directory '*_test.py'
128-
129-
Test modules and packages can customize test loading and discovery by through
130-
the `load_tests protocol`_.
131-
13298
.. _unittest-minimal-example:
13399

134100
Basic example
@@ -1904,8 +1870,17 @@ allow the currently running test to complete, and the test run will then end
19041870
and report all the results so far. A second control-c will raise a
19051871
``KeyboardInterrupt`` in the usual way.
19061872

1907-
There are a few utility functions for framework authors to enable this
1908-
functionality within test frameworks.
1873+
The control-c handling signal handler attempts to remain compatible with code or
1874+
tests that install their own :const:`signal.SIGINT` handler. If the ``unittest``
1875+
handler is called but *isn't* the installed :const:`signal.SIGINT` handler,
1876+
i.e. it has been replaced by the system under test and delegated to, then it
1877+
calls the default handler. This will normally be the expected behavior by code
1878+
that replaces an installed handler and delegates to it. For individual tests
1879+
that need ``unittest`` control-c handling disabled the :func:`removeHandler`
1880+
decorator can be used.
1881+
1882+
There are a few utility functions for framework authors to enable control-c
1883+
handling functionality within test frameworks.
19091884

19101885
.. function:: installHandler()
19111886

@@ -1919,9 +1894,23 @@ functionality within test frameworks.
19191894
result stores a weak reference to it, so it doesn't prevent the result from
19201895
being garbage collected.
19211896

1897+
Registering a :class:`TestResult` object has no side-effects if control-c
1898+
handling is not enabled, so test frameworks can unconditionally register
1899+
all results they create independently of whether or not handling is enabled.
1900+
19221901
.. function:: removeResult(result)
19231902

19241903
Remove a registered result. Once a result has been removed then
19251904
:meth:`~TestResult.stop` will no longer be called on that result object in
19261905
response to a control-c.
19271906

1907+
.. function:: removeHandler(function=None)
1908+
1909+
When called without arguments this function removes the control-c handler
1910+
if it has been installed. This function can also be used as a test decorator
1911+
to temporarily remove the handler whilst the test is being executed::
1912+
1913+
@unittest.removeHandler
1914+
def test_signal_handling(self):
1915+
...
1916+

Lib/unittest/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def testMultiply(self):
4848
'TextTestRunner', 'TestLoader', 'FunctionTestCase', 'main',
4949
'defaultTestLoader', 'SkipTest', 'skip', 'skipIf', 'skipUnless',
5050
'expectedFailure', 'TextTestResult', 'installHandler',
51-
'registerResult', 'removeResult']
51+
'registerResult', 'removeResult', 'removeHandler']
5252

5353
# Expose obsolete functions for backwards compatibility
5454
__all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases'])
@@ -63,7 +63,7 @@ def testMultiply(self):
6363
findTestCases)
6464
from .main import TestProgram, main
6565
from .runner import TextTestRunner, TextTestResult
66-
from .signals import installHandler, registerResult, removeResult
66+
from .signals import installHandler, registerResult, removeResult, removeHandler
6767

6868
# deprecated
6969
_TextTestResult = TextTestResult

Lib/unittest/signals.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import signal
22
import weakref
33

4+
from functools import wraps
5+
46
__unittest = True
57

68

@@ -36,3 +38,20 @@ def installHandler():
3638
default_handler = signal.getsignal(signal.SIGINT)
3739
_interrupt_handler = _InterruptHandler(default_handler)
3840
signal.signal(signal.SIGINT, _interrupt_handler)
41+
42+
43+
def removeHandler(method=None):
44+
if method is not None:
45+
@wraps(method)
46+
def inner(*args, **kwargs):
47+
initial = signal.getsignal(signal.SIGINT)
48+
removeHandler()
49+
try:
50+
return method(*args, **kwargs)
51+
finally:
52+
signal.signal(signal.SIGINT, initial)
53+
return inner
54+
55+
global _interrupt_handler
56+
if _interrupt_handler is not None:
57+
signal.signal(signal.SIGINT, _interrupt_handler.default_handler)

Lib/unittest/test/test_break.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,24 @@ def __init__(self, catchbreak):
227227
self.assertEqual(p.result, result)
228228

229229
self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler)
230+
231+
def testRemoveHandler(self):
232+
default_handler = signal.getsignal(signal.SIGINT)
233+
unittest.installHandler()
234+
unittest.removeHandler()
235+
self.assertEqual(signal.getsignal(signal.SIGINT), default_handler)
236+
237+
# check that calling removeHandler multiple times has no ill-effect
238+
unittest.removeHandler()
239+
self.assertEqual(signal.getsignal(signal.SIGINT), default_handler)
240+
241+
def testRemoveHandlerAsDecorator(self):
242+
default_handler = signal.getsignal(signal.SIGINT)
243+
unittest.installHandler()
244+
245+
@unittest.removeHandler
246+
def test():
247+
self.assertEqual(signal.getsignal(signal.SIGINT), default_handler)
248+
249+
test()
250+
self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler)

0 commit comments

Comments
 (0)