Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 65b69a1

Browse files
author
Michael Foord
committed
Merged revisions 79437 via svnmerge from
svn+ssh://[email protected]/python/trunk ........ r79437 | michael.foord | 2010-03-26 03:18:31 +0000 (Fri, 26 Mar 2010) | 1 line Addition of -c command line option to unittest, to handle ctrl-c during a test run more elegantly ........
1 parent 8777f01 commit 65b69a1

6 files changed

Lines changed: 321 additions & 16 deletions

File tree

Lib/unittest/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ def testMultiply(self):
4747
__all__ = ['TestResult', 'TestCase', 'TestSuite',
4848
'TextTestRunner', 'TestLoader', 'FunctionTestCase', 'main',
4949
'defaultTestLoader', 'SkipTest', 'skip', 'skipIf', 'skipUnless',
50-
'expectedFailure', 'TextTestResult']
50+
'expectedFailure', 'TextTestResult', 'installHandler',
51+
'registerResult', 'removeResult']
5152

5253
# Expose obsolete functions for backwards compatibility
5354
__all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases'])
@@ -62,6 +63,7 @@ def testMultiply(self):
6263
findTestCases)
6364
from .main import TestProgram, main
6465
from .runner import TextTestRunner, TextTestResult
66+
from .signals import installHandler, registerResult, removeResult
6567

6668
# deprecated
6769
_TextTestResult = TextTestResult

Lib/unittest/main.py

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,22 @@
55
import types
66

77
from . import loader, runner
8+
from .signals import installHandler
89

910
__unittest = True
1011

1112

13+
FAILFAST = " -f, --failfast Stop on first failure\n"
14+
CATCHBREAK = " -c, --catch Catch control-C and display results\n"
15+
1216
USAGE_AS_MAIN = """\
1317
Usage: %(progName)s [options] [tests]
1418
1519
Options:
1620
-h, --help Show this message
1721
-v, --verbose Verbose output
1822
-q, --quiet Minimal output
19-
-f, --failfast Stop on first failure
20-
23+
%(failfast)s%(catchbreak)s
2124
Examples:
2225
%(progName)s test_module - run tests from test_module
2326
%(progName)s test_module.TestClass - run tests from
@@ -31,8 +34,7 @@
3134
3235
Options:
3336
-v, --verbose Verbose output
34-
-f, --failfast Stop on first failure
35-
-s directory Directory to start discovery ('.' default)
37+
%(failfast)s%(catchbreak)s -s directory Directory to start discovery ('.' default)
3638
-p pattern Pattern to match test files ('test*.py' default)
3739
-t directory Top level directory of project (default to
3840
start directory)
@@ -48,8 +50,7 @@
4850
-h, --help Show this message
4951
-v, --verbose Verbose output
5052
-q, --quiet Minimal output
51-
-f, --failfast Stop on first failure
52-
53+
%(failfast)s%(catchbreak)s
5354
Examples:
5455
%(progName)s - run default set of tests
5556
%(progName)s MyTestSuite - run suite 'MyTestSuite'
@@ -58,15 +59,21 @@
5859
in MyTestCase
5960
"""
6061

62+
63+
6164
class TestProgram(object):
6265
"""A command-line program that runs a set of tests; this is primarily
6366
for making test modules conveniently executable.
6467
"""
6568
USAGE = USAGE_FROM_MODULE
69+
70+
# defaults for testing
71+
failfast = catchbreak = None
72+
6673
def __init__(self, module='__main__', defaultTest=None,
6774
argv=None, testRunner=None,
6875
testLoader=loader.defaultTestLoader, exit=True,
69-
verbosity=1, failfast=False):
76+
verbosity=1, failfast=None, catchbreak=None):
7077
if isinstance(module, str):
7178
self.module = __import__(module)
7279
for part in module.split('.')[1:]:
@@ -78,6 +85,7 @@ def __init__(self, module='__main__', defaultTest=None,
7885

7986
self.exit = exit
8087
self.failfast = failfast
88+
self.catchbreak = catchbreak
8189
self.verbosity = verbosity
8290
self.defaultTest = defaultTest
8391
self.testRunner = testRunner
@@ -89,7 +97,12 @@ def __init__(self, module='__main__', defaultTest=None,
8997
def usageExit(self, msg=None):
9098
if msg:
9199
print(msg)
92-
print(self.USAGE % self.__dict__)
100+
usage = {'progName': self.progName, 'catchbreak': '', 'failfast': ''}
101+
if self.failfast != False:
102+
usage['failfast'] = FAILFAST
103+
if self.catchbreak != False:
104+
usage['catchbreak'] = CATCHBREAK
105+
print(self.USAGE % usage)
93106
sys.exit(2)
94107

95108
def parseArgs(self, argv):
@@ -98,9 +111,9 @@ def parseArgs(self, argv):
98111
return
99112

100113
import getopt
101-
long_opts = ['help', 'verbose', 'quiet', 'failfast']
114+
long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch']
102115
try:
103-
options, args = getopt.getopt(argv[1:], 'hHvqf', long_opts)
116+
options, args = getopt.getopt(argv[1:], 'hHvqfc', long_opts)
104117
for opt, value in options:
105118
if opt in ('-h','-H','--help'):
106119
self.usageExit()
@@ -109,7 +122,13 @@ def parseArgs(self, argv):
109122
if opt in ('-v','--verbose'):
110123
self.verbosity = 2
111124
if opt in ('-f','--failfast'):
112-
self.failfast = True
125+
if self.failfast is None:
126+
self.failfast = True
127+
# Should this raise an exception if -f is not valid?
128+
if opt in ('-c','--catch'):
129+
if self.catchbreak is None:
130+
self.catchbreak = True
131+
# Should this raise an exception if -c is not valid?
113132
if len(args) == 0 and self.defaultTest is None:
114133
# createTests will load tests from self.module
115134
self.testNames = None
@@ -137,8 +156,14 @@ def _do_discovery(self, argv, Loader=loader.TestLoader):
137156
parser = optparse.OptionParser()
138157
parser.add_option('-v', '--verbose', dest='verbose', default=False,
139158
help='Verbose output', action='store_true')
140-
parser.add_option('-f', '--failfast', dest='failfast', default=False,
141-
help='Stop on first fail or error', action='store_true')
159+
if self.failfast != False:
160+
parser.add_option('-f', '--failfast', dest='failfast', default=False,
161+
help='Stop on first fail or error',
162+
action='store_true')
163+
if self.catchbreak != False:
164+
parser.add_option('-c', '--catch', dest='catchbreak', default=False,
165+
help='Catch ctrl-C and display results so far',
166+
action='store_true')
142167
parser.add_option('-s', '--start-directory', dest='start', default='.',
143168
help="Directory to start discovery ('.' default)")
144169
parser.add_option('-p', '--pattern', dest='pattern', default='test*.py',
@@ -153,7 +178,13 @@ def _do_discovery(self, argv, Loader=loader.TestLoader):
153178
for name, value in zip(('start', 'pattern', 'top'), args):
154179
setattr(options, name, value)
155180

156-
self.failfast = options.failfast
181+
# only set options from the parsing here
182+
# if they weren't set explicitly in the constructor
183+
if self.failfast is None:
184+
self.failfast = options.failfast
185+
if self.catchbreak is None:
186+
self.catchbreak = options.catchbreak
187+
157188
if options.verbose:
158189
self.verbosity = 2
159190

@@ -165,6 +196,8 @@ def _do_discovery(self, argv, Loader=loader.TestLoader):
165196
self.test = loader.discover(start_dir, pattern, top_level_dir)
166197

167198
def runTests(self):
199+
if self.catchbreak:
200+
installHandler()
168201
if self.testRunner is None:
169202
self.testRunner = runner.TextTestRunner
170203
if isinstance(self.testRunner, type):

Lib/unittest/runner.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import time
55

66
from . import result
7+
from .signals import registerResult
78

89
__unittest = True
910

@@ -138,6 +139,7 @@ def _makeResult(self):
138139
def run(self, test):
139140
"Run the given test case or test suite."
140141
result = self._makeResult()
142+
registerResult(result)
141143
result.failfast = self.failfast
142144
startTime = time.time()
143145
startTestRun = getattr(result, 'startTestRun', None)

Lib/unittest/signals.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import signal
2+
import weakref
3+
4+
__unittest = True
5+
6+
7+
class _InterruptHandler(object):
8+
def __init__(self, default_handler):
9+
self.called = False
10+
self.default_handler = default_handler
11+
12+
def __call__(self, signum, frame):
13+
installed_handler = signal.getsignal(signal.SIGINT)
14+
if installed_handler is not self:
15+
# if we aren't the installed handler, then delegate immediately
16+
# to the default handler
17+
self.default_handler(signum, frame)
18+
19+
if self.called:
20+
self.default_handler(signum, frame)
21+
self.called = True
22+
for result in _results.keys():
23+
result.stop()
24+
25+
_results = weakref.WeakKeyDictionary()
26+
def registerResult(result):
27+
_results[result] = 1
28+
29+
def removeResult(result):
30+
return bool(_results.pop(result, None))
31+
32+
_interrupt_handler = None
33+
def installHandler():
34+
global _interrupt_handler
35+
if _interrupt_handler is None:
36+
default_handler = signal.getsignal(signal.SIGINT)
37+
_interrupt_handler = _InterruptHandler(default_handler)
38+
signal.signal(signal.SIGINT, _interrupt_handler)

0 commit comments

Comments
 (0)