blob: e62469aa2a170f2bc1894adc232cf46cdeb2b4d9 [file] [log] [blame]
Benjamin Petersonbed7d042009-07-19 21:01:52 +00001"""Unittest main program"""
2
3import sys
Serhiy Storchakade2800f2013-08-29 12:37:28 +03004import argparse
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
Serhiy Storchakade2800f2013-08-29 12:37:28 +030012MAIN_EXAMPLES = """\
Benjamin Petersonbed7d042009-07-19 21:01:52 +000013Examples:
Serhiy Storchakade2800f2013-08-29 12:37:28 +030014 %(prog)s test_module - run tests from test_module
15 %(prog)s module.TestClass - run tests from module.TestClass
16 %(prog)s module.Class.test_method - run specified test method
Louie Luf7e62cf2017-04-20 11:46:59 +080017 %(prog)s path/to/test_file.py - run tests from test_file.py
Benjamin Petersonbed7d042009-07-19 21:01:52 +000018"""
19
Serhiy Storchakade2800f2013-08-29 12:37:28 +030020MODULE_EXAMPLES = """\
Benjamin Petersonbed7d042009-07-19 21:01:52 +000021Examples:
Serhiy Storchakade2800f2013-08-29 12:37:28 +030022 %(prog)s - run default set of tests
23 %(prog)s MyTestSuite - run suite 'MyTestSuite'
24 %(prog)s MyTestCase.testSomething - run MyTestCase.testSomething
25 %(prog)s MyTestCase - run all 'test*' test methods
26 in MyTestCase
Benjamin Petersonbed7d042009-07-19 21:01:52 +000027"""
28
Michael Foord37d120a2010-12-04 01:11:21 +000029def _convert_name(name):
30 # on Linux / Mac OS X 'foo.PY' is not importable, but on
31 # Windows it is. Simpler to do a case insensitive match
32 # a better check would be to check that the name is a
33 # valid Python module name.
34 if os.path.isfile(name) and name.lower().endswith('.py'):
35 if os.path.isabs(name):
36 rel_path = os.path.relpath(name, os.getcwd())
37 if os.path.isabs(rel_path) or rel_path.startswith(os.pardir):
38 return name
39 name = rel_path
40 # on Windows both '\' and '/' are used as path
41 # separators. Better to replace both than rely on os.path.sep
42 return name[:-3].replace('\\', '.').replace('/', '.')
43 return name
Michael Foord65b69a12010-03-27 13:25:41 +000044
Michael Foord37d120a2010-12-04 01:11:21 +000045def _convert_names(names):
46 return [_convert_name(name) for name in names]
Michael Foord65b69a12010-03-27 13:25:41 +000047
Michael Foord5f99ced2012-03-12 13:53:04 -070048
Jonas Haag5b48dc62017-11-25 16:23:52 +010049def _convert_select_pattern(pattern):
50 if not '*' in pattern:
51 pattern = '*%s*' % pattern
52 return pattern
53
54
Benjamin Petersonbed7d042009-07-19 21:01:52 +000055class TestProgram(object):
56 """A command-line program that runs a set of tests; this is primarily
57 for making test modules conveniently executable.
58 """
Michael Foord65b69a12010-03-27 13:25:41 +000059 # defaults for testing
Serhiy Storchakade2800f2013-08-29 12:37:28 +030060 module=None
61 verbosity = 1
Jonas Haag5b48dc62017-11-25 16:23:52 +010062 failfast = catchbreak = buffer = progName = warnings = testNamePatterns = None
Serhiy Storchakade2800f2013-08-29 12:37:28 +030063 _discovery_parser = None
Michael Foord65b69a12010-03-27 13:25:41 +000064
Michael Foord4a8cf3c2010-05-07 15:35:24 +000065 def __init__(self, module='__main__', defaultTest=None, argv=None,
66 testRunner=None, testLoader=loader.defaultTestLoader,
67 exit=True, verbosity=1, failfast=None, catchbreak=None,
Robert Collinsf0c819a2015-03-06 13:46:35 +130068 buffer=None, warnings=None, *, tb_locals=False):
Benjamin Petersonbed7d042009-07-19 21:01:52 +000069 if isinstance(module, str):
70 self.module = __import__(module)
71 for part in module.split('.')[1:]:
72 self.module = getattr(self.module, part)
73 else:
74 self.module = module
75 if argv is None:
76 argv = sys.argv
77
78 self.exit = exit
Benjamin Peterson8769fd82010-03-22 01:13:48 +000079 self.failfast = failfast
Michael Foord65b69a12010-03-27 13:25:41 +000080 self.catchbreak = catchbreak
Benjamin Petersonbed7d042009-07-19 21:01:52 +000081 self.verbosity = verbosity
Benjamin Petersonb48af542010-04-11 20:43:16 +000082 self.buffer = buffer
Robert Collinsf0c819a2015-03-06 13:46:35 +130083 self.tb_locals = tb_locals
Ezio Melotti60901872010-12-01 00:56:10 +000084 if warnings is None and not sys.warnoptions:
Robert Collins7e28df92015-03-06 11:35:29 +130085 # even if DeprecationWarnings are ignored by default
Ezio Melotti60901872010-12-01 00:56:10 +000086 # print them anyway unless other warnings settings are
87 # specified by the warnings arg or the -W python flag
88 self.warnings = 'default'
89 else:
90 # here self.warnings is set either to the value passed
91 # to the warnings args or to None.
92 # If the user didn't pass a value self.warnings will
93 # be None. This means that the behavior is unchanged
94 # and depends on the values passed to -W.
95 self.warnings = warnings
Benjamin Petersonbed7d042009-07-19 21:01:52 +000096 self.defaultTest = defaultTest
97 self.testRunner = testRunner
98 self.testLoader = testLoader
99 self.progName = os.path.basename(argv[0])
100 self.parseArgs(argv)
101 self.runTests()
102
103 def usageExit(self, msg=None):
104 if msg:
105 print(msg)
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300106 if self._discovery_parser is None:
107 self._initArgParsers()
108 self._print_help()
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000109 sys.exit(2)
110
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300111 def _print_help(self, *args, **kwargs):
112 if self.module is None:
113 print(self._main_parser.format_help())
114 print(MAIN_EXAMPLES % {'prog': self.progName})
115 self._discovery_parser.print_help()
116 else:
117 print(self._main_parser.format_help())
118 print(MODULE_EXAMPLES % {'prog': self.progName})
119
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000120 def parseArgs(self, argv):
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300121 self._initArgParsers()
122 if self.module is None:
123 if len(argv) > 1 and argv[1].lower() == 'discover':
124 self._do_discovery(argv[2:])
125 return
126 self._main_parser.parse_args(argv[1:], self)
127 if not self.tests:
128 # this allows "python -m unittest -v" to still work for
129 # test discovery.
130 self._do_discovery([])
131 return
132 else:
133 self._main_parser.parse_args(argv[1:], self)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000134
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300135 if self.tests:
136 self.testNames = _convert_names(self.tests)
Michael Foordf100dbd2010-12-19 03:59:10 +0000137 if __name__ == '__main__':
138 # to support python -m unittest ...
139 self.module = None
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300140 elif self.defaultTest is None:
141 # createTests will load tests from self.module
142 self.testNames = None
143 elif isinstance(self.defaultTest, str):
144 self.testNames = (self.defaultTest,)
Michael Foordf100dbd2010-12-19 03:59:10 +0000145 else:
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300146 self.testNames = list(self.defaultTest)
Michael Foordf100dbd2010-12-19 03:59:10 +0000147 self.createTests()
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000148
Jonas Haag5b48dc62017-11-25 16:23:52 +0100149 def createTests(self, from_discovery=False, Loader=None):
150 if self.testNamePatterns:
151 self.testLoader.testNamePatterns = self.testNamePatterns
152 if from_discovery:
153 loader = self.testLoader if Loader is None else Loader()
154 self.test = loader.discover(self.start, self.pattern, self.top)
155 elif self.testNames is None:
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000156 self.test = self.testLoader.loadTestsFromModule(self.module)
157 else:
158 self.test = self.testLoader.loadTestsFromNames(self.testNames,
159 self.module)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000160
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300161 def _initArgParsers(self):
162 parent_parser = self._getParentArgParser()
163 self._main_parser = self._getMainArgParser(parent_parser)
164 self._discovery_parser = self._getDiscoveryArgParser(parent_parser)
Michael Foord5f99ced2012-03-12 13:53:04 -0700165
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300166 def _getParentArgParser(self):
167 parser = argparse.ArgumentParser(add_help=False)
168
169 parser.add_argument('-v', '--verbose', dest='verbosity',
170 action='store_const', const=2,
171 help='Verbose output')
172 parser.add_argument('-q', '--quiet', dest='verbosity',
173 action='store_const', const=0,
174 help='Quiet output')
Robert Collinsf0c819a2015-03-06 13:46:35 +1300175 parser.add_argument('--locals', dest='tb_locals',
176 action='store_true',
177 help='Show local variables in tracebacks')
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300178 if self.failfast is None:
179 parser.add_argument('-f', '--failfast', dest='failfast',
180 action='store_true',
181 help='Stop on first fail or error')
182 self.failfast = False
183 if self.catchbreak is None:
184 parser.add_argument('-c', '--catch', dest='catchbreak',
185 action='store_true',
Serhiy Storchaka0424eaf2015-09-12 17:45:25 +0300186 help='Catch Ctrl-C and display results so far')
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300187 self.catchbreak = False
188 if self.buffer is None:
189 parser.add_argument('-b', '--buffer', dest='buffer',
190 action='store_true',
191 help='Buffer stdout and stderr during tests')
192 self.buffer = False
Jonas Haag5b48dc62017-11-25 16:23:52 +0100193 if self.testNamePatterns is None:
194 parser.add_argument('-k', dest='testNamePatterns',
195 action='append', type=_convert_select_pattern,
196 help='Only run tests which match the given substring')
197 self.testNamePatterns = []
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300198
Michael Foord5f99ced2012-03-12 13:53:04 -0700199 return parser
200
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300201 def _getMainArgParser(self, parent):
202 parser = argparse.ArgumentParser(parents=[parent])
203 parser.prog = self.progName
204 parser.print_help = self._print_help
Michael Foord5f99ced2012-03-12 13:53:04 -0700205
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300206 parser.add_argument('tests', nargs='*',
207 help='a list of any number of test modules, '
208 'classes and test methods.')
Michael Foord5f99ced2012-03-12 13:53:04 -0700209
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300210 return parser
211
212 def _getDiscoveryArgParser(self, parent):
213 parser = argparse.ArgumentParser(parents=[parent])
214 parser.prog = '%s discover' % self.progName
215 parser.epilog = ('For test discovery all test modules must be '
216 'importable from the top level directory of the '
217 'project.')
218
219 parser.add_argument('-s', '--start-directory', dest='start',
220 help="Directory to start discovery ('.' default)")
221 parser.add_argument('-p', '--pattern', dest='pattern',
222 help="Pattern to match tests ('test*.py' default)")
223 parser.add_argument('-t', '--top-level-directory', dest='top',
224 help='Top level directory of project (defaults to '
225 'start directory)')
226 for arg in ('start', 'pattern', 'top'):
227 parser.add_argument(arg, nargs='?',
228 default=argparse.SUPPRESS,
229 help=argparse.SUPPRESS)
230
231 return parser
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000232
Michael Foorda23a39c2013-02-11 00:18:07 +0000233 def _do_discovery(self, argv, Loader=None):
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300234 self.start = '.'
235 self.pattern = 'test*.py'
236 self.top = None
237 if argv is not None:
238 # handle command line args for test discovery
239 if self._discovery_parser is None:
240 # for testing
241 self._initArgParsers()
242 self._discovery_parser.parse_args(argv, self)
Michael Foorda23a39c2013-02-11 00:18:07 +0000243
Jonas Haag5b48dc62017-11-25 16:23:52 +0100244 self.createTests(from_discovery=True, Loader=Loader)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000245
246 def runTests(self):
Michael Foord65b69a12010-03-27 13:25:41 +0000247 if self.catchbreak:
248 installHandler()
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000249 if self.testRunner is None:
250 self.testRunner = runner.TextTestRunner
251 if isinstance(self.testRunner, type):
252 try:
Robert Collinsf0c819a2015-03-06 13:46:35 +1300253 try:
254 testRunner = self.testRunner(verbosity=self.verbosity,
255 failfast=self.failfast,
256 buffer=self.buffer,
257 warnings=self.warnings,
258 tb_locals=self.tb_locals)
259 except TypeError:
260 # didn't accept the tb_locals argument
261 testRunner = self.testRunner(verbosity=self.verbosity,
262 failfast=self.failfast,
263 buffer=self.buffer,
264 warnings=self.warnings)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000265 except TypeError:
Benjamin Petersonb48af542010-04-11 20:43:16 +0000266 # didn't accept the verbosity, buffer or failfast arguments
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000267 testRunner = self.testRunner()
268 else:
269 # it is assumed to be a TestRunner instance
270 testRunner = self.testRunner
271 self.result = testRunner.run(self.test)
272 if self.exit:
273 sys.exit(not self.result.wasSuccessful())
274
275main = TestProgram