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