jpayne@68: """Unittest main program""" jpayne@68: jpayne@68: import sys jpayne@68: import argparse jpayne@68: import os jpayne@68: jpayne@68: from . import loader, runner jpayne@68: from .signals import installHandler jpayne@68: jpayne@68: __unittest = True jpayne@68: jpayne@68: MAIN_EXAMPLES = """\ jpayne@68: Examples: jpayne@68: %(prog)s test_module - run tests from test_module jpayne@68: %(prog)s module.TestClass - run tests from module.TestClass jpayne@68: %(prog)s module.Class.test_method - run specified test method jpayne@68: %(prog)s path/to/test_file.py - run tests from test_file.py jpayne@68: """ jpayne@68: jpayne@68: MODULE_EXAMPLES = """\ jpayne@68: Examples: jpayne@68: %(prog)s - run default set of tests jpayne@68: %(prog)s MyTestSuite - run suite 'MyTestSuite' jpayne@68: %(prog)s MyTestCase.testSomething - run MyTestCase.testSomething jpayne@68: %(prog)s MyTestCase - run all 'test*' test methods jpayne@68: in MyTestCase jpayne@68: """ jpayne@68: jpayne@68: def _convert_name(name): jpayne@68: # on Linux / Mac OS X 'foo.PY' is not importable, but on jpayne@68: # Windows it is. Simpler to do a case insensitive match jpayne@68: # a better check would be to check that the name is a jpayne@68: # valid Python module name. jpayne@68: if os.path.isfile(name) and name.lower().endswith('.py'): jpayne@68: if os.path.isabs(name): jpayne@68: rel_path = os.path.relpath(name, os.getcwd()) jpayne@68: if os.path.isabs(rel_path) or rel_path.startswith(os.pardir): jpayne@68: return name jpayne@68: name = rel_path jpayne@68: # on Windows both '\' and '/' are used as path jpayne@68: # separators. Better to replace both than rely on os.path.sep jpayne@68: return name[:-3].replace('\\', '.').replace('/', '.') jpayne@68: return name jpayne@68: jpayne@68: def _convert_names(names): jpayne@68: return [_convert_name(name) for name in names] jpayne@68: jpayne@68: jpayne@68: def _convert_select_pattern(pattern): jpayne@68: if not '*' in pattern: jpayne@68: pattern = '*%s*' % pattern jpayne@68: return pattern jpayne@68: jpayne@68: jpayne@68: class TestProgram(object): jpayne@68: """A command-line program that runs a set of tests; this is primarily jpayne@68: for making test modules conveniently executable. jpayne@68: """ jpayne@68: # defaults for testing jpayne@68: module=None jpayne@68: verbosity = 1 jpayne@68: failfast = catchbreak = buffer = progName = warnings = testNamePatterns = None jpayne@68: _discovery_parser = None jpayne@68: jpayne@68: def __init__(self, module='__main__', defaultTest=None, argv=None, jpayne@68: testRunner=None, testLoader=loader.defaultTestLoader, jpayne@68: exit=True, verbosity=1, failfast=None, catchbreak=None, jpayne@68: buffer=None, warnings=None, *, tb_locals=False): jpayne@68: if isinstance(module, str): jpayne@68: self.module = __import__(module) jpayne@68: for part in module.split('.')[1:]: jpayne@68: self.module = getattr(self.module, part) jpayne@68: else: jpayne@68: self.module = module jpayne@68: if argv is None: jpayne@68: argv = sys.argv jpayne@68: jpayne@68: self.exit = exit jpayne@68: self.failfast = failfast jpayne@68: self.catchbreak = catchbreak jpayne@68: self.verbosity = verbosity jpayne@68: self.buffer = buffer jpayne@68: self.tb_locals = tb_locals jpayne@68: if warnings is None and not sys.warnoptions: jpayne@68: # even if DeprecationWarnings are ignored by default jpayne@68: # print them anyway unless other warnings settings are jpayne@68: # specified by the warnings arg or the -W python flag jpayne@68: self.warnings = 'default' jpayne@68: else: jpayne@68: # here self.warnings is set either to the value passed jpayne@68: # to the warnings args or to None. jpayne@68: # If the user didn't pass a value self.warnings will jpayne@68: # be None. This means that the behavior is unchanged jpayne@68: # and depends on the values passed to -W. jpayne@68: self.warnings = warnings jpayne@68: self.defaultTest = defaultTest jpayne@68: self.testRunner = testRunner jpayne@68: self.testLoader = testLoader jpayne@68: self.progName = os.path.basename(argv[0]) jpayne@68: self.parseArgs(argv) jpayne@68: self.runTests() jpayne@68: jpayne@68: def usageExit(self, msg=None): jpayne@68: if msg: jpayne@68: print(msg) jpayne@68: if self._discovery_parser is None: jpayne@68: self._initArgParsers() jpayne@68: self._print_help() jpayne@68: sys.exit(2) jpayne@68: jpayne@68: def _print_help(self, *args, **kwargs): jpayne@68: if self.module is None: jpayne@68: print(self._main_parser.format_help()) jpayne@68: print(MAIN_EXAMPLES % {'prog': self.progName}) jpayne@68: self._discovery_parser.print_help() jpayne@68: else: jpayne@68: print(self._main_parser.format_help()) jpayne@68: print(MODULE_EXAMPLES % {'prog': self.progName}) jpayne@68: jpayne@68: def parseArgs(self, argv): jpayne@68: self._initArgParsers() jpayne@68: if self.module is None: jpayne@68: if len(argv) > 1 and argv[1].lower() == 'discover': jpayne@68: self._do_discovery(argv[2:]) jpayne@68: return jpayne@68: self._main_parser.parse_args(argv[1:], self) jpayne@68: if not self.tests: jpayne@68: # this allows "python -m unittest -v" to still work for jpayne@68: # test discovery. jpayne@68: self._do_discovery([]) jpayne@68: return jpayne@68: else: jpayne@68: self._main_parser.parse_args(argv[1:], self) jpayne@68: jpayne@68: if self.tests: jpayne@68: self.testNames = _convert_names(self.tests) jpayne@68: if __name__ == '__main__': jpayne@68: # to support python -m unittest ... jpayne@68: self.module = None jpayne@68: elif self.defaultTest is None: jpayne@68: # createTests will load tests from self.module jpayne@68: self.testNames = None jpayne@68: elif isinstance(self.defaultTest, str): jpayne@68: self.testNames = (self.defaultTest,) jpayne@68: else: jpayne@68: self.testNames = list(self.defaultTest) jpayne@68: self.createTests() jpayne@68: jpayne@68: def createTests(self, from_discovery=False, Loader=None): jpayne@68: if self.testNamePatterns: jpayne@68: self.testLoader.testNamePatterns = self.testNamePatterns jpayne@68: if from_discovery: jpayne@68: loader = self.testLoader if Loader is None else Loader() jpayne@68: self.test = loader.discover(self.start, self.pattern, self.top) jpayne@68: elif self.testNames is None: jpayne@68: self.test = self.testLoader.loadTestsFromModule(self.module) jpayne@68: else: jpayne@68: self.test = self.testLoader.loadTestsFromNames(self.testNames, jpayne@68: self.module) jpayne@68: jpayne@68: def _initArgParsers(self): jpayne@68: parent_parser = self._getParentArgParser() jpayne@68: self._main_parser = self._getMainArgParser(parent_parser) jpayne@68: self._discovery_parser = self._getDiscoveryArgParser(parent_parser) jpayne@68: jpayne@68: def _getParentArgParser(self): jpayne@68: parser = argparse.ArgumentParser(add_help=False) jpayne@68: jpayne@68: parser.add_argument('-v', '--verbose', dest='verbosity', jpayne@68: action='store_const', const=2, jpayne@68: help='Verbose output') jpayne@68: parser.add_argument('-q', '--quiet', dest='verbosity', jpayne@68: action='store_const', const=0, jpayne@68: help='Quiet output') jpayne@68: parser.add_argument('--locals', dest='tb_locals', jpayne@68: action='store_true', jpayne@68: help='Show local variables in tracebacks') jpayne@68: if self.failfast is None: jpayne@68: parser.add_argument('-f', '--failfast', dest='failfast', jpayne@68: action='store_true', jpayne@68: help='Stop on first fail or error') jpayne@68: self.failfast = False jpayne@68: if self.catchbreak is None: jpayne@68: parser.add_argument('-c', '--catch', dest='catchbreak', jpayne@68: action='store_true', jpayne@68: help='Catch Ctrl-C and display results so far') jpayne@68: self.catchbreak = False jpayne@68: if self.buffer is None: jpayne@68: parser.add_argument('-b', '--buffer', dest='buffer', jpayne@68: action='store_true', jpayne@68: help='Buffer stdout and stderr during tests') jpayne@68: self.buffer = False jpayne@68: if self.testNamePatterns is None: jpayne@68: parser.add_argument('-k', dest='testNamePatterns', jpayne@68: action='append', type=_convert_select_pattern, jpayne@68: help='Only run tests which match the given substring') jpayne@68: self.testNamePatterns = [] jpayne@68: jpayne@68: return parser jpayne@68: jpayne@68: def _getMainArgParser(self, parent): jpayne@68: parser = argparse.ArgumentParser(parents=[parent]) jpayne@68: parser.prog = self.progName jpayne@68: parser.print_help = self._print_help jpayne@68: jpayne@68: parser.add_argument('tests', nargs='*', jpayne@68: help='a list of any number of test modules, ' jpayne@68: 'classes and test methods.') jpayne@68: jpayne@68: return parser jpayne@68: jpayne@68: def _getDiscoveryArgParser(self, parent): jpayne@68: parser = argparse.ArgumentParser(parents=[parent]) jpayne@68: parser.prog = '%s discover' % self.progName jpayne@68: parser.epilog = ('For test discovery all test modules must be ' jpayne@68: 'importable from the top level directory of the ' jpayne@68: 'project.') jpayne@68: jpayne@68: parser.add_argument('-s', '--start-directory', dest='start', jpayne@68: help="Directory to start discovery ('.' default)") jpayne@68: parser.add_argument('-p', '--pattern', dest='pattern', jpayne@68: help="Pattern to match tests ('test*.py' default)") jpayne@68: parser.add_argument('-t', '--top-level-directory', dest='top', jpayne@68: help='Top level directory of project (defaults to ' jpayne@68: 'start directory)') jpayne@68: for arg in ('start', 'pattern', 'top'): jpayne@68: parser.add_argument(arg, nargs='?', jpayne@68: default=argparse.SUPPRESS, jpayne@68: help=argparse.SUPPRESS) jpayne@68: jpayne@68: return parser jpayne@68: jpayne@68: def _do_discovery(self, argv, Loader=None): jpayne@68: self.start = '.' jpayne@68: self.pattern = 'test*.py' jpayne@68: self.top = None jpayne@68: if argv is not None: jpayne@68: # handle command line args for test discovery jpayne@68: if self._discovery_parser is None: jpayne@68: # for testing jpayne@68: self._initArgParsers() jpayne@68: self._discovery_parser.parse_args(argv, self) jpayne@68: jpayne@68: self.createTests(from_discovery=True, Loader=Loader) jpayne@68: jpayne@68: def runTests(self): jpayne@68: if self.catchbreak: jpayne@68: installHandler() jpayne@68: if self.testRunner is None: jpayne@68: self.testRunner = runner.TextTestRunner jpayne@68: if isinstance(self.testRunner, type): jpayne@68: try: jpayne@68: try: jpayne@68: testRunner = self.testRunner(verbosity=self.verbosity, jpayne@68: failfast=self.failfast, jpayne@68: buffer=self.buffer, jpayne@68: warnings=self.warnings, jpayne@68: tb_locals=self.tb_locals) jpayne@68: except TypeError: jpayne@68: # didn't accept the tb_locals argument jpayne@68: testRunner = self.testRunner(verbosity=self.verbosity, jpayne@68: failfast=self.failfast, jpayne@68: buffer=self.buffer, jpayne@68: warnings=self.warnings) jpayne@68: except TypeError: jpayne@68: # didn't accept the verbosity, buffer or failfast arguments jpayne@68: testRunner = self.testRunner() jpayne@68: else: jpayne@68: # it is assumed to be a TestRunner instance jpayne@68: testRunner = self.testRunner jpayne@68: self.result = testRunner.run(self.test) jpayne@68: if self.exit: jpayne@68: sys.exit(not self.result.wasSuccessful()) jpayne@68: jpayne@68: main = TestProgram