diff -r 617cb2f978b0 Lib/unittest/__main__.py --- a/Lib/unittest/__main__.py Mon May 13 19:56:35 2013 -0500 +++ b/Lib/unittest/__main__.py Wed May 15 01:58:48 2013 +0300 @@ -13,7 +13,6 @@ __unittest = True -from .main import main, TestProgram, USAGE_AS_MAIN -TestProgram.USAGE = USAGE_AS_MAIN +from .main import main, TestProgram main(module=None) diff -r 617cb2f978b0 Lib/unittest/main.py --- a/Lib/unittest/main.py Mon May 13 19:56:35 2013 -0500 +++ b/Lib/unittest/main.py Wed May 15 01:58:48 2013 +0300 @@ -1,7 +1,7 @@ """Unittest main program""" import sys -import optparse +import argparse import os from . import loader, runner @@ -9,53 +9,20 @@ __unittest = True -FAILFAST = " -f, --failfast Stop on first failure\n" -CATCHBREAK = " -c, --catch Catch control-C and display results\n" -BUFFEROUTPUT = " -b, --buffer Buffer stdout and stderr during test runs\n" - -USAGE_AS_MAIN = """\ -Usage: %(progName)s [options] [tests] - -Options: - -h, --help Show this message - -v, --verbose Verbose output - -q, --quiet Minimal output -%(failfast)s%(catchbreak)s%(buffer)s +MAIN_EXAMPLES = """\ Examples: - %(progName)s test_module - run tests from test_module - %(progName)s module.TestClass - run tests from module.TestClass - %(progName)s module.Class.test_method - run specified test method - -[tests] can be a list of any number of test modules, classes and test -methods. - -Alternative Usage: %(progName)s discover [options] - -Options: - -v, --verbose Verbose output -%(failfast)s%(catchbreak)s%(buffer)s -s directory Directory to start discovery ('.' default) - -p pattern Pattern to match test files ('test*.py' default) - -t directory Top level directory of project (default to - start directory) - -For test discovery all test modules must be importable from the top -level directory of the project. + %(prog)s test_module - run tests from test_module + %(prog)s module.TestClass - run tests from module.TestClass + %(prog)s module.Class.test_method - run specified test method """ -USAGE_FROM_MODULE = """\ -Usage: %(progName)s [options] [test] [...] - -Options: - -h, --help Show this message - -v, --verbose Verbose output - -q, --quiet Minimal output -%(failfast)s%(catchbreak)s%(buffer)s +MODULE_EXAMPLES = """\ Examples: - %(progName)s - run default set of tests - %(progName)s MyTestSuite - run suite 'MyTestSuite' - %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething - %(progName)s MyTestCase - run all 'test*' test methods - in MyTestCase + %(prog)s - run default set of tests + %(prog)s MyTestSuite - run suite 'MyTestSuite' + %(prog)s MyTestCase.testSomething - run MyTestCase.testSomething + %(prog)s MyTestCase - run all 'test*' test methods + in MyTestCase """ def _convert_name(name): @@ -82,9 +49,8 @@ """A command-line program that runs a set of tests; this is primarily for making test modules conveniently executable. """ - USAGE = USAGE_FROM_MODULE - # defaults for testing + verbosity = 1 failfast = catchbreak = buffer = progName = warnings = None def __init__(self, module='__main__', defaultTest=None, argv=None, @@ -121,50 +87,52 @@ self.testRunner = testRunner self.testLoader = testLoader self.progName = os.path.basename(argv[0]) + self._initArgParsers() self.parseArgs(argv) self.runTests() def usageExit(self, msg=None): if msg: print(msg) - usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '', - 'buffer': ''} - if self.failfast != False: - usage['failfast'] = FAILFAST - if self.catchbreak != False: - usage['catchbreak'] = CATCHBREAK - if self.buffer != False: - usage['buffer'] = BUFFEROUTPUT - print(self.USAGE % usage) + self.print_help() sys.exit(2) + def _print_help(self): + if self.module is None: + print(self._main_parser.format_help()) + print(MAIN_EXAMPLES % {'prog': self.progName}) + self._discovery_parser.print_help() + else: + print(self._main_parser.format_help()) + print(MODULE_EXAMPLES % {'prog': self.progName}) + def parseArgs(self, argv): if ((len(argv) > 1 and argv[1].lower() == 'discover') or (len(argv) == 1 and self.module is None)): self._do_discovery(argv[2:]) return - parser = self._getOptParser() - options, args = parser.parse_args(argv[1:]) + parser = self._main_parser + options = parser.parse_args(argv[1:]) self._setAttributesFromOptions(options) - if len(args) == 0 and self.module is None: - # this allows "python -m unittest -v" to still work for - # test discovery. This means -c / -b / -v / -f options will - # be handled twice, which is harmless but not ideal. - self._do_discovery(argv[1:]) - return - - if len(args) == 0 and self.defaultTest is None: - # createTests will load tests from self.module - self.testNames = None - elif len(args) > 0: - self.testNames = _convert_names(args) + if options.tests: + self.testNames = _convert_names(options.tests) if __name__ == '__main__': # to support python -m unittest ... self.module = None else: - if isinstance(self.defaultTest, str): + if self.module is None: + # this allows "python -m unittest -v" to still work for + # test discovery. This means -c / -b / -v / -f options will + # be handled twice, which is harmless but not ideal. + self._do_discovery(options=options) + return + + if self.defaultTest is None: + # createTests will load tests from self.module + self.testNames = None + elif isinstance(self.defaultTest, str): self.testNames = (self.defaultTest,) else: self.testNames = list(self.defaultTest) @@ -177,27 +145,70 @@ self.test = self.testLoader.loadTestsFromNames(self.testNames, self.module) - def _getOptParser(self): - import optparse - parser = optparse.OptionParser() - parser.prog = self.progName - parser.add_option('-v', '--verbose', dest='verbose', default=False, - help='Verbose output', action='store_true') - parser.add_option('-q', '--quiet', dest='quiet', default=False, - help='Quiet output', action='store_true') + def _initArgParsers(self): + parent_parser = self._getParentArgParser() + self._main_parser = self._getMainArgParser(parent_parser) + self._discovery_parser = self._getDiscoveryArgParser(parent_parser) + + def _getParentArgParser(self): + parser = argparse.ArgumentParser(add_help=False) + + parser.add_argument('-v', '--verbose', dest='verbosity', default=self.verbosity, + help='Verbose output', action='store_const', const=2) + parser.add_argument('-q', '--quiet', dest='verbosity', + help='Quiet output', action='store_const', const=0) if self.failfast != False: - parser.add_option('-f', '--failfast', dest='failfast', default=False, - help='Stop on first fail or error', - action='store_true') + parser.add_argument('-f', '--failfast', dest='failfast', default=False, + help='Stop on first fail or error', + action='store_true') if self.catchbreak != False: - parser.add_option('-c', '--catch', dest='catchbreak', default=False, - help='Catch ctrl-C and display results so far', - action='store_true') + parser.add_argument('-c', '--catch', dest='catchbreak', default=False, + help='Catch ctrl-C and display results so far', + action='store_true') if self.buffer != False: - parser.add_option('-b', '--buffer', dest='buffer', default=False, - help='Buffer stdout and stderr during tests', - action='store_true') + parser.add_argument('-b', '--buffer', dest='buffer', default=False, + help='Buffer stdout and stderr during tests', + action='store_true') + + parser.set_defaults(verbosity=self.verbosity, + failfast=False, + catchbreak=False, + buffer=False) + return parser + + def _getMainArgParser(self, parent): + parser = argparse.ArgumentParser(parents=[parent]) + parser.prog = self.progName + parser.print_help = self._print_help + + parser.add_argument('tests', nargs='*', + help='a list of any number of test modules, ' + 'classes and test methods.') + + return parser + + def _getDiscoveryArgParser(self, parent): + parser = argparse.ArgumentParser(parents=[parent]) + parser.prog = '%s discover' % self.progName + #parser.usage = 'fff' + parser.epilog = ('For test discovery all test modules must be ' + 'importable from the top level directory of the ' + 'project.') + + group = parser.add_argument_group(argparse.SUPPRESS, argparse.SUPPRESS) + parser.add_argument('-s', '--start-directory', dest='start', + help="Directory to start discovery ('.' default)") + parser.add_argument('-p', '--pattern', dest='pattern', + help="Pattern to match tests ('test*.py' default)") + parser.add_argument('-t', '--top-level-directory', dest='top', + help='Top level directory of project (defaults to ' + 'start directory)') + group.add_argument('START', nargs='?', help=argparse.SUPPRESS) + group.add_argument('PATTERN', nargs='?', help=argparse.SUPPRESS) + group.add_argument('TOP', nargs='?', help=argparse.SUPPRESS) + + parser.set_defaults(start='.', pattern='test*.py', top=None) return parser def _setAttributesFromOptions(self, options): @@ -210,34 +221,19 @@ if self.buffer is None: self.buffer = options.buffer - if options.verbose: - self.verbosity = 2 - elif options.quiet: - self.verbosity = 0 + self.verbosity = options.verbosity - def _addDiscoveryOptions(self, parser): - parser.add_option('-s', '--start-directory', dest='start', default='.', - help="Directory to start discovery ('.' default)") - parser.add_option('-p', '--pattern', dest='pattern', default='test*.py', - help="Pattern to match tests ('test*.py' default)") - parser.add_option('-t', '--top-level-directory', dest='top', default=None, - help='Top level directory of project (defaults to start directory)') - - def _do_discovery(self, argv, Loader=None): + def _do_discovery(self, argv=(), Loader=None, *, options=None): if Loader is None: Loader = lambda: self.testLoader - # handle command line args for test discovery - self.progName = '%s discover' % self.progName - parser = self._getOptParser() - self._addDiscoveryOptions(parser) - - options, args = parser.parse_args(argv) - if len(args) > 3: - self.usageExit() - - for name, value in zip(('start', 'pattern', 'top'), args): - setattr(options, name, value) + if options is None: + # handle command line args for test discovery + options = self._discovery_parser.parse_args(argv) + for name in ('start', 'pattern', 'top'): + value = getattr(options, name.upper()) + if value is not None: + setattr(options, name, value) self._setAttributesFromOptions(options) diff -r 617cb2f978b0 Lib/unittest/test/test_discovery.py --- a/Lib/unittest/test/test_discovery.py Mon May 13 19:56:35 2013 -0500 +++ b/Lib/unittest/test/test_discovery.py Wed May 15 01:58:48 2013 +0300 @@ -1,6 +1,7 @@ import os import re import sys +from test import support import unittest @@ -14,7 +15,7 @@ testRunner = testLoader = None def __init__(self): - pass + self._initArgParsers() class TestDiscovery(unittest.TestCase): @@ -259,31 +260,30 @@ args = ['something', '-v', '-b', '-v', '-c', '-f'] self.called = False - def do_discovery(argv): + def do_discovery(argv=(), options=None): self.called = True - self.assertEqual(argv, args[1:]) + self.assertEqual(options.verbosity, 2) + self.assertTrue(options.failfast) program._do_discovery = do_discovery program.parseArgs(args) self.assertTrue(self.called) def test_command_line_handling_do_discovery_too_many_arguments(self): - class Stop(Exception): - pass - def usageExit(): - raise Stop - program = TestableTestProgram() - program.usageExit = usageExit program.testLoader = None - with self.assertRaises(Stop): + with support.captured_stderr() as stderr, \ + self.assertRaises(SystemExit) as cm: # too many args program._do_discovery(['one', 'two', 'three', 'four']) + self.assertEqual(cm.exception.args, (2,)) + self.assertIn('usage:', stderr.getvalue()) def test_command_line_handling_do_discovery_uses_default_loader(self): program = object.__new__(unittest.TestProgram) + program._initArgParsers() class Loader(object): args = [] diff -r 617cb2f978b0 Lib/unittest/test/test_program.py --- a/Lib/unittest/test/test_program.py Mon May 13 19:56:35 2013 -0500 +++ b/Lib/unittest/test/test_program.py Wed May 15 01:58:48 2013 +0300 @@ -138,7 +138,7 @@ progName = 'test' test = 'test' def __init__(self, *args): - pass + self._initArgParsers() RESULT = object()