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 12MAIN_EXAMPLES = """\ 13Examples: 14 %(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 17 %(prog)s path/to/test_file.py - run tests from test_file.py 18""" 19 20MODULE_EXAMPLES = """\ 21Examples: 22 %(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 27""" 28 29def _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 44 45def _convert_names(names): 46 return [_convert_name(name) for name in names] 47 48 49def _convert_select_pattern(pattern): 50 if not '*' in pattern: 51 pattern = '*%s*' % pattern 52 return pattern 53 54 55class TestProgram(object): 56 """A command-line program that runs a set of tests; this is primarily 57 for making test modules conveniently executable. 58 """ 59 # defaults for testing 60 module=None 61 verbosity = 1 62 failfast = catchbreak = buffer = progName = warnings = testNamePatterns = None 63 _discovery_parser = None 64 65 def __init__(self, module='__main__', defaultTest=None, argv=None, 66 testRunner=None, testLoader=loader.defaultTestLoader, 67 exit=True, verbosity=1, failfast=None, catchbreak=None, 68 buffer=None, warnings=None, *, tb_locals=False): 69 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 79 self.failfast = failfast 80 self.catchbreak = catchbreak 81 self.verbosity = verbosity 82 self.buffer = buffer 83 self.tb_locals = tb_locals 84 if warnings is None and not sys.warnoptions: 85 # even if DeprecationWarnings are ignored by default 86 # 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 96 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) 106 if self._discovery_parser is None: 107 self._initArgParsers() 108 self._print_help() 109 sys.exit(2) 110 111 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 120 def parseArgs(self, argv): 121 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) 134 135 if self.tests: 136 self.testNames = _convert_names(self.tests) 137 if __name__ == '__main__': 138 # to support python -m unittest ... 139 self.module = None 140 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,) 145 else: 146 self.testNames = list(self.defaultTest) 147 self.createTests() 148 149 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: 156 self.test = self.testLoader.loadTestsFromModule(self.module) 157 else: 158 self.test = self.testLoader.loadTestsFromNames(self.testNames, 159 self.module) 160 161 def _initArgParsers(self): 162 parent_parser = self._getParentArgParser() 163 self._main_parser = self._getMainArgParser(parent_parser) 164 self._discovery_parser = self._getDiscoveryArgParser(parent_parser) 165 166 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') 175 parser.add_argument('--locals', dest='tb_locals', 176 action='store_true', 177 help='Show local variables in tracebacks') 178 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', 186 help='Catch Ctrl-C and display results so far') 187 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 193 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 = [] 198 199 return parser 200 201 def _getMainArgParser(self, parent): 202 parser = argparse.ArgumentParser(parents=[parent]) 203 parser.prog = self.progName 204 parser.print_help = self._print_help 205 206 parser.add_argument('tests', nargs='*', 207 help='a list of any number of test modules, ' 208 'classes and test methods.') 209 210 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 232 233 def _do_discovery(self, argv, Loader=None): 234 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) 243 244 self.createTests(from_discovery=True, Loader=Loader) 245 246 def runTests(self): 247 if self.catchbreak: 248 installHandler() 249 if self.testRunner is None: 250 self.testRunner = runner.TextTestRunner 251 if isinstance(self.testRunner, type): 252 try: 253 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) 265 except TypeError: 266 # didn't accept the verbosity, buffer or failfast arguments 267 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 276