blob: 180df8676e5f17e1f942a3ae7c0fee8cc5fcef32 [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
Benjamin Petersonbed7d042009-07-19 21:01:52 +000017"""
18
Serhiy Storchakade2800f2013-08-29 12:37:28 +030019MODULE_EXAMPLES = """\
Benjamin Petersonbed7d042009-07-19 21:01:52 +000020Examples:
Serhiy Storchakade2800f2013-08-29 12:37:28 +030021 %(prog)s - run default set of tests
22 %(prog)s MyTestSuite - run suite 'MyTestSuite'
23 %(prog)s MyTestCase.testSomething - run MyTestCase.testSomething
24 %(prog)s MyTestCase - run all 'test*' test methods
25 in MyTestCase
Benjamin Petersonbed7d042009-07-19 21:01:52 +000026"""
27
Michael Foord37d120a2010-12-04 01:11:21 +000028def _convert_name(name):
29 # on Linux / Mac OS X 'foo.PY' is not importable, but on
30 # Windows it is. Simpler to do a case insensitive match
31 # a better check would be to check that the name is a
32 # valid Python module name.
33 if os.path.isfile(name) and name.lower().endswith('.py'):
34 if os.path.isabs(name):
35 rel_path = os.path.relpath(name, os.getcwd())
36 if os.path.isabs(rel_path) or rel_path.startswith(os.pardir):
37 return name
38 name = rel_path
39 # on Windows both '\' and '/' are used as path
40 # separators. Better to replace both than rely on os.path.sep
41 return name[:-3].replace('\\', '.').replace('/', '.')
42 return name
Michael Foord65b69a12010-03-27 13:25:41 +000043
Michael Foord37d120a2010-12-04 01:11:21 +000044def _convert_names(names):
45 return [_convert_name(name) for name in names]
Michael Foord65b69a12010-03-27 13:25:41 +000046
Michael Foord5f99ced2012-03-12 13:53:04 -070047
Benjamin Petersonbed7d042009-07-19 21:01:52 +000048class TestProgram(object):
49 """A command-line program that runs a set of tests; this is primarily
50 for making test modules conveniently executable.
51 """
Michael Foord65b69a12010-03-27 13:25:41 +000052 # defaults for testing
Serhiy Storchakade2800f2013-08-29 12:37:28 +030053 module=None
54 verbosity = 1
Ezio Melotti60901872010-12-01 00:56:10 +000055 failfast = catchbreak = buffer = progName = warnings = None
Serhiy Storchakade2800f2013-08-29 12:37:28 +030056 _discovery_parser = None
Michael Foord65b69a12010-03-27 13:25:41 +000057
Michael Foord4a8cf3c2010-05-07 15:35:24 +000058 def __init__(self, module='__main__', defaultTest=None, argv=None,
59 testRunner=None, testLoader=loader.defaultTestLoader,
60 exit=True, verbosity=1, failfast=None, catchbreak=None,
Ezio Melotti60901872010-12-01 00:56:10 +000061 buffer=None, warnings=None):
Benjamin Petersonbed7d042009-07-19 21:01:52 +000062 if isinstance(module, str):
63 self.module = __import__(module)
64 for part in module.split('.')[1:]:
65 self.module = getattr(self.module, part)
66 else:
67 self.module = module
68 if argv is None:
69 argv = sys.argv
70
71 self.exit = exit
Benjamin Peterson8769fd82010-03-22 01:13:48 +000072 self.failfast = failfast
Michael Foord65b69a12010-03-27 13:25:41 +000073 self.catchbreak = catchbreak
Benjamin Petersonbed7d042009-07-19 21:01:52 +000074 self.verbosity = verbosity
Benjamin Petersonb48af542010-04-11 20:43:16 +000075 self.buffer = buffer
Ezio Melotti60901872010-12-01 00:56:10 +000076 if warnings is None and not sys.warnoptions:
77 # even if DreprecationWarnings are ignored by default
78 # print them anyway unless other warnings settings are
79 # specified by the warnings arg or the -W python flag
80 self.warnings = 'default'
81 else:
82 # here self.warnings is set either to the value passed
83 # to the warnings args or to None.
84 # If the user didn't pass a value self.warnings will
85 # be None. This means that the behavior is unchanged
86 # and depends on the values passed to -W.
87 self.warnings = warnings
Benjamin Petersonbed7d042009-07-19 21:01:52 +000088 self.defaultTest = defaultTest
89 self.testRunner = testRunner
90 self.testLoader = testLoader
91 self.progName = os.path.basename(argv[0])
92 self.parseArgs(argv)
93 self.runTests()
94
95 def usageExit(self, msg=None):
96 if msg:
97 print(msg)
Serhiy Storchakade2800f2013-08-29 12:37:28 +030098 if self._discovery_parser is None:
99 self._initArgParsers()
100 self._print_help()
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000101 sys.exit(2)
102
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300103 def _print_help(self, *args, **kwargs):
104 if self.module is None:
105 print(self._main_parser.format_help())
106 print(MAIN_EXAMPLES % {'prog': self.progName})
107 self._discovery_parser.print_help()
108 else:
109 print(self._main_parser.format_help())
110 print(MODULE_EXAMPLES % {'prog': self.progName})
111
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000112 def parseArgs(self, argv):
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300113 self._initArgParsers()
114 if self.module is None:
115 if len(argv) > 1 and argv[1].lower() == 'discover':
116 self._do_discovery(argv[2:])
117 return
118 self._main_parser.parse_args(argv[1:], self)
119 if not self.tests:
120 # this allows "python -m unittest -v" to still work for
121 # test discovery.
122 self._do_discovery([])
123 return
124 else:
125 self._main_parser.parse_args(argv[1:], self)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000126
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300127 if self.tests:
128 self.testNames = _convert_names(self.tests)
Michael Foordf100dbd2010-12-19 03:59:10 +0000129 if __name__ == '__main__':
130 # to support python -m unittest ...
131 self.module = None
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300132 elif self.defaultTest is None:
133 # createTests will load tests from self.module
134 self.testNames = None
135 elif isinstance(self.defaultTest, str):
136 self.testNames = (self.defaultTest,)
Michael Foordf100dbd2010-12-19 03:59:10 +0000137 else:
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300138 self.testNames = list(self.defaultTest)
Michael Foordf100dbd2010-12-19 03:59:10 +0000139 self.createTests()
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000140
141 def createTests(self):
Benjamin Peterson4ac9ce42009-10-04 14:49:41 +0000142 if self.testNames is None:
143 self.test = self.testLoader.loadTestsFromModule(self.module)
144 else:
145 self.test = self.testLoader.loadTestsFromNames(self.testNames,
146 self.module)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000147
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300148 def _initArgParsers(self):
149 parent_parser = self._getParentArgParser()
150 self._main_parser = self._getMainArgParser(parent_parser)
151 self._discovery_parser = self._getDiscoveryArgParser(parent_parser)
Michael Foord5f99ced2012-03-12 13:53:04 -0700152
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300153 def _getParentArgParser(self):
154 parser = argparse.ArgumentParser(add_help=False)
155
156 parser.add_argument('-v', '--verbose', dest='verbosity',
157 action='store_const', const=2,
158 help='Verbose output')
159 parser.add_argument('-q', '--quiet', dest='verbosity',
160 action='store_const', const=0,
161 help='Quiet output')
162
163 if self.failfast is None:
164 parser.add_argument('-f', '--failfast', dest='failfast',
165 action='store_true',
166 help='Stop on first fail or error')
167 self.failfast = False
168 if self.catchbreak is None:
169 parser.add_argument('-c', '--catch', dest='catchbreak',
170 action='store_true',
171 help='Catch ctrl-C and display results so far')
172 self.catchbreak = False
173 if self.buffer is None:
174 parser.add_argument('-b', '--buffer', dest='buffer',
175 action='store_true',
176 help='Buffer stdout and stderr during tests')
177 self.buffer = False
178
Michael Foord5f99ced2012-03-12 13:53:04 -0700179 return parser
180
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300181 def _getMainArgParser(self, parent):
182 parser = argparse.ArgumentParser(parents=[parent])
183 parser.prog = self.progName
184 parser.print_help = self._print_help
Michael Foord5f99ced2012-03-12 13:53:04 -0700185
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300186 parser.add_argument('tests', nargs='*',
187 help='a list of any number of test modules, '
188 'classes and test methods.')
Michael Foord5f99ced2012-03-12 13:53:04 -0700189
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300190 return parser
191
192 def _getDiscoveryArgParser(self, parent):
193 parser = argparse.ArgumentParser(parents=[parent])
194 parser.prog = '%s discover' % self.progName
195 parser.epilog = ('For test discovery all test modules must be '
196 'importable from the top level directory of the '
197 'project.')
198
199 parser.add_argument('-s', '--start-directory', dest='start',
200 help="Directory to start discovery ('.' default)")
201 parser.add_argument('-p', '--pattern', dest='pattern',
202 help="Pattern to match tests ('test*.py' default)")
203 parser.add_argument('-t', '--top-level-directory', dest='top',
204 help='Top level directory of project (defaults to '
205 'start directory)')
206 for arg in ('start', 'pattern', 'top'):
207 parser.add_argument(arg, nargs='?',
208 default=argparse.SUPPRESS,
209 help=argparse.SUPPRESS)
210
211 return parser
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000212
Michael Foorda23a39c2013-02-11 00:18:07 +0000213 def _do_discovery(self, argv, Loader=None):
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300214 self.start = '.'
215 self.pattern = 'test*.py'
216 self.top = None
217 if argv is not None:
218 # handle command line args for test discovery
219 if self._discovery_parser is None:
220 # for testing
221 self._initArgParsers()
222 self._discovery_parser.parse_args(argv, self)
Michael Foorda23a39c2013-02-11 00:18:07 +0000223
Serhiy Storchakade2800f2013-08-29 12:37:28 +0300224 loader = self.testLoader if Loader is None else Loader()
225 self.test = loader.discover(self.start, self.pattern, self.top)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000226
227 def runTests(self):
Michael Foord65b69a12010-03-27 13:25:41 +0000228 if self.catchbreak:
229 installHandler()
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000230 if self.testRunner is None:
231 self.testRunner = runner.TextTestRunner
232 if isinstance(self.testRunner, type):
233 try:
Benjamin Peterson8769fd82010-03-22 01:13:48 +0000234 testRunner = self.testRunner(verbosity=self.verbosity,
Benjamin Petersonb48af542010-04-11 20:43:16 +0000235 failfast=self.failfast,
Ezio Melotti60901872010-12-01 00:56:10 +0000236 buffer=self.buffer,
237 warnings=self.warnings)
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000238 except TypeError:
Benjamin Petersonb48af542010-04-11 20:43:16 +0000239 # didn't accept the verbosity, buffer or failfast arguments
Benjamin Petersonbed7d042009-07-19 21:01:52 +0000240 testRunner = self.testRunner()
241 else:
242 # it is assumed to be a TestRunner instance
243 testRunner = self.testRunner
244 self.result = testRunner.run(self.test)
245 if self.exit:
246 sys.exit(not self.result.wasSuccessful())
247
248main = TestProgram