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