jpayne@68
|
1 """Unittest main program"""
|
jpayne@68
|
2
|
jpayne@68
|
3 import sys
|
jpayne@68
|
4 import argparse
|
jpayne@68
|
5 import os
|
jpayne@68
|
6
|
jpayne@68
|
7 from . import loader, runner
|
jpayne@68
|
8 from .signals import installHandler
|
jpayne@68
|
9
|
jpayne@68
|
10 __unittest = True
|
jpayne@68
|
11
|
jpayne@68
|
12 MAIN_EXAMPLES = """\
|
jpayne@68
|
13 Examples:
|
jpayne@68
|
14 %(prog)s test_module - run tests from test_module
|
jpayne@68
|
15 %(prog)s module.TestClass - run tests from module.TestClass
|
jpayne@68
|
16 %(prog)s module.Class.test_method - run specified test method
|
jpayne@68
|
17 %(prog)s path/to/test_file.py - run tests from test_file.py
|
jpayne@68
|
18 """
|
jpayne@68
|
19
|
jpayne@68
|
20 MODULE_EXAMPLES = """\
|
jpayne@68
|
21 Examples:
|
jpayne@68
|
22 %(prog)s - run default set of tests
|
jpayne@68
|
23 %(prog)s MyTestSuite - run suite 'MyTestSuite'
|
jpayne@68
|
24 %(prog)s MyTestCase.testSomething - run MyTestCase.testSomething
|
jpayne@68
|
25 %(prog)s MyTestCase - run all 'test*' test methods
|
jpayne@68
|
26 in MyTestCase
|
jpayne@68
|
27 """
|
jpayne@68
|
28
|
jpayne@68
|
29 def _convert_name(name):
|
jpayne@68
|
30 # on Linux / Mac OS X 'foo.PY' is not importable, but on
|
jpayne@68
|
31 # Windows it is. Simpler to do a case insensitive match
|
jpayne@68
|
32 # a better check would be to check that the name is a
|
jpayne@68
|
33 # valid Python module name.
|
jpayne@68
|
34 if os.path.isfile(name) and name.lower().endswith('.py'):
|
jpayne@68
|
35 if os.path.isabs(name):
|
jpayne@68
|
36 rel_path = os.path.relpath(name, os.getcwd())
|
jpayne@68
|
37 if os.path.isabs(rel_path) or rel_path.startswith(os.pardir):
|
jpayne@68
|
38 return name
|
jpayne@68
|
39 name = rel_path
|
jpayne@68
|
40 # on Windows both '\' and '/' are used as path
|
jpayne@68
|
41 # separators. Better to replace both than rely on os.path.sep
|
jpayne@68
|
42 return name[:-3].replace('\\', '.').replace('/', '.')
|
jpayne@68
|
43 return name
|
jpayne@68
|
44
|
jpayne@68
|
45 def _convert_names(names):
|
jpayne@68
|
46 return [_convert_name(name) for name in names]
|
jpayne@68
|
47
|
jpayne@68
|
48
|
jpayne@68
|
49 def _convert_select_pattern(pattern):
|
jpayne@68
|
50 if not '*' in pattern:
|
jpayne@68
|
51 pattern = '*%s*' % pattern
|
jpayne@68
|
52 return pattern
|
jpayne@68
|
53
|
jpayne@68
|
54
|
jpayne@68
|
55 class TestProgram(object):
|
jpayne@68
|
56 """A command-line program that runs a set of tests; this is primarily
|
jpayne@68
|
57 for making test modules conveniently executable.
|
jpayne@68
|
58 """
|
jpayne@68
|
59 # defaults for testing
|
jpayne@68
|
60 module=None
|
jpayne@68
|
61 verbosity = 1
|
jpayne@68
|
62 failfast = catchbreak = buffer = progName = warnings = testNamePatterns = None
|
jpayne@68
|
63 _discovery_parser = None
|
jpayne@68
|
64
|
jpayne@68
|
65 def __init__(self, module='__main__', defaultTest=None, argv=None,
|
jpayne@68
|
66 testRunner=None, testLoader=loader.defaultTestLoader,
|
jpayne@68
|
67 exit=True, verbosity=1, failfast=None, catchbreak=None,
|
jpayne@68
|
68 buffer=None, warnings=None, *, tb_locals=False):
|
jpayne@68
|
69 if isinstance(module, str):
|
jpayne@68
|
70 self.module = __import__(module)
|
jpayne@68
|
71 for part in module.split('.')[1:]:
|
jpayne@68
|
72 self.module = getattr(self.module, part)
|
jpayne@68
|
73 else:
|
jpayne@68
|
74 self.module = module
|
jpayne@68
|
75 if argv is None:
|
jpayne@68
|
76 argv = sys.argv
|
jpayne@68
|
77
|
jpayne@68
|
78 self.exit = exit
|
jpayne@68
|
79 self.failfast = failfast
|
jpayne@68
|
80 self.catchbreak = catchbreak
|
jpayne@68
|
81 self.verbosity = verbosity
|
jpayne@68
|
82 self.buffer = buffer
|
jpayne@68
|
83 self.tb_locals = tb_locals
|
jpayne@68
|
84 if warnings is None and not sys.warnoptions:
|
jpayne@68
|
85 # even if DeprecationWarnings are ignored by default
|
jpayne@68
|
86 # print them anyway unless other warnings settings are
|
jpayne@68
|
87 # specified by the warnings arg or the -W python flag
|
jpayne@68
|
88 self.warnings = 'default'
|
jpayne@68
|
89 else:
|
jpayne@68
|
90 # here self.warnings is set either to the value passed
|
jpayne@68
|
91 # to the warnings args or to None.
|
jpayne@68
|
92 # If the user didn't pass a value self.warnings will
|
jpayne@68
|
93 # be None. This means that the behavior is unchanged
|
jpayne@68
|
94 # and depends on the values passed to -W.
|
jpayne@68
|
95 self.warnings = warnings
|
jpayne@68
|
96 self.defaultTest = defaultTest
|
jpayne@68
|
97 self.testRunner = testRunner
|
jpayne@68
|
98 self.testLoader = testLoader
|
jpayne@68
|
99 self.progName = os.path.basename(argv[0])
|
jpayne@68
|
100 self.parseArgs(argv)
|
jpayne@68
|
101 self.runTests()
|
jpayne@68
|
102
|
jpayne@68
|
103 def usageExit(self, msg=None):
|
jpayne@68
|
104 if msg:
|
jpayne@68
|
105 print(msg)
|
jpayne@68
|
106 if self._discovery_parser is None:
|
jpayne@68
|
107 self._initArgParsers()
|
jpayne@68
|
108 self._print_help()
|
jpayne@68
|
109 sys.exit(2)
|
jpayne@68
|
110
|
jpayne@68
|
111 def _print_help(self, *args, **kwargs):
|
jpayne@68
|
112 if self.module is None:
|
jpayne@68
|
113 print(self._main_parser.format_help())
|
jpayne@68
|
114 print(MAIN_EXAMPLES % {'prog': self.progName})
|
jpayne@68
|
115 self._discovery_parser.print_help()
|
jpayne@68
|
116 else:
|
jpayne@68
|
117 print(self._main_parser.format_help())
|
jpayne@68
|
118 print(MODULE_EXAMPLES % {'prog': self.progName})
|
jpayne@68
|
119
|
jpayne@68
|
120 def parseArgs(self, argv):
|
jpayne@68
|
121 self._initArgParsers()
|
jpayne@68
|
122 if self.module is None:
|
jpayne@68
|
123 if len(argv) > 1 and argv[1].lower() == 'discover':
|
jpayne@68
|
124 self._do_discovery(argv[2:])
|
jpayne@68
|
125 return
|
jpayne@68
|
126 self._main_parser.parse_args(argv[1:], self)
|
jpayne@68
|
127 if not self.tests:
|
jpayne@68
|
128 # this allows "python -m unittest -v" to still work for
|
jpayne@68
|
129 # test discovery.
|
jpayne@68
|
130 self._do_discovery([])
|
jpayne@68
|
131 return
|
jpayne@68
|
132 else:
|
jpayne@68
|
133 self._main_parser.parse_args(argv[1:], self)
|
jpayne@68
|
134
|
jpayne@68
|
135 if self.tests:
|
jpayne@68
|
136 self.testNames = _convert_names(self.tests)
|
jpayne@68
|
137 if __name__ == '__main__':
|
jpayne@68
|
138 # to support python -m unittest ...
|
jpayne@68
|
139 self.module = None
|
jpayne@68
|
140 elif self.defaultTest is None:
|
jpayne@68
|
141 # createTests will load tests from self.module
|
jpayne@68
|
142 self.testNames = None
|
jpayne@68
|
143 elif isinstance(self.defaultTest, str):
|
jpayne@68
|
144 self.testNames = (self.defaultTest,)
|
jpayne@68
|
145 else:
|
jpayne@68
|
146 self.testNames = list(self.defaultTest)
|
jpayne@68
|
147 self.createTests()
|
jpayne@68
|
148
|
jpayne@68
|
149 def createTests(self, from_discovery=False, Loader=None):
|
jpayne@68
|
150 if self.testNamePatterns:
|
jpayne@68
|
151 self.testLoader.testNamePatterns = self.testNamePatterns
|
jpayne@68
|
152 if from_discovery:
|
jpayne@68
|
153 loader = self.testLoader if Loader is None else Loader()
|
jpayne@68
|
154 self.test = loader.discover(self.start, self.pattern, self.top)
|
jpayne@68
|
155 elif self.testNames is None:
|
jpayne@68
|
156 self.test = self.testLoader.loadTestsFromModule(self.module)
|
jpayne@68
|
157 else:
|
jpayne@68
|
158 self.test = self.testLoader.loadTestsFromNames(self.testNames,
|
jpayne@68
|
159 self.module)
|
jpayne@68
|
160
|
jpayne@68
|
161 def _initArgParsers(self):
|
jpayne@68
|
162 parent_parser = self._getParentArgParser()
|
jpayne@68
|
163 self._main_parser = self._getMainArgParser(parent_parser)
|
jpayne@68
|
164 self._discovery_parser = self._getDiscoveryArgParser(parent_parser)
|
jpayne@68
|
165
|
jpayne@68
|
166 def _getParentArgParser(self):
|
jpayne@68
|
167 parser = argparse.ArgumentParser(add_help=False)
|
jpayne@68
|
168
|
jpayne@68
|
169 parser.add_argument('-v', '--verbose', dest='verbosity',
|
jpayne@68
|
170 action='store_const', const=2,
|
jpayne@68
|
171 help='Verbose output')
|
jpayne@68
|
172 parser.add_argument('-q', '--quiet', dest='verbosity',
|
jpayne@68
|
173 action='store_const', const=0,
|
jpayne@68
|
174 help='Quiet output')
|
jpayne@68
|
175 parser.add_argument('--locals', dest='tb_locals',
|
jpayne@68
|
176 action='store_true',
|
jpayne@68
|
177 help='Show local variables in tracebacks')
|
jpayne@68
|
178 if self.failfast is None:
|
jpayne@68
|
179 parser.add_argument('-f', '--failfast', dest='failfast',
|
jpayne@68
|
180 action='store_true',
|
jpayne@68
|
181 help='Stop on first fail or error')
|
jpayne@68
|
182 self.failfast = False
|
jpayne@68
|
183 if self.catchbreak is None:
|
jpayne@68
|
184 parser.add_argument('-c', '--catch', dest='catchbreak',
|
jpayne@68
|
185 action='store_true',
|
jpayne@68
|
186 help='Catch Ctrl-C and display results so far')
|
jpayne@68
|
187 self.catchbreak = False
|
jpayne@68
|
188 if self.buffer is None:
|
jpayne@68
|
189 parser.add_argument('-b', '--buffer', dest='buffer',
|
jpayne@68
|
190 action='store_true',
|
jpayne@68
|
191 help='Buffer stdout and stderr during tests')
|
jpayne@68
|
192 self.buffer = False
|
jpayne@68
|
193 if self.testNamePatterns is None:
|
jpayne@68
|
194 parser.add_argument('-k', dest='testNamePatterns',
|
jpayne@68
|
195 action='append', type=_convert_select_pattern,
|
jpayne@68
|
196 help='Only run tests which match the given substring')
|
jpayne@68
|
197 self.testNamePatterns = []
|
jpayne@68
|
198
|
jpayne@68
|
199 return parser
|
jpayne@68
|
200
|
jpayne@68
|
201 def _getMainArgParser(self, parent):
|
jpayne@68
|
202 parser = argparse.ArgumentParser(parents=[parent])
|
jpayne@68
|
203 parser.prog = self.progName
|
jpayne@68
|
204 parser.print_help = self._print_help
|
jpayne@68
|
205
|
jpayne@68
|
206 parser.add_argument('tests', nargs='*',
|
jpayne@68
|
207 help='a list of any number of test modules, '
|
jpayne@68
|
208 'classes and test methods.')
|
jpayne@68
|
209
|
jpayne@68
|
210 return parser
|
jpayne@68
|
211
|
jpayne@68
|
212 def _getDiscoveryArgParser(self, parent):
|
jpayne@68
|
213 parser = argparse.ArgumentParser(parents=[parent])
|
jpayne@68
|
214 parser.prog = '%s discover' % self.progName
|
jpayne@68
|
215 parser.epilog = ('For test discovery all test modules must be '
|
jpayne@68
|
216 'importable from the top level directory of the '
|
jpayne@68
|
217 'project.')
|
jpayne@68
|
218
|
jpayne@68
|
219 parser.add_argument('-s', '--start-directory', dest='start',
|
jpayne@68
|
220 help="Directory to start discovery ('.' default)")
|
jpayne@68
|
221 parser.add_argument('-p', '--pattern', dest='pattern',
|
jpayne@68
|
222 help="Pattern to match tests ('test*.py' default)")
|
jpayne@68
|
223 parser.add_argument('-t', '--top-level-directory', dest='top',
|
jpayne@68
|
224 help='Top level directory of project (defaults to '
|
jpayne@68
|
225 'start directory)')
|
jpayne@68
|
226 for arg in ('start', 'pattern', 'top'):
|
jpayne@68
|
227 parser.add_argument(arg, nargs='?',
|
jpayne@68
|
228 default=argparse.SUPPRESS,
|
jpayne@68
|
229 help=argparse.SUPPRESS)
|
jpayne@68
|
230
|
jpayne@68
|
231 return parser
|
jpayne@68
|
232
|
jpayne@68
|
233 def _do_discovery(self, argv, Loader=None):
|
jpayne@68
|
234 self.start = '.'
|
jpayne@68
|
235 self.pattern = 'test*.py'
|
jpayne@68
|
236 self.top = None
|
jpayne@68
|
237 if argv is not None:
|
jpayne@68
|
238 # handle command line args for test discovery
|
jpayne@68
|
239 if self._discovery_parser is None:
|
jpayne@68
|
240 # for testing
|
jpayne@68
|
241 self._initArgParsers()
|
jpayne@68
|
242 self._discovery_parser.parse_args(argv, self)
|
jpayne@68
|
243
|
jpayne@68
|
244 self.createTests(from_discovery=True, Loader=Loader)
|
jpayne@68
|
245
|
jpayne@68
|
246 def runTests(self):
|
jpayne@68
|
247 if self.catchbreak:
|
jpayne@68
|
248 installHandler()
|
jpayne@68
|
249 if self.testRunner is None:
|
jpayne@68
|
250 self.testRunner = runner.TextTestRunner
|
jpayne@68
|
251 if isinstance(self.testRunner, type):
|
jpayne@68
|
252 try:
|
jpayne@68
|
253 try:
|
jpayne@68
|
254 testRunner = self.testRunner(verbosity=self.verbosity,
|
jpayne@68
|
255 failfast=self.failfast,
|
jpayne@68
|
256 buffer=self.buffer,
|
jpayne@68
|
257 warnings=self.warnings,
|
jpayne@68
|
258 tb_locals=self.tb_locals)
|
jpayne@68
|
259 except TypeError:
|
jpayne@68
|
260 # didn't accept the tb_locals argument
|
jpayne@68
|
261 testRunner = self.testRunner(verbosity=self.verbosity,
|
jpayne@68
|
262 failfast=self.failfast,
|
jpayne@68
|
263 buffer=self.buffer,
|
jpayne@68
|
264 warnings=self.warnings)
|
jpayne@68
|
265 except TypeError:
|
jpayne@68
|
266 # didn't accept the verbosity, buffer or failfast arguments
|
jpayne@68
|
267 testRunner = self.testRunner()
|
jpayne@68
|
268 else:
|
jpayne@68
|
269 # it is assumed to be a TestRunner instance
|
jpayne@68
|
270 testRunner = self.testRunner
|
jpayne@68
|
271 self.result = testRunner.run(self.test)
|
jpayne@68
|
272 if self.exit:
|
jpayne@68
|
273 sys.exit(not self.result.wasSuccessful())
|
jpayne@68
|
274
|
jpayne@68
|
275 main = TestProgram
|