blob: edb141ac915a8618f90ae4cc370cd6906fd29d21 [file] [log] [blame]
Benjamin Petersonbed7d042009-07-19 21:01:52 +00001"""Unittest main program"""
2
3import sys
Michael Foord5f99ced2012-03-12 13:53:04 -07004import optparse
Benjamin Petersonbed7d042009-07-19 21:01:52 +00005import os
Benjamin Petersonbed7d042009-07-19 21:01:52 +00006
7from . import loader, runner
Michael Foord65b69a12010-03-27 13:25:41 +00008from .signals import installHandler
Benjamin Petersonbed7d042009-07-19 21:01:52 +00009
Benjamin Petersondccc1fc2010-03-22 00:15:53 +000010__unittest = True
11
Benjamin Petersonb48af542010-04-11 20:43:16 +000012FAILFAST = " -f, --failfast Stop on first failure\n"
13CATCHBREAK = " -c, --catch Catch control-C and display results\n"
14BUFFEROUTPUT = " -b, --buffer Buffer stdout and stderr during test runs\n"
Michael Foord65b69a12010-03-27 13:25:41 +000015
Benjamin Petersonbed7d042009-07-19 21:01:52 +000016USAGE_AS_MAIN = """\
17Usage: %(progName)s [options] [tests]
18
19Options:
20 -h, --help Show this message
21 -v, --verbose Verbose output
22 -q, --quiet Minimal output
Benjamin Petersonb48af542010-04-11 20:43:16 +000023%(failfast)s%(catchbreak)s%(buffer)s
Benjamin Petersonbed7d042009-07-19 21:01:52 +000024Examples:
Michael Foordf707aa72010-05-10 20:23:58 +000025 %(progName)s test_module - run tests from test_module
26 %(progName)s module.TestClass - run tests from module.TestClass
27 %(progName)s module.Class.test_method - run specified test method
Benjamin Petersonbed7d042009-07-19 21:01:52 +000028
29[tests] can be a list of any number of test modules, classes and test
30methods.
31
32Alternative Usage: %(progName)s discover [options]
33
34Options:
35 -v, --verbose Verbose output
Benjamin Petersonb48af542010-04-11 20:43:16 +000036%(failfast)s%(catchbreak)s%(buffer)s -s directory Directory to start discovery ('.' default)
Benjamin Petersonbed7d042009-07-19 21:01:52 +000037 -p pattern Pattern to match test files ('test*.py' default)
38 -t directory Top level directory of project (default to
39 start directory)
40
41For test discovery all test modules must be importable from the top
42level directory of the project.
43"""
44
45USAGE_FROM_MODULE = """\
46Usage: %(progName)s [options] [test] [...]
47
48Options:
49 -h, --help Show this message
50 -v, --verbose Verbose output
51 -q, --quiet Minimal output
Benjamin Petersonb48af542010-04-11 20:43:16 +000052%(failfast)s%(catchbreak)s%(buffer)s
Benjamin Petersonbed7d042009-07-19 21:01:52 +000053Examples:
54 %(progName)s - run default set of tests
55 %(progName)s MyTestSuite - run suite 'MyTestSuite'
56 %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething
57 %(progName)s MyTestCase - run all 'test*' test methods
58 in MyTestCase
59"""
60
Michael Foord37d120a2010-12-04 01:11:21 +000061def _convert_name(name):
62 # on Linux / Mac OS X 'foo.PY' is not importable, but on
63 # Windows it is. Simpler to do a case insensitive match
64 # a better check would be to check that the name is a
65 # valid Python module name.
66 if os.path.isfile(name) and name.lower().endswith('.py'):
67 if os.path.isabs(name):
68 rel_path = os.path.relpath(name, os.getcwd())
69 if os.path.isabs(rel_path) or rel_path.startswith(os.pardir):
70 return name
71 name = rel_path
72 # on Windows both '\' and '/' are used as path
73 # separators. Better to replace both than rely on os.path.sep
74 return name[:-3].replace('\\', '.').replace('/', '.')
75 return name
Michael Foord65b69a12010-03-27 13:25:41 +000076
Michael Foord37d120a2010-12-04 01:11:21 +000077def _convert_names(names):
78 return [_convert_name(name) for name in names]
Michael Foord65b69a12010-03-27 13:25:41 +000079
Michael Foord5f99ced2012-03-12 13:53:04 -070080
Benjamin Petersonbed7d042009-07-19 21:01:52 +000081class TestProgram(object):
82 """A command-line program that runs a set of tests; this is primarily
83 for making test modules conveniently executable.
84 """
Benjamin Petersoneab4b4c2010-03-22 02:53:52 +000085 USAGE = USAGE_FROM_MODULE
Michael Foord65b69a12010-03-27 13:25:41 +000086
87 # defaults for testing
Ezio Melotti60901872010-12-01 00:56:10 +000088 failfast = catchbreak = buffer = progName = warnings = None
Michael Foord65b69a12010-03-27 13:25:41 +000089
Michael Foord4a8cf3c2010-05-07 15:35:24 +000090 def __init__(self, module='__main__', defaultTest=None, argv=None,
91 testRunner=None, testLoader=loader.defaultTestLoader,
92 exit=True, verbosity=1, failfast=None, catchbreak=None,
Ezio Melotti60901872010-12-01 00:56:10 +000093 buffer=None, warnings=None):
Benjamin Petersonbed7d042009-07-19 21:01:52 +000094 if isinstance(module, str):
95 self.module = __import__(module)
96 for part in module.split('.')[1:]:
97 self.module = getattr(self.module, part)
98 else:
99 self.module = module
100 if argv is None:
101 argv = sys.argv
102
103 self.exit = exit
Benjamin Peterson8769fd82010-03-22 01:13:48 +0000104 self.failfast = failfast
Michael Foord65b69a12010-03-27 13:25:41 +0000105 self.catchbreak = catchbreak
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000106 self.verbosity = verbosity
Benjamin Petersonb48af542010-04-11 20:43:16 +0000107 self.buffer = buffer
Ezio Melotti60901872010-12-01 00:56:10 +0000108 if warnings is None and not sys.warnoptions:
109 # even if DreprecationWarnings are ignored by default
110 # print them anyway unless other warnings settings are
111 # specified by the warnings arg or the -W python flag
112 self.warnings = 'default'
113 else:
114 # here self.warnings is set either to the value passed
115 # to the warnings args or to None.
116 # If the user didn't pass a value self.warnings will
117 # be None. This means that the behavior is unchanged
118 # and depends on the values passed to -W.
119 self.warnings = warnings
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000120 self.defaultTest = defaultTest
121 self.testRunner = testRunner
122 self.testLoader = testLoader
123 self.progName = os.path.basename(argv[0])
124 self.parseArgs(argv)
125 self.runTests()
126
127 def usageExit(self, msg=None):
128 if msg:
129 print(msg)
Benjamin Petersonb48af542010-04-11 20:43:16 +0000130 usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '',
131 'buffer': ''}
Michael Foord65b69a12010-03-27 13:25:41 +0000132 if self.failfast != False:
133 usage['failfast'] = FAILFAST
134 if self.catchbreak != False:
135 usage['catchbreak'] = CATCHBREAK
Benjamin Petersonb48af542010-04-11 20:43:16 +0000136 if self.buffer != False:
137 usage['buffer'] = BUFFEROUTPUT
Michael Foord65b69a12010-03-27 13:25:41 +0000138 print(self.USAGE % usage)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000139 sys.exit(2)
140
141 def parseArgs(self, argv):
Michael Foord086f3082010-11-21 21:28:01 +0000142 if ((len(argv) > 1 and argv[1].lower() == 'discover') or
143 (len(argv) == 1 and self.module is None)):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000144 self._do_discovery(argv[2:])
145 return
146
Michael Foord5f99ced2012-03-12 13:53:04 -0700147 parser = self._getOptParser()
148 options, args = parser.parse_args(argv[1:])
149 self._setAttributesFromOptions(options)
Michael Foordf100dbd2010-12-19 03:59:10 +0000150
151 if len(args) == 0 and self.module is None:
152 # this allows "python -m unittest -v" to still work for
153 # test discovery. This means -c / -b / -v / -f options will
154 # be handled twice, which is harmless but not ideal.
155 self._do_discovery(argv[1:])
156 return
157
158 if len(args) == 0 and self.defaultTest is None:
159 # createTests will load tests from self.module
160 self.testNames = None
161 elif len(args) > 0:
162 self.testNames = _convert_names(args)
163 if __name__ == '__main__':
164 # to support python -m unittest ...
165 self.module = None
166 else:
Petri Lehtinen20054472013-02-23 18:52:51 +0100167 if isinstance(self.defaultTest, str):
168 self.testNames = (self.defaultTest,)
169 else:
170 self.testNames = list(self.defaultTest)
Michael Foordf100dbd2010-12-19 03:59:10 +0000171 self.createTests()
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000172
173 def createTests(self):
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000174 if self.testNames is None:
175 self.test = self.testLoader.loadTestsFromModule(self.module)
176 else:
177 self.test = self.testLoader.loadTestsFromNames(self.testNames,
178 self.module)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000179
Michael Foord5f99ced2012-03-12 13:53:04 -0700180 def _getOptParser(self):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000181 import optparse
182 parser = optparse.OptionParser()
Michael Foordf707aa72010-05-10 20:23:58 +0000183 parser.prog = self.progName
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000184 parser.add_option('-v', '--verbose', dest='verbose', default=False,
185 help='Verbose output', action='store_true')
Michael Foord5f99ced2012-03-12 13:53:04 -0700186 parser.add_option('-q', '--quiet', dest='quiet', default=False,
187 help='Quiet output', action='store_true')
188
Michael Foord65b69a12010-03-27 13:25:41 +0000189 if self.failfast != False:
190 parser.add_option('-f', '--failfast', dest='failfast', default=False,
191 help='Stop on first fail or error',
192 action='store_true')
193 if self.catchbreak != False:
194 parser.add_option('-c', '--catch', dest='catchbreak', default=False,
195 help='Catch ctrl-C and display results so far',
196 action='store_true')
Benjamin Petersonb48af542010-04-11 20:43:16 +0000197 if self.buffer != False:
198 parser.add_option('-b', '--buffer', dest='buffer', default=False,
199 help='Buffer stdout and stderr during tests',
200 action='store_true')
Michael Foord5f99ced2012-03-12 13:53:04 -0700201 return parser
202
203 def _setAttributesFromOptions(self, options):
204 # only set options from the parsing here
205 # if they weren't set explicitly in the constructor
206 if self.failfast is None:
207 self.failfast = options.failfast
208 if self.catchbreak is None:
209 self.catchbreak = options.catchbreak
210 if self.buffer is None:
211 self.buffer = options.buffer
212
213 if options.verbose:
214 self.verbosity = 2
215 elif options.quiet:
216 self.verbosity = 0
217
Michael Foordb36c70c2012-03-12 15:24:46 -0700218 def _addDiscoveryOptions(self, parser):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000219 parser.add_option('-s', '--start-directory', dest='start', default='.',
220 help="Directory to start discovery ('.' default)")
221 parser.add_option('-p', '--pattern', dest='pattern', default='test*.py',
222 help="Pattern to match tests ('test*.py' default)")
223 parser.add_option('-t', '--top-level-directory', dest='top', default=None,
224 help='Top level directory of project (defaults to start directory)')
225
Michael Foorda23a39c2013-02-11 00:18:07 +0000226 def _do_discovery(self, argv, Loader=None):
227 if Loader is None:
Michael Foordb71b8ec2013-02-11 13:29:58 +0000228 Loader = lambda: self.testLoader
Michael Foorda23a39c2013-02-11 00:18:07 +0000229
Michael Foordb36c70c2012-03-12 15:24:46 -0700230 # handle command line args for test discovery
231 self.progName = '%s discover' % self.progName
232 parser = self._getOptParser()
233 self._addDiscoveryOptions(parser)
234
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000235 options, args = parser.parse_args(argv)
236 if len(args) > 3:
237 self.usageExit()
238
239 for name, value in zip(('start', 'pattern', 'top'), args):
240 setattr(options, name, value)
241
Michael Foord5f99ced2012-03-12 13:53:04 -0700242 self._setAttributesFromOptions(options)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000243
244 start_dir = options.start
245 pattern = options.pattern
246 top_level_dir = options.top
247
248 loader = Loader()
249 self.test = loader.discover(start_dir, pattern, top_level_dir)
250
251 def runTests(self):
Michael Foord65b69a12010-03-27 13:25:41 +0000252 if self.catchbreak:
253 installHandler()
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000254 if self.testRunner is None:
255 self.testRunner = runner.TextTestRunner
256 if isinstance(self.testRunner, type):
257 try:
Benjamin Peterson8769fd82010-03-22 01:13:48 +0000258 testRunner = self.testRunner(verbosity=self.verbosity,
Benjamin Petersonb48af542010-04-11 20:43:16 +0000259 failfast=self.failfast,
Ezio Melotti60901872010-12-01 00:56:10 +0000260 buffer=self.buffer,
261 warnings=self.warnings)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000262 except TypeError:
Benjamin Petersonb48af542010-04-11 20:43:16 +0000263 # didn't accept the verbosity, buffer or failfast arguments
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000264 testRunner = self.testRunner()
265 else:
266 # it is assumed to be a TestRunner instance
267 testRunner = self.testRunner
268 self.result = testRunner.run(self.test)
269 if self.exit:
270 sys.exit(not self.result.wasSuccessful())
271
272main = TestProgram