mirror of
https://github.com/python/cpython.git
synced 2025-08-31 14:07:50 +00:00
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:
parent
af30c5d32e
commit
de4ceabfd8
4 changed files with 67 additions and 38 deletions
|
@ -95,40 +95,6 @@ need to derive from a specific class.
|
||||||
A special-interest-group for discussion of testing, and testing tools,
|
A special-interest-group for discussion of testing, and testing tools,
|
||||||
in Python.
|
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:
|
.. _unittest-minimal-example:
|
||||||
|
|
||||||
Basic 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
|
and report all the results so far. A second control-c will raise a
|
||||||
``KeyboardInterrupt`` in the usual way.
|
``KeyboardInterrupt`` in the usual way.
|
||||||
|
|
||||||
There are a few utility functions for framework authors to enable this
|
The control-c handling signal handler attempts to remain compatible with code or
|
||||||
functionality within test frameworks.
|
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()
|
.. 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
|
result stores a weak reference to it, so it doesn't prevent the result from
|
||||||
being garbage collected.
|
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)
|
.. function:: removeResult(result)
|
||||||
|
|
||||||
Remove a registered result. Once a result has been removed then
|
Remove a registered result. Once a result has been removed then
|
||||||
:meth:`~TestResult.stop` will no longer be called on that result object in
|
:meth:`~TestResult.stop` will no longer be called on that result object in
|
||||||
response to a control-c.
|
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',
|
'TextTestRunner', 'TestLoader', 'FunctionTestCase', 'main',
|
||||||
'defaultTestLoader', 'SkipTest', 'skip', 'skipIf', 'skipUnless',
|
'defaultTestLoader', 'SkipTest', 'skip', 'skipIf', 'skipUnless',
|
||||||
'expectedFailure', 'TextTestResult', 'installHandler',
|
'expectedFailure', 'TextTestResult', 'installHandler',
|
||||||
'registerResult', 'removeResult']
|
'registerResult', 'removeResult', 'removeHandler']
|
||||||
|
|
||||||
# Expose obsolete functions for backwards compatibility
|
# Expose obsolete functions for backwards compatibility
|
||||||
__all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases'])
|
__all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases'])
|
||||||
|
@ -63,7 +63,7 @@ from .loader import (TestLoader, defaultTestLoader, makeSuite, getTestCaseNames,
|
||||||
findTestCases)
|
findTestCases)
|
||||||
from .main import TestProgram, main
|
from .main import TestProgram, main
|
||||||
from .runner import TextTestRunner, TextTestResult
|
from .runner import TextTestRunner, TextTestResult
|
||||||
from .signals import installHandler, registerResult, removeResult
|
from .signals import installHandler, registerResult, removeResult, removeHandler
|
||||||
|
|
||||||
# deprecated
|
# deprecated
|
||||||
_TextTestResult = TextTestResult
|
_TextTestResult = TextTestResult
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import signal
|
import signal
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
__unittest = True
|
__unittest = True
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,3 +38,20 @@ def installHandler():
|
||||||
default_handler = signal.getsignal(signal.SIGINT)
|
default_handler = signal.getsignal(signal.SIGINT)
|
||||||
_interrupt_handler = _InterruptHandler(default_handler)
|
_interrupt_handler = _InterruptHandler(default_handler)
|
||||||
signal.signal(signal.SIGINT, _interrupt_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)
|
||||||
|
|
|
@ -227,3 +227,24 @@ class TestBreak(unittest.TestCase):
|
||||||
self.assertEqual(p.result, result)
|
self.assertEqual(p.result, result)
|
||||||
|
|
||||||
self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler)
|
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…
Add table
Add a link
Reference in a new issue