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