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.
........
This commit is contained in:
Michael Foord 2010-04-25 19:53:49 +00:00
parent af30c5d32e
commit de4ceabfd8
4 changed files with 67 additions and 38 deletions

View file

@ -95,40 +95,6 @@ need to derive from a specific class.
A special-interest-group for discussion of testing, and testing tools,
in Python.
.. _unittest-test-discovery:
Test Discovery
--------------
.. versionadded:: 3.2
unittest supports simple test discovery. For a project's tests to be
compatible with test discovery they must all be importable from the top level
directory of the project; i.e. they must all be in Python packages.
Test discovery is implemented in :meth:`TestLoader.discover`, but can also be
used from the command line. The basic command line usage is::
cd project_directory
python -m unittest discover
The ``discover`` sub-command has the following options:
-v, --verbose Verbose output
-s directory Directory to start discovery ('.' default)
-p pattern Pattern to match test files ('test*.py' default)
-t directory Top level directory of project (default to
start directory)
The -s, -p, & -t options can be passsed in as positional arguments. The
following two command lines are equivalent::
python -m unittest discover -s project_directory -p '*_test.py'
python -m unittest discover project_directory '*_test.py'
Test modules and packages can customize test loading and discovery by through
the `load_tests protocol`_.
.. _unittest-minimal-example:
Basic example
@ -1904,8 +1870,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()
@ -1919,9 +1894,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 @@ def testMultiply(self):
'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 @@ def testMultiply(self):
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

@ -227,3 +227,24 @@ def __init__(self, catchbreak):
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)