1"""Unittest main program""" 2 3import sys 4import argparse 5import os 6import warnings 7 8from . import loader, runner 9from .signals import installHandler 10 11__unittest = True 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 if isinstance(module, str): 71 self.module = __import__(module) 72 for part in module.split('.')[1:]: 73 self.module = getattr(self.module, part) 74 else: 75 self.module = module 76 if argv is None: 77 argv = sys.argv 78 79 self.exit = exit 80 self.failfast = failfast 81 self.catchbreak = catchbreak 82 self.verbosity = verbosity 83 self.buffer = buffer 84 self.tb_locals = tb_locals 85 if warnings is None and not sys.warnoptions: 86 # even if DeprecationWarnings are ignored by default 87 # print them anyway unless other warnings settings are 88 # specified by the warnings arg or the -W python flag 89 self.warnings = 'default' 90 else: 91 # here self.warnings is set either to the value passed 92 # to the warnings args or to None. 93 # If the user didn't pass a value self.warnings will 94 # be None. This means that the behavior is unchanged 95 # and depends on the values passed to -W. 96 self.warnings = warnings 97 self.defaultTest = defaultTest 98 self.testRunner = testRunner 99 self.testLoader = testLoader 100 self.progName = os.path.basename(argv[0]) 101 self.parseArgs(argv) 102 self.runTests() 103 104 def usageExit(self, msg=None): 105 warnings.warn("TestProgram.usageExit() is deprecated and will be" 106 " removed in Python 3.13", DeprecationWarning) 107 if msg: 108 print(msg) 109 if self._discovery_parser is None: 110 self._initArgParsers() 111 self._print_help() 112 sys.exit(2) 113 114 def _print_help(self, *args, **kwargs): 115 if self.module is None: 116 print(self._main_parser.format_help()) 117 print(MAIN_EXAMPLES % {'prog': self.progName}) 118 self._discovery_parser.print_help() 119 else: 120 print(self._main_parser.format_help()) 121 print(MODULE_EXAMPLES % {'prog': self.progName}) 122 123 def parseArgs(self, argv): 124 self._initArgParsers() 125 if self.module is None: 126 if len(argv) > 1 and argv[1].lower() == 'discover': 127 self._do_discovery(argv[2:]) 128 return 129 self._main_parser.parse_args(argv[1:], self) 130 if not self.tests: 131 # this allows "python -m unittest -v" to still work for 132 # test discovery. 133 self._do_discovery([]) 134 return 135 else: 136 self._main_parser.parse_args(argv[1:], self) 137 138 if self.tests: 139 self.testNames = _convert_names(self.tests) 140 if __name__ == '__main__': 141 # to support python -m unittest ... 142 self.module = None 143 elif self.defaultTest is None: 144 # createTests will load tests from self.module 145 self.testNames = None 146 elif isinstance(self.defaultTest, str): 147 self.testNames = (self.defaultTest,) 148 else: 149 self.testNames = list(self.defaultTest) 150 self.createTests() 151 152 def createTests(self, from_discovery=False, Loader=None): 153 if self.testNamePatterns: 154 self.testLoader.testNamePatterns = self.testNamePatterns 155 if from_discovery: 156 loader = self.testLoader if Loader is None else Loader() 157 self.test = loader.discover(self.start, self.pattern, self.top) 158 elif self.testNames is None: 159 self.test = self.testLoader.loadTestsFromModule(self.module) 160 else: 161 self.test = self.testLoader.loadTestsFromNames(self.testNames, 162 self.module) 163 164 def _initArgParsers(self): 165 parent_parser = self._getParentArgParser() 166 self._main_parser = self._getMainArgParser(parent_parser) 167 self._discovery_parser = self._getDiscoveryArgParser(parent_parser) 168 169 def _getParentArgParser(self): 170 parser = argparse.ArgumentParser(add_help=False) 171 172 parser.add_argument('-v', '--verbose', dest='verbosity', 173 action='store_const', const=2, 174 help='Verbose output') 175 parser.add_argument('-q', '--quiet', dest='verbosity', 176 action='store_const', const=0, 177 help='Quiet output') 178 parser.add_argument('--locals', dest='tb_locals', 179 action='store_true', 180 help='Show local variables in tracebacks') 181 if self.failfast is None: 182 parser.add_argument('-f', '--failfast', dest='failfast', 183 action='store_true', 184 help='Stop on first fail or error') 185 self.failfast = False 186 if self.catchbreak is None: 187 parser.add_argument('-c', '--catch', dest='catchbreak', 188 action='store_true', 189 help='Catch Ctrl-C and display results so far') 190 self.catchbreak = False 191 if self.buffer is None: 192 parser.add_argument('-b', '--buffer', dest='buffer', 193 action='store_true', 194 help='Buffer stdout and stderr during tests') 195 self.buffer = False 196 if self.testNamePatterns is None: 197 parser.add_argument('-k', dest='testNamePatterns', 198 action='append', type=_convert_select_pattern, 199 help='Only run tests which match the given substring') 200 self.testNamePatterns = [] 201 202 return parser 203 204 def _getMainArgParser(self, parent): 205 parser = argparse.ArgumentParser(parents=[parent]) 206 parser.prog = self.progName 207 parser.print_help = self._print_help 208 209 parser.add_argument('tests', nargs='*', 210 help='a list of any number of test modules, ' 211 'classes and test methods.') 212 213 return parser 214 215 def _getDiscoveryArgParser(self, parent): 216 parser = argparse.ArgumentParser(parents=[parent]) 217 parser.prog = '%s discover' % self.progName 218 parser.epilog = ('For test discovery all test modules must be ' 219 'importable from the top level directory of the ' 220 'project.') 221 222 parser.add_argument('-s', '--start-directory', dest='start', 223 help="Directory to start discovery ('.' default)") 224 parser.add_argument('-p', '--pattern', dest='pattern', 225 help="Pattern to match tests ('test*.py' default)") 226 parser.add_argument('-t', '--top-level-directory', dest='top', 227 help='Top level directory of project (defaults to ' 228 'start directory)') 229 for arg in ('start', 'pattern', 'top'): 230 parser.add_argument(arg, nargs='?', 231 default=argparse.SUPPRESS, 232 help=argparse.SUPPRESS) 233 234 return parser 235 236 def _do_discovery(self, argv, Loader=None): 237 self.start = '.' 238 self.pattern = 'test*.py' 239 self.top = None 240 if argv is not None: 241 # handle command line args for test discovery 242 if self._discovery_parser is None: 243 # for testing 244 self._initArgParsers() 245 self._discovery_parser.parse_args(argv, self) 246 247 self.createTests(from_discovery=True, Loader=Loader) 248 249 def runTests(self): 250 if self.catchbreak: 251 installHandler() 252 if self.testRunner is None: 253 self.testRunner = runner.TextTestRunner 254 if isinstance(self.testRunner, type): 255 try: 256 try: 257 testRunner = self.testRunner(verbosity=self.verbosity, 258 failfast=self.failfast, 259 buffer=self.buffer, 260 warnings=self.warnings, 261 tb_locals=self.tb_locals) 262 except TypeError: 263 # didn't accept the tb_locals argument 264 testRunner = self.testRunner(verbosity=self.verbosity, 265 failfast=self.failfast, 266 buffer=self.buffer, 267 warnings=self.warnings) 268 except TypeError: 269 # didn't accept the verbosity, buffer or failfast arguments 270 testRunner = self.testRunner() 271 else: 272 # it is assumed to be a TestRunner instance 273 testRunner = self.testRunner 274 self.result = testRunner.run(self.test) 275 if self.exit: 276 sys.exit(not self.result.wasSuccessful()) 277 278main = TestProgram 279