1
# Copyright (c) 2009 testtools developers. See LICENSE for details.
3
"""python -m testtools.run testspec [testspec...]
5
Run some tests with the testtools extended API.
7
For instance, to run the testtools test suite.
8
$ python -m testtools.run testtools.tests.test_suite
15
from testtools import TextTestResult
16
from testtools.compat import classtypes, istext, unicode_output_stream
17
from testtools.testsuite import iterate_tests
20
defaultTestLoader = unittest.defaultTestLoader
21
defaultTestLoaderCls = unittest.TestLoader
23
if getattr(defaultTestLoader, 'discover', None) is None:
26
defaultTestLoader = discover.DiscoveringTestLoader()
27
defaultTestLoaderCls = discover.DiscoveringTestLoader
35
class TestToolsTestRunner(object):
36
""" A thunk object to support unittest.TestProgram."""
38
def __init__(self, stdout):
42
"Run the given test case or test suite."
43
result = TextTestResult(unicode_output_stream(self.stdout))
46
return test.run(result)
52
# Taken from python 2.7 and slightly modified for compatibility with
53
# older versions. Delete when 2.7 is the oldest supported version.
55
# - Use have_discover to raise an error if the user tries to use
56
# discovery on an old version and doesn't have discover installed.
57
# - If --catch is given check that installHandler is available, as
58
# it won't be on old python versions.
59
# - print calls have been been made single-source python3 compatibile.
60
# - exception handling likewise.
61
# - The default help has been changed to USAGE_AS_MAIN and USAGE_FROM_MODULE
63
# - A tweak has been added to detect 'python -m *.run' and use a
64
# better progName in that case.
65
# - self.module is more comprehensively set to None when being invoked from
66
# the commandline - __name__ is used as a sentinel value.
67
# - --list has been added which can list tests (should be upstreamed).
68
# - --load-list has been added which can reduce the tests used (should be
70
# - The limitation of using getopt is declared to the user.
72
FAILFAST = " -f, --failfast Stop on first failure\n"
73
CATCHBREAK = " -c, --catch Catch control-C and display results\n"
74
BUFFEROUTPUT = " -b, --buffer Buffer stdout and stderr during test runs\n"
77
Usage: %(progName)s [options] [tests]
80
-h, --help Show this message
81
-v, --verbose Verbose output
82
-q, --quiet Minimal output
83
-l, --list List tests rather than executing them.
84
--load-list Specifies a file containing test ids, only tests matching
85
those ids are executed.
86
%(failfast)s%(catchbreak)s%(buffer)s
88
%(progName)s test_module - run tests from test_module
89
%(progName)s module.TestClass - run tests from module.TestClass
90
%(progName)s module.Class.test_method - run specified test method
92
All options must come before [tests]. [tests] can be a list of any number of
93
test modules, classes and test methods.
95
Alternative Usage: %(progName)s discover [options]
98
-v, --verbose Verbose output
99
%(failfast)s%(catchbreak)s%(buffer)s -s directory Directory to start discovery ('.' default)
100
-p pattern Pattern to match test files ('test*.py' default)
101
-t directory Top level directory of project (default to
103
-l, --list List tests rather than executing them.
104
--load-list Specifies a file containing test ids, only tests matching
105
those ids are executed.
107
For test discovery all test modules must be importable from the top
108
level directory of the project.
112
class TestProgram(object):
113
"""A command-line program that runs a set of tests; this is primarily
114
for making test modules conveniently executable.
116
USAGE = USAGE_AS_MAIN
118
# defaults for testing
119
failfast = catchbreak = buffer = progName = None
121
def __init__(self, module=__name__, defaultTest=None, argv=None,
122
testRunner=None, testLoader=defaultTestLoader,
123
exit=True, verbosity=1, failfast=None, catchbreak=None,
124
buffer=None, stdout=None):
125
if module == __name__:
128
self.module = __import__(module)
129
for part in module.split('.')[1:]:
130
self.module = getattr(self.module, part)
139
self.failfast = failfast
140
self.catchbreak = catchbreak
141
self.verbosity = verbosity
143
self.defaultTest = defaultTest
144
self.listtests = False
145
self.load_list = None
146
self.testRunner = testRunner
147
self.testLoader = testLoader
149
if progName.endswith('%srun.py' % os.path.sep):
150
elements = progName.split(os.path.sep)
151
progName = '%s.run' % elements[-2]
153
progName = os.path.basename(argv[0])
154
self.progName = progName
157
# TODO: preserve existing suites (like testresources does in
158
# OptimisingTestSuite.add, but with a standard protocol).
159
# This is needed because the load_tests hook allows arbitrary
160
# suites, even if that is rarely used.
161
source = file(self.load_list, 'rb')
163
lines = source.readlines()
166
test_ids = set(line.strip() for line in lines)
167
filtered = unittest.TestSuite()
168
for test in iterate_tests(self.test):
169
if test.id() in test_ids:
170
filtered.addTest(test)
172
if not self.listtests:
175
for test in iterate_tests(self.test):
176
stdout.write('%s\n' % test.id())
178
def usageExit(self, msg=None):
181
usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '',
183
if self.failfast != False:
184
usage['failfast'] = FAILFAST
185
if self.catchbreak != False:
186
usage['catchbreak'] = CATCHBREAK
187
if self.buffer != False:
188
usage['buffer'] = BUFFEROUTPUT
189
print(self.USAGE % usage)
192
def parseArgs(self, argv):
193
if len(argv) > 1 and argv[1].lower() == 'discover':
194
self._do_discovery(argv[2:])
198
long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch', 'buffer',
199
'list', 'load-list=']
201
options, args = getopt.getopt(argv[1:], 'hHvqfcbl', long_opts)
202
for opt, value in options:
203
if opt in ('-h','-H','--help'):
205
if opt in ('-q','--quiet'):
207
if opt in ('-v','--verbose'):
209
if opt in ('-f','--failfast'):
210
if self.failfast is None:
212
# Should this raise an exception if -f is not valid?
213
if opt in ('-c','--catch'):
214
if self.catchbreak is None:
215
self.catchbreak = True
216
# Should this raise an exception if -c is not valid?
217
if opt in ('-b','--buffer'):
218
if self.buffer is None:
220
# Should this raise an exception if -b is not valid?
221
if opt in ('-l', '--list'):
222
self.listtests = True
223
if opt == '--load-list':
224
self.load_list = value
225
if len(args) == 0 and self.defaultTest is None:
226
# createTests will load tests from self.module
227
self.testNames = None
229
self.testNames = args
231
self.testNames = (self.defaultTest,)
234
self.usageExit(sys.exc_info()[1])
236
def createTests(self):
237
if self.testNames is None:
238
self.test = self.testLoader.loadTestsFromModule(self.module)
240
self.test = self.testLoader.loadTestsFromNames(self.testNames,
243
def _do_discovery(self, argv, Loader=defaultTestLoaderCls):
244
# handle command line args for test discovery
245
if not have_discover:
246
raise AssertionError("Unable to use discovery, must use python 2.7 "
247
"or greater, or install the discover package.")
248
self.progName = '%s discover' % self.progName
250
parser = optparse.OptionParser()
251
parser.prog = self.progName
252
parser.add_option('-v', '--verbose', dest='verbose', default=False,
253
help='Verbose output', action='store_true')
254
if self.failfast != False:
255
parser.add_option('-f', '--failfast', dest='failfast', default=False,
256
help='Stop on first fail or error',
258
if self.catchbreak != False:
259
parser.add_option('-c', '--catch', dest='catchbreak', default=False,
260
help='Catch ctrl-C and display results so far',
262
if self.buffer != False:
263
parser.add_option('-b', '--buffer', dest='buffer', default=False,
264
help='Buffer stdout and stderr during tests',
266
parser.add_option('-s', '--start-directory', dest='start', default='.',
267
help="Directory to start discovery ('.' default)")
268
parser.add_option('-p', '--pattern', dest='pattern', default='test*.py',
269
help="Pattern to match tests ('test*.py' default)")
270
parser.add_option('-t', '--top-level-directory', dest='top', default=None,
271
help='Top level directory of project (defaults to start directory)')
272
parser.add_option('-l', '--list', dest='listtests', default=False,
273
help='List tests rather than running them.')
274
parser.add_option('--load-list', dest='load_list', default=None,
275
help='Specify a filename containing the test ids to use.')
277
options, args = parser.parse_args(argv)
281
for name, value in zip(('start', 'pattern', 'top'), args):
282
setattr(options, name, value)
284
# only set options from the parsing here
285
# if they weren't set explicitly in the constructor
286
if self.failfast is None:
287
self.failfast = options.failfast
288
if self.catchbreak is None:
289
self.catchbreak = options.catchbreak
290
if self.buffer is None:
291
self.buffer = options.buffer
292
self.listtests = options.listtests
293
self.load_list = options.load_list
298
start_dir = options.start
299
pattern = options.pattern
300
top_level_dir = options.top
303
self.test = loader.discover(start_dir, pattern, top_level_dir)
307
and getattr(unittest, 'installHandler', None) is not None):
308
unittest.installHandler()
309
if self.testRunner is None:
310
self.testRunner = runner.TextTestRunner
311
if isinstance(self.testRunner, classtypes()):
313
testRunner = self.testRunner(verbosity=self.verbosity,
314
failfast=self.failfast,
317
# didn't accept the verbosity, buffer or failfast arguments
318
testRunner = self.testRunner()
320
# it is assumed to be a TestRunner instance
321
testRunner = self.testRunner
322
self.result = testRunner.run(self.test)
324
sys.exit(not self.result.wasSuccessful())
327
def main(argv, stdout):
328
runner = TestToolsTestRunner(stdout)
329
program = TestProgram(argv=argv, testRunner=runner, stdout=stdout)
331
if __name__ == '__main__':
332
main(sys.argv, sys.stdout)