blob: d09bccdf2c22c53d14a5485ab13ae91c3a7dabdb [file] [log] [blame]
Benjamin Petersonbed7d042009-07-19 21:01:52 +00001"""Unittest main program"""
2
3import sys
4import os
5import types
6
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
Benjamin Petersonbed7d042009-07-19 21:01:52 +000080class TestProgram(object):
81 """A command-line program that runs a set of tests; this is primarily
82 for making test modules conveniently executable.
83 """
Benjamin Petersoneab4b4c2010-03-22 02:53:52 +000084 USAGE = USAGE_FROM_MODULE
Michael Foord65b69a12010-03-27 13:25:41 +000085
86 # defaults for testing
Ezio Melotti60901872010-12-01 00:56:10 +000087 failfast = catchbreak = buffer = progName = warnings = None
Michael Foord65b69a12010-03-27 13:25:41 +000088
Michael Foord4a8cf3c2010-05-07 15:35:24 +000089 def __init__(self, module='__main__', defaultTest=None, argv=None,
90 testRunner=None, testLoader=loader.defaultTestLoader,
91 exit=True, verbosity=1, failfast=None, catchbreak=None,
Ezio Melotti60901872010-12-01 00:56:10 +000092 buffer=None, warnings=None):
Benjamin Petersonbed7d042009-07-19 21:01:52 +000093 if isinstance(module, str):
94 self.module = __import__(module)
95 for part in module.split('.')[1:]:
96 self.module = getattr(self.module, part)
97 else:
98 self.module = module
99 if argv is None:
100 argv = sys.argv
101
102 self.exit = exit
Benjamin Peterson8769fd82010-03-22 01:13:48 +0000103 self.failfast = failfast
Michael Foord65b69a12010-03-27 13:25:41 +0000104 self.catchbreak = catchbreak
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000105 self.verbosity = verbosity
Benjamin Petersonb48af542010-04-11 20:43:16 +0000106 self.buffer = buffer
Ezio Melotti60901872010-12-01 00:56:10 +0000107 if warnings is None and not sys.warnoptions:
108 # even if DreprecationWarnings are ignored by default
109 # print them anyway unless other warnings settings are
110 # specified by the warnings arg or the -W python flag
111 self.warnings = 'default'
112 else:
113 # here self.warnings is set either to the value passed
114 # to the warnings args or to None.
115 # If the user didn't pass a value self.warnings will
116 # be None. This means that the behavior is unchanged
117 # and depends on the values passed to -W.
118 self.warnings = warnings
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000119 self.defaultTest = defaultTest
120 self.testRunner = testRunner
121 self.testLoader = testLoader
122 self.progName = os.path.basename(argv[0])
123 self.parseArgs(argv)
124 self.runTests()
125
126 def usageExit(self, msg=None):
127 if msg:
128 print(msg)
Benjamin Petersonb48af542010-04-11 20:43:16 +0000129 usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '',
130 'buffer': ''}
Michael Foord65b69a12010-03-27 13:25:41 +0000131 if self.failfast != False:
132 usage['failfast'] = FAILFAST
133 if self.catchbreak != False:
134 usage['catchbreak'] = CATCHBREAK
Benjamin Petersonb48af542010-04-11 20:43:16 +0000135 if self.buffer != False:
136 usage['buffer'] = BUFFEROUTPUT
Michael Foord65b69a12010-03-27 13:25:41 +0000137 print(self.USAGE % usage)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000138 sys.exit(2)
139
140 def parseArgs(self, argv):
Michael Foord086f3082010-11-21 21:28:01 +0000141 if ((len(argv) > 1 and argv[1].lower() == 'discover') or
142 (len(argv) == 1 and self.module is None)):
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000143 self._do_discovery(argv[2:])
144 return
145
146 import getopt
Benjamin Petersonb48af542010-04-11 20:43:16 +0000147 long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch', 'buffer']
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000148 try:
Benjamin Petersonb48af542010-04-11 20:43:16 +0000149 options, args = getopt.getopt(argv[1:], 'hHvqfcb', long_opts)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000150 except getopt.error as msg:
151 self.usageExit(msg)
Michael Foordf100dbd2010-12-19 03:59:10 +0000152 return
153
154 for opt, value in options:
155 if opt in ('-h','-H','--help'):
156 self.usageExit()
157 if opt in ('-q','--quiet'):
158 self.verbosity = 0
159 if opt in ('-v','--verbose'):
160 self.verbosity = 2
161 if opt in ('-f','--failfast'):
162 if self.failfast is None:
163 self.failfast = True
164 # Should this raise an exception if -f is not valid?
165 if opt in ('-c','--catch'):
166 if self.catchbreak is None:
167 self.catchbreak = True
168 # Should this raise an exception if -c is not valid?
169 if opt in ('-b','--buffer'):
170 if self.buffer is None:
171 self.buffer = True
172 # Should this raise an exception if -b is not valid?
173
174 if len(args) == 0 and self.module is None:
175 # this allows "python -m unittest -v" to still work for
176 # test discovery. This means -c / -b / -v / -f options will
177 # be handled twice, which is harmless but not ideal.
178 self._do_discovery(argv[1:])
179 return
180
181 if len(args) == 0 and self.defaultTest is None:
182 # createTests will load tests from self.module
183 self.testNames = None
184 elif len(args) > 0:
185 self.testNames = _convert_names(args)
186 if __name__ == '__main__':
187 # to support python -m unittest ...
188 self.module = None
189 else:
190 self.testNames = (self.defaultTest,)
191 self.createTests()
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000192
193 def createTests(self):
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000194 if self.testNames is None:
195 self.test = self.testLoader.loadTestsFromModule(self.module)
196 else:
197 self.test = self.testLoader.loadTestsFromNames(self.testNames,
198 self.module)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000199
Michael Foord8fd396b2013-02-11 00:04:24 +0000200 def _do_discovery(self, argv, Loader=None):
201 if Loader is None:
202 Loader = self.testLoader
203
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000204 # handle command line args for test discovery
Michael Foordf707aa72010-05-10 20:23:58 +0000205 self.progName = '%s discover' % self.progName
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000206 import optparse
207 parser = optparse.OptionParser()
Michael Foordf707aa72010-05-10 20:23:58 +0000208 parser.prog = self.progName
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000209 parser.add_option('-v', '--verbose', dest='verbose', default=False,
210 help='Verbose output', action='store_true')
Michael Foord65b69a12010-03-27 13:25:41 +0000211 if self.failfast != False:
212 parser.add_option('-f', '--failfast', dest='failfast', default=False,
213 help='Stop on first fail or error',
214 action='store_true')
215 if self.catchbreak != False:
216 parser.add_option('-c', '--catch', dest='catchbreak', default=False,
217 help='Catch ctrl-C and display results so far',
218 action='store_true')
Benjamin Petersonb48af542010-04-11 20:43:16 +0000219 if self.buffer != False:
220 parser.add_option('-b', '--buffer', dest='buffer', default=False,
221 help='Buffer stdout and stderr during tests',
222 action='store_true')
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000223 parser.add_option('-s', '--start-directory', dest='start', default='.',
224 help="Directory to start discovery ('.' default)")
225 parser.add_option('-p', '--pattern', dest='pattern', default='test*.py',
226 help="Pattern to match tests ('test*.py' default)")
227 parser.add_option('-t', '--top-level-directory', dest='top', default=None,
228 help='Top level directory of project (defaults to start directory)')
229
230 options, args = parser.parse_args(argv)
231 if len(args) > 3:
232 self.usageExit()
233
234 for name, value in zip(('start', 'pattern', 'top'), args):
235 setattr(options, name, value)
236
Michael Foord65b69a12010-03-27 13:25:41 +0000237 # only set options from the parsing here
238 # if they weren't set explicitly in the constructor
239 if self.failfast is None:
240 self.failfast = options.failfast
241 if self.catchbreak is None:
242 self.catchbreak = options.catchbreak
Benjamin Petersonb48af542010-04-11 20:43:16 +0000243 if self.buffer is None:
244 self.buffer = options.buffer
Michael Foord65b69a12010-03-27 13:25:41 +0000245
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000246 if options.verbose:
247 self.verbosity = 2
248
249 start_dir = options.start
250 pattern = options.pattern
251 top_level_dir = options.top
252
253 loader = Loader()
254 self.test = loader.discover(start_dir, pattern, top_level_dir)
255
256 def runTests(self):
Michael Foord65b69a12010-03-27 13:25:41 +0000257 if self.catchbreak:
258 installHandler()
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000259 if self.testRunner is None:
260 self.testRunner = runner.TextTestRunner
261 if isinstance(self.testRunner, type):
262 try:
Benjamin Peterson8769fd82010-03-22 01:13:48 +0000263 testRunner = self.testRunner(verbosity=self.verbosity,
Benjamin Petersonb48af542010-04-11 20:43:16 +0000264 failfast=self.failfast,
Ezio Melotti60901872010-12-01 00:56:10 +0000265 buffer=self.buffer,
266 warnings=self.warnings)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000267 except TypeError:
Benjamin Petersonb48af542010-04-11 20:43:16 +0000268 # didn't accept the verbosity, buffer or failfast arguments
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000269 testRunner = self.testRunner()
270 else:
271 # it is assumed to be a TestRunner instance
272 testRunner = self.testRunner
273 self.result = testRunner.run(self.test)
274 if self.exit:
275 sys.exit(not self.result.wasSuccessful())
276
277main = TestProgram