[Python-Dev] [Python-checkins] cpython: Issue #16799: Switched from getopt to argparse style in regrtest's argument
Eli Bendersky
eliben at gmail.com
Thu Aug 29 18:50:08 CEST 2013
Great job, Serhiy. In general, eating our own dogfood is a great idea. The
more we use new Python features in our own stuff, the better.
Eli
On Thu, Aug 29, 2013 at 2:27 AM, serhiy.storchaka <
python-checkins at python.org> wrote:
> http://hg.python.org/cpython/rev/997de0edc5bd
> changeset: 85444:997de0edc5bd
> parent: 85442:676bbd5b0254
> user: Serhiy Storchaka <storchaka at gmail.com>
> date: Thu Aug 29 12:26:23 2013 +0300
> summary:
> Issue #16799: Switched from getopt to argparse style in regrtest's
> argument
> parsing. Added more tests for regrtest's argument parsing.
>
> files:
> Lib/test/regrtest.py | 529 +++++++++++--------------
> Lib/test/test_regrtest.py | 328 ++++++++++++---
> Misc/NEWS | 3 +
> 3 files changed, 500 insertions(+), 360 deletions(-)
>
>
> diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py
> --- a/Lib/test/regrtest.py
> +++ b/Lib/test/regrtest.py
> @@ -233,18 +233,20 @@
> # We add help explicitly to control what argument group it renders
> under.
> group.add_argument('-h', '--help', action='help',
> help='show this help message and exit')
> - group.add_argument('--timeout', metavar='TIMEOUT',
> + group.add_argument('--timeout', metavar='TIMEOUT', type=float,
> help='dump the traceback and exit if a test takes
> '
> 'more than TIMEOUT seconds; disabled if
> TIMEOUT '
> 'is negative or equals to zero')
> - group.add_argument('--wait', action='store_true', help='wait for user
> '
> - 'input, e.g., allow a debugger to be attached')
> + group.add_argument('--wait', action='store_true',
> + help='wait for user input, e.g., allow a debugger '
> + 'to be attached')
> group.add_argument('--slaveargs', metavar='ARGS')
> - group.add_argument('-S', '--start', metavar='START', help='the name
> of '
> - 'the test at which to start.' + more_details)
> + group.add_argument('-S', '--start', metavar='START',
> + help='the name of the test at which to start.' +
> + more_details)
>
> group = parser.add_argument_group('Verbosity')
> - group.add_argument('-v', '--verbose', action='store_true',
> + group.add_argument('-v', '--verbose', action='count',
> help='run tests in verbose mode with output to
> stdout')
> group.add_argument('-w', '--verbose2', action='store_true',
> help='re-run failed tests in verbose mode')
> @@ -254,7 +256,7 @@
> help='print traceback for failed tests')
> group.add_argument('-q', '--quiet', action='store_true',
> help='no output unless one or more tests fail')
> - group.add_argument('-o', '--slow', action='store_true',
> + group.add_argument('-o', '--slow', action='store_true',
> dest='print_slow',
> help='print the slowest 10 tests')
> group.add_argument('--header', action='store_true',
> help='print header with interpreter info')
> @@ -262,45 +264,60 @@
> group = parser.add_argument_group('Selecting tests')
> group.add_argument('-r', '--randomize', action='store_true',
> help='randomize test execution order.' +
> more_details)
> - group.add_argument('--randseed', metavar='SEED', help='pass a random
> seed '
> - 'to reproduce a previous random run')
> - group.add_argument('-f', '--fromfile', metavar='FILE', help='read
> names '
> - 'of tests to run from a file.' + more_details)
> + group.add_argument('--randseed', metavar='SEED',
> + dest='random_seed', type=int,
> + help='pass a random seed to reproduce a previous '
> + 'random run')
> + group.add_argument('-f', '--fromfile', metavar='FILE',
> + help='read names of tests to run from a file.' +
> + more_details)
> group.add_argument('-x', '--exclude', action='store_true',
> help='arguments are tests to *exclude*')
> - group.add_argument('-s', '--single', action='store_true',
> help='single '
> - 'step through a set of tests.' + more_details)
> - group.add_argument('-m', '--match', metavar='PAT', help='match test
> cases '
> - 'and methods with glob pattern PAT')
> - group.add_argument('-G', '--failfast', action='store_true',
> help='fail as '
> - 'soon as a test fails (only with -v or -W)')
> - group.add_argument('-u', '--use', metavar='RES1,RES2,...',
> help='specify '
> - 'which special resource intensive tests to run.' +
> - more_details)
> - group.add_argument('-M', '--memlimit', metavar='LIMIT', help='run
> very '
> - 'large memory-consuming tests.' + more_details)
> + group.add_argument('-s', '--single', action='store_true',
> + help='single step through a set of tests.' +
> + more_details)
> + group.add_argument('-m', '--match', metavar='PAT',
> + dest='match_tests',
> + help='match test cases and methods with glob
> pattern PAT')
> + group.add_argument('-G', '--failfast', action='store_true',
> + help='fail as soon as a test fails (only with -v
> or -W)')
> + group.add_argument('-u', '--use', metavar='RES1,RES2,...',
> + action='append', type=resources_list,
> + help='specify which special resource intensive
> tests '
> + 'to run.' + more_details)
> + group.add_argument('-M', '--memlimit', metavar='LIMIT',
> + help='run very large memory-consuming tests.' +
> + more_details)
> group.add_argument('--testdir', metavar='DIR',
> + type=relative_filename,
> help='execute test files in the specified
> directory '
> '(instead of the Python stdlib test suite)')
>
> group = parser.add_argument_group('Special runs')
> - group.add_argument('-l', '--findleaks', action='store_true', help='if
> GC '
> - 'is available detect tests that leak memory')
> + group.add_argument('-l', '--findleaks', action='store_true',
> + help='if GC is available detect tests that leak
> memory')
> group.add_argument('-L', '--runleaks', action='store_true',
> help='run the leaks(1) command just before exit.' +
> - more_details)
> + more_details)
> group.add_argument('-R', '--huntrleaks', metavar='RUNCOUNTS',
> + type=huntrleaks,
> help='search for reference leaks (needs debug
> build, '
> 'very slow).' + more_details)
> group.add_argument('-j', '--multiprocess', metavar='PROCESSES',
> + dest='use_mp', type=int,
> help='run PROCESSES processes at once')
> - group.add_argument('-T', '--coverage', action='store_true',
> help='turn on '
> - 'code coverage tracing using the trace module')
> + group.add_argument('-T', '--coverage', action='store_true',
> + dest='trace',
> + help='turn on code coverage tracing using the
> trace '
> + 'module')
> group.add_argument('-D', '--coverdir', metavar='DIR',
> + type=relative_filename,
> help='directory where coverage files are put')
> - group.add_argument('-N', '--nocoverdir', action='store_true',
> + group.add_argument('-N', '--nocoverdir',
> + action='store_const', const=None, dest='coverdir',
> help='put coverage files alongside modules')
> group.add_argument('-t', '--threshold', metavar='THRESHOLD',
> + type=int,
> help='call gc.set_threshold(THRESHOLD)')
> group.add_argument('-n', '--nowindows', action='store_true',
> help='suppress error message boxes on Windows')
> @@ -313,43 +330,103 @@
>
> return parser
>
> -# TODO: remove this function as described in issue #16799, for example.
> -# We use this function since regrtest.main() was originally written to use
> -# getopt for parsing.
> -def _convert_namespace_to_getopt(ns):
> - """Convert an argparse.Namespace object to a getopt-style opts list.
> +def relative_filename(string):
> + # CWD is replaced with a temporary dir before calling main(), so we
> + # join it with the saved CWD so it ends up where the user expects.
> + return os.path.join(support.SAVEDCWD, string)
>
> - The return value of this function mimics the first element of
> - getopt.getopt()'s (opts, args) return value. In addition, the
> (option,
> - value) pairs in the opts list are sorted by option and use the long
> - option string. The args part of (opts, args) can be mimicked by the
> - args attribute of the Namespace object we are using in regrtest.
> - """
> - opts = []
> - args_dict = vars(ns)
> - for key in sorted(args_dict.keys()):
> - if key == 'args':
> +def huntrleaks(string):
> + args = string.split(':')
> + if len(args) not in (2, 3):
> + raise argparse.ArgumentTypeError(
> + 'needs 2 or 3 colon-separated arguments')
> + nwarmup = int(args[0]) if args[0] else 5
> + ntracked = int(args[1]) if args[1] else 4
> + fname = args[2] if len(args) > 2 and args[2] else 'reflog.txt'
> + return nwarmup, ntracked, fname
> +
> +def resources_list(string):
> + u = [x.lower() for x in string.split(',')]
> + for r in u:
> + if r == 'all' or r == 'none':
> continue
> - val = args_dict[key]
> - # Don't continue if val equals '' because this means an option
> - # accepting a value was provided the empty string. Such values
> should
> - # show up in the returned opts list.
> - if val is None or val is False:
> - continue
> - if val is True:
> - # Then an option with action store_true was passed. getopt
> - # includes these with value '' in the opts list.
> - val = ''
> - opts.append(('--' + key, val))
> - return opts
> + if r[0] == '-':
> + r = r[1:]
> + if r not in RESOURCE_NAMES:
> + raise argparse.ArgumentTypeError('invalid resource: ' + r)
> + return u
>
> -
> -def main(tests=None, testdir=None, verbose=0, quiet=False,
> +def _parse_args(args, **kwargs):
> + # Defaults
> + ns = argparse.Namespace(testdir=None, verbose=0, quiet=False,
> exclude=False, single=False, randomize=False, fromfile=None,
> findleaks=False, use_resources=None, trace=False,
> coverdir='coverage',
> runleaks=False, huntrleaks=False, verbose2=False,
> print_slow=False,
> random_seed=None, use_mp=None, verbose3=False, forever=False,
> - header=False, failfast=False, match_tests=None):
> + header=False, failfast=False, match_tests=None)
> + for k, v in kwargs.items():
> + if not hasattr(ns, k):
> + raise TypeError('%r is an invalid keyword argument '
> + 'for this function' % k)
> + setattr(ns, k, v)
> + if ns.use_resources is None:
> + ns.use_resources = []
> +
> + parser = _create_parser()
> + parser.parse_args(args=args, namespace=ns)
> +
> + if ns.single and ns.fromfile:
> + parser.error("-s and -f don't go together!")
> + if ns.use_mp and ns.trace:
> + parser.error("-T and -j don't go together!")
> + if ns.use_mp and ns.findleaks:
> + parser.error("-l and -j don't go together!")
> + if ns.use_mp and ns.memlimit:
> + parser.error("-M and -j don't go together!")
> + if ns.failfast and not (ns.verbose or ns.verbose3):
> + parser.error("-G/--failfast needs either -v or -W")
> +
> + if ns.quiet:
> + ns.verbose = 0
> + if ns.timeout is not None:
> + if hasattr(faulthandler, 'dump_traceback_later'):
> + if ns.timeout <= 0:
> + ns.timeout = None
> + else:
> + print("Warning: The timeout option requires "
> + "faulthandler.dump_traceback_later")
> + ns.timeout = None
> + if ns.use_mp is not None:
> + if ns.use_mp <= 0:
> + # Use all cores + extras for tests that like to sleep
> + ns.use_mp = 2 + (os.cpu_count() or 1)
> + if ns.use_mp == 1:
> + ns.use_mp = None
> + if ns.use:
> + for a in ns.use:
> + for r in a:
> + if r == 'all':
> + ns.use_resources[:] = RESOURCE_NAMES
> + continue
> + if r == 'none':
> + del ns.use_resources[:]
> + continue
> + remove = False
> + if r[0] == '-':
> + remove = True
> + r = r[1:]
> + if remove:
> + if r in ns.use_resources:
> + ns.use_resources.remove(r)
> + elif r not in ns.use_resources:
> + ns.use_resources.append(r)
> + if ns.random_seed is not None:
> + ns.randomize = True
> +
> + return ns
> +
> +
> +def main(tests=None, **kwargs):
> """Execute a test suite.
>
> This also parses command-line options and modifies its behavior
> @@ -372,7 +449,6 @@
> directly to set the values that would normally be set by flags
> on the command line.
> """
> -
> # Display the Python traceback on fatal errors (e.g. segfault)
> faulthandler.enable(all_threads=True)
>
> @@ -389,174 +465,48 @@
>
> support.record_original_stdout(sys.stdout)
>
> - parser = _create_parser()
> - ns = parser.parse_args()
> - opts = _convert_namespace_to_getopt(ns)
> - args = ns.args
> - usage = parser.error
> + ns = _parse_args(sys.argv[1:], **kwargs)
>
> - # Defaults
> - if random_seed is None:
> - random_seed = random.randrange(10000000)
> - if use_resources is None:
> - use_resources = []
> - debug = False
> - start = None
> - timeout = None
> - for o, a in opts:
> - if o in ('-v', '--verbose'):
> - verbose += 1
> - elif o in ('-w', '--verbose2'):
> - verbose2 = True
> - elif o in ('-d', '--debug'):
> - debug = True
> - elif o in ('-W', '--verbose3'):
> - verbose3 = True
> - elif o in ('-G', '--failfast'):
> - failfast = True
> - elif o in ('-q', '--quiet'):
> - quiet = True;
> - verbose = 0
> - elif o in ('-x', '--exclude'):
> - exclude = True
> - elif o in ('-S', '--start'):
> - start = a
> - elif o in ('-s', '--single'):
> - single = True
> - elif o in ('-o', '--slow'):
> - print_slow = True
> - elif o in ('-r', '--randomize'):
> - randomize = True
> - elif o == '--randseed':
> - randomize = True
> - random_seed = int(a)
> - elif o in ('-f', '--fromfile'):
> - fromfile = a
> - elif o in ('-m', '--match'):
> - match_tests = a
> - elif o in ('-l', '--findleaks'):
> - findleaks = True
> - elif o in ('-L', '--runleaks'):
> - runleaks = True
> - elif o in ('-t', '--threshold'):
> - import gc
> - gc.set_threshold(int(a))
> - elif o in ('-T', '--coverage'):
> - trace = True
> - elif o in ('-D', '--coverdir'):
> - # CWD is replaced with a temporary dir before calling main(),
> so we
> - # need join it with the saved CWD so it goes where the user
> expects.
> - coverdir = os.path.join(support.SAVEDCWD, a)
> - elif o in ('-N', '--nocoverdir'):
> - coverdir = None
> - elif o in ('-R', '--huntrleaks'):
> - huntrleaks = a.split(':')
> - if len(huntrleaks) not in (2, 3):
> - print(a, huntrleaks)
> - usage('-R takes 2 or 3 colon-separated arguments')
> - if not huntrleaks[0]:
> - huntrleaks[0] = 5
> - else:
> - huntrleaks[0] = int(huntrleaks[0])
> - if not huntrleaks[1]:
> - huntrleaks[1] = 4
> - else:
> - huntrleaks[1] = int(huntrleaks[1])
> - if len(huntrleaks) == 2 or not huntrleaks[2]:
> - huntrleaks[2:] = ["reflog.txt"]
> - # Avoid false positives due to various caches
> - # filling slowly with random data:
> - warm_caches()
> - elif o in ('-M', '--memlimit'):
> - support.set_memlimit(a)
> - elif o in ('-u', '--use'):
> - u = [x.lower() for x in a.split(',')]
> - for r in u:
> - if r == 'all':
> - use_resources[:] = RESOURCE_NAMES
> - continue
> - if r == 'none':
> - del use_resources[:]
> - continue
> - remove = False
> - if r[0] == '-':
> - remove = True
> - r = r[1:]
> - if r not in RESOURCE_NAMES:
> - usage('Invalid -u/--use option: ' + a)
> - if remove:
> - if r in use_resources:
> - use_resources.remove(r)
> - elif r not in use_resources:
> - use_resources.append(r)
> - elif o in ('-n', '--nowindows'):
> - import msvcrt
> - msvcrt.SetErrorMode(msvcrt.SEM_FAILCRITICALERRORS|
> - msvcrt.SEM_NOALIGNMENTFAULTEXCEPT|
> - msvcrt.SEM_NOGPFAULTERRORBOX|
> - msvcrt.SEM_NOOPENFILEERRORBOX)
> - try:
> - msvcrt.CrtSetReportMode
> - except AttributeError:
> - # release build
> - pass
> - else:
> - for m in [msvcrt.CRT_WARN, msvcrt.CRT_ERROR,
> msvcrt.CRT_ASSERT]:
> - msvcrt.CrtSetReportMode(m, msvcrt.CRTDBG_MODE_FILE)
> - msvcrt.CrtSetReportFile(m, msvcrt.CRTDBG_FILE_STDERR)
> - elif o in ('-F', '--forever'):
> - forever = True
> - elif o in ('-j', '--multiprocess'):
> - use_mp = int(a)
> - if use_mp <= 0:
> - # Use all cores + extras for tests that like to sleep
> - use_mp = 2 + (os.cpu_count() or 1)
> - if use_mp == 1:
> - use_mp = None
> - elif o == '--header':
> - header = True
> - elif o == '--slaveargs':
> - args, kwargs = json.loads(a)
> - try:
> - result = runtest(*args, **kwargs)
> - except KeyboardInterrupt:
> - result = INTERRUPTED, ''
> - except BaseException as e:
> - traceback.print_exc()
> - result = CHILD_ERROR, str(e)
> - sys.stdout.flush()
> - print() # Force a newline (just in case)
> - print(json.dumps(result))
> - sys.exit(0)
> - elif o == '--testdir':
> - # CWD is replaced with a temporary dir before calling main(),
> so we
> - # join it with the saved CWD so it ends up where the user
> expects.
> - testdir = os.path.join(support.SAVEDCWD, a)
> - elif o == '--timeout':
> - if hasattr(faulthandler, 'dump_traceback_later'):
> - timeout = float(a)
> - if timeout <= 0:
> - timeout = None
> - else:
> - print("Warning: The timeout option requires "
> - "faulthandler.dump_traceback_later")
> - timeout = None
> - elif o == '--wait':
> - input("Press any key to continue...")
> + if ns.huntrleaks:
> + # Avoid false positives due to various caches
> + # filling slowly with random data:
> + warm_caches()
> + if ns.memlimit is not None:
> + support.set_memlimit(ns.memlimit)
> + if ns.threshold is not None:
> + import gc
> + gc.set_threshold(ns.threshold)
> + if ns.nowindows:
> + import msvcrt
> + msvcrt.SetErrorMode(msvcrt.SEM_FAILCRITICALERRORS|
> + msvcrt.SEM_NOALIGNMENTFAULTEXCEPT|
> + msvcrt.SEM_NOGPFAULTERRORBOX|
> + msvcrt.SEM_NOOPENFILEERRORBOX)
> + try:
> + msvcrt.CrtSetReportMode
> + except AttributeError:
> + # release build
> + pass
> else:
> - print(("No handler for option {}. Please report this as a
> bug "
> - "at http://bugs.python.org.").format(o),
> file=sys.stderr)
> - sys.exit(1)
> - if single and fromfile:
> - usage("-s and -f don't go together!")
> - if use_mp and trace:
> - usage("-T and -j don't go together!")
> - if use_mp and findleaks:
> - usage("-l and -j don't go together!")
> - if use_mp and support.max_memuse:
> - usage("-M and -j don't go together!")
> - if failfast and not (verbose or verbose3):
> - usage("-G/--failfast needs either -v or -W")
> + for m in [msvcrt.CRT_WARN, msvcrt.CRT_ERROR,
> msvcrt.CRT_ASSERT]:
> + msvcrt.CrtSetReportMode(m, msvcrt.CRTDBG_MODE_FILE)
> + msvcrt.CrtSetReportFile(m, msvcrt.CRTDBG_FILE_STDERR)
> + if ns.wait:
> + input("Press any key to continue...")
> +
> + if ns.slaveargs is not None:
> + args, kwargs = json.loads(ns.slaveargs)
> + try:
> + result = runtest(*args, **kwargs)
> + except KeyboardInterrupt:
> + result = INTERRUPTED, ''
> + except BaseException as e:
> + traceback.print_exc()
> + result = CHILD_ERROR, str(e)
> + sys.stdout.flush()
> + print() # Force a newline (just in case)
> + print(json.dumps(result))
> + sys.exit(0)
>
> good = []
> bad = []
> @@ -565,12 +515,12 @@
> environment_changed = []
> interrupted = False
>
> - if findleaks:
> + if ns.findleaks:
> try:
> import gc
> except ImportError:
> print('No GC available, disabling findleaks.')
> - findleaks = False
> + ns.findleaks = False
> else:
> # Uncomment the line below to report garbage that is not
> # freeable by reference counting alone. By default only
> @@ -578,42 +528,40 @@
> #gc.set_debug(gc.DEBUG_SAVEALL)
> found_garbage = []
>
> - if single:
> + if ns.single:
> filename = os.path.join(TEMPDIR, 'pynexttest')
> try:
> - fp = open(filename, 'r')
> - next_test = fp.read().strip()
> - tests = [next_test]
> - fp.close()
> + with open(filename, 'r') as fp:
> + next_test = fp.read().strip()
> + tests = [next_test]
> except OSError:
> pass
>
> - if fromfile:
> + if ns.fromfile:
> tests = []
> - fp = open(os.path.join(support.SAVEDCWD, fromfile))
> - count_pat = re.compile(r'\[\s*\d+/\s*\d+\]')
> - for line in fp:
> - line = count_pat.sub('', line)
> - guts = line.split() # assuming no test has whitespace in its
> name
> - if guts and not guts[0].startswith('#'):
> - tests.extend(guts)
> - fp.close()
> + with open(os.path.join(support.SAVEDCWD, ns.fromfile)) as fp:
> + count_pat = re.compile(r'\[\s*\d+/\s*\d+\]')
> + for line in fp:
> + line = count_pat.sub('', line)
> + guts = line.split() # assuming no test has whitespace in
> its name
> + if guts and not guts[0].startswith('#'):
> + tests.extend(guts)
>
> # Strip .py extensions.
> - removepy(args)
> + removepy(ns.args)
> removepy(tests)
>
> stdtests = STDTESTS[:]
> nottests = NOTTESTS.copy()
> - if exclude:
> - for arg in args:
> + if ns.exclude:
> + for arg in ns.args:
> if arg in stdtests:
> stdtests.remove(arg)
> nottests.add(arg)
> - args = []
> + ns.args = []
>
> # For a partial run, we do not need to clutter the output.
> - if verbose or header or not (quiet or single or tests or args):
> + if ns.verbose or ns.header or not (ns.quiet or ns.single or tests or
> ns.args):
> # Print basic platform information
> print("==", platform.python_implementation(),
> *sys.version.split())
> print("== ", platform.platform(aliased=True),
> @@ -623,37 +571,39 @@
>
> # if testdir is set, then we are not running the python tests suite,
> so
> # don't add default tests to be executed or skipped (pass empty
> values)
> - if testdir:
> - alltests = findtests(testdir, list(), set())
> + if ns.testdir:
> + alltests = findtests(ns.testdir, list(), set())
> else:
> - alltests = findtests(testdir, stdtests, nottests)
> + alltests = findtests(ns.testdir, stdtests, nottests)
>
> - selected = tests or args or alltests
> - if single:
> + selected = tests or ns.args or alltests
> + if ns.single:
> selected = selected[:1]
> try:
> next_single_test = alltests[alltests.index(selected[0])+1]
> except IndexError:
> next_single_test = None
> # Remove all the selected tests that precede start if it's set.
> - if start:
> + if ns.start:
> try:
> - del selected[:selected.index(start)]
> + del selected[:selected.index(ns.start)]
> except ValueError:
> - print("Couldn't find starting test (%s), using all tests" %
> start)
> - if randomize:
> - random.seed(random_seed)
> - print("Using random seed", random_seed)
> + print("Couldn't find starting test (%s), using all tests" %
> ns.start)
> + if ns.randomize:
> + if ns.random_seed is None:
> + ns.random_seed = random.randrange(10000000)
> + random.seed(ns.random_seed)
> + print("Using random seed", ns.random_seed)
> random.shuffle(selected)
> - if trace:
> + if ns.trace:
> import trace, tempfile
> tracer = trace.Trace(ignoredirs=[sys.base_prefix,
> sys.base_exec_prefix,
> tempfile.gettempdir()],
> trace=False, count=True)
>
> test_times = []
> - support.verbose = verbose # Tell tests to be moderately quiet
> - support.use_resources = use_resources
> + support.verbose = ns.verbose # Tell tests to be moderately quiet
> + support.use_resources = ns.use_resources
> save_modules = sys.modules.keys()
>
> def accumulate_result(test, result):
> @@ -671,7 +621,7 @@
> skipped.append(test)
> resource_denieds.append(test)
>
> - if forever:
> + if ns.forever:
> def test_forever(tests=list(selected)):
> while True:
> for test in tests:
> @@ -686,7 +636,7 @@
> test_count = '/{}'.format(len(selected))
> test_count_width = len(test_count) - 1
>
> - if use_mp:
> + if ns.use_mp:
> try:
> from threading import Thread
> except ImportError:
> @@ -710,11 +660,12 @@
> output.put((None, None, None, None))
> return
> args_tuple = (
> - (test, verbose, quiet),
> - dict(huntrleaks=huntrleaks,
> use_resources=use_resources,
> - debug=debug, output_on_failure=verbose3,
> - timeout=timeout, failfast=failfast,
> - match_tests=match_tests)
> + (test, ns.verbose, ns.quiet),
> + dict(huntrleaks=ns.huntrleaks,
> + use_resources=ns.use_resources,
> + debug=ns.debug,
> output_on_failure=ns.verbose3,
> + timeout=ns.timeout, failfast=ns.failfast,
> + match_tests=ns.match_tests)
> )
> # -E is needed by some tests, e.g. test_import
> # Running the child from the same working directory
> ensures
> @@ -743,19 +694,19 @@
> except BaseException:
> output.put((None, None, None, None))
> raise
> - workers = [Thread(target=work) for i in range(use_mp)]
> + workers = [Thread(target=work) for i in range(ns.use_mp)]
> for worker in workers:
> worker.start()
> finished = 0
> test_index = 1
> try:
> - while finished < use_mp:
> + while finished < ns.use_mp:
> test, stdout, stderr, result = output.get()
> if test is None:
> finished += 1
> continue
> accumulate_result(test, result)
> - if not quiet:
> + if not ns.quiet:
> fmt = "[{1:{0}}{2}/{3}] {4}" if bad else
> "[{1:{0}}{2}] {4}"
> print(fmt.format(
> test_count_width, test_index, test_count,
> @@ -778,29 +729,30 @@
> worker.join()
> else:
> for test_index, test in enumerate(tests, 1):
> - if not quiet:
> + if not ns.quiet:
> fmt = "[{1:{0}}{2}/{3}] {4}" if bad else "[{1:{0}}{2}]
> {4}"
> print(fmt.format(
> test_count_width, test_index, test_count, len(bad),
> test))
> sys.stdout.flush()
> - if trace:
> + if ns.trace:
> # If we're tracing code coverage, then we don't exit with
> status
> # if on a false return value from main.
> - tracer.runctx('runtest(test, verbose, quiet,
> timeout=timeout)',
> + tracer.runctx('runtest(test, ns.verbose, ns.quiet,
> timeout=ns.timeout)',
> globals=globals(), locals=vars())
> else:
> try:
> - result = runtest(test, verbose, quiet, huntrleaks,
> debug,
> - output_on_failure=verbose3,
> - timeout=timeout, failfast=failfast,
> - match_tests=match_tests)
> + result = runtest(test, ns.verbose, ns.quiet,
> + ns.huntrleaks, ns.debug,
> + output_on_failure=ns.verbose3,
> + timeout=ns.timeout,
> failfast=ns.failfast,
> + match_tests=ns.match_tests)
> accumulate_result(test, result)
> except KeyboardInterrupt:
> interrupted = True
> break
> except:
> raise
> - if findleaks:
> + if ns.findleaks:
> gc.collect()
> if gc.garbage:
> print("Warning: test created", len(gc.garbage), end='
> ')
> @@ -821,11 +773,11 @@
> omitted = set(selected) - set(good) - set(bad) - set(skipped)
> print(count(len(omitted), "test"), "omitted:")
> printlist(omitted)
> - if good and not quiet:
> + if good and not ns.quiet:
> if not bad and not skipped and not interrupted and len(good) > 1:
> print("All", end=' ')
> print(count(len(good), "test"), "OK.")
> - if print_slow:
> + if ns.print_slow:
> test_times.sort(reverse=True)
> print("10 slowest tests:")
> for time, test in test_times[:10]:
> @@ -839,18 +791,19 @@
> print("{} altered the execution environment:".format(
> count(len(environment_changed), "test")))
> printlist(environment_changed)
> - if skipped and not quiet:
> + if skipped and not ns.quiet:
> print(count(len(skipped), "test"), "skipped:")
> printlist(skipped)
>
> - if verbose2 and bad:
> + if ns.verbose2 and bad:
> print("Re-running failed tests in verbose mode")
> for test in bad:
> print("Re-running test %r in verbose mode" % test)
> sys.stdout.flush()
> try:
> - verbose = True
> - ok = runtest(test, True, quiet, huntrleaks, debug,
> timeout=timeout)
> + ns.verbose = True
> + ok = runtest(test, True, ns.quiet, ns.huntrleaks,
> ns.debug,
> + timeout=ns.timeout)
> except KeyboardInterrupt:
> # print a newline separate from the ^C
> print()
> @@ -858,18 +811,18 @@
> except:
> raise
>
> - if single:
> + if ns.single:
> if next_single_test:
> with open(filename, 'w') as fp:
> fp.write(next_single_test + '\n')
> else:
> os.unlink(filename)
>
> - if trace:
> + if ns.trace:
> r = tracer.results()
> - r.write_results(show_missing=True, summary=True,
> coverdir=coverdir)
> + r.write_results(show_missing=True, summary=True,
> coverdir=ns.coverdir)
>
> - if runleaks:
> + if ns.runleaks:
> os.system("leaks %d" % os.getpid())
>
> sys.exit(len(bad) > 0 or interrupted)
> diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py
> --- a/Lib/test/test_regrtest.py
> +++ b/Lib/test/test_regrtest.py
> @@ -4,97 +4,281 @@
>
> import argparse
> import getopt
> +import os.path
> import unittest
> from test import regrtest, support
>
> -def old_parse_args(args):
> - """Parse arguments as regrtest did strictly prior to 3.4.
> -
> - Raises getopt.GetoptError on bad arguments.
> - """
> - return getopt.getopt(args, 'hvqxsoS:rf:lu:t:TD:NLR:FdwWM:nj:Gm:',
> - ['help', 'verbose', 'verbose2', 'verbose3', 'quiet',
> - 'exclude', 'single', 'slow', 'randomize', 'fromfile=',
> 'findleaks',
> - 'use=', 'threshold=', 'coverdir=', 'nocoverdir',
> - 'runleaks', 'huntrleaks=', 'memlimit=', 'randseed=',
> - 'multiprocess=', 'coverage', 'slaveargs=', 'forever', 'debug',
> - 'start=', 'nowindows', 'header', 'testdir=', 'timeout=', 'wait',
> - 'failfast', 'match='])
> -
> class ParseArgsTestCase(unittest.TestCase):
>
> - """Test that regrtest's parsing code matches the prior getopt
> behavior."""
> + """Test regrtest's argument parsing."""
>
> - def _parse_args(self, args):
> - # This is the same logic as that used in regrtest.main()
> - parser = regrtest._create_parser()
> - ns = parser.parse_args(args=args)
> - opts = regrtest._convert_namespace_to_getopt(ns)
> - return opts, ns.args
> + def checkError(self, args, msg):
> + with support.captured_stderr() as err,
> self.assertRaises(SystemExit):
> + regrtest._parse_args(args)
> + self.assertIn(msg, err.getvalue())
>
> - def _check_args(self, args, expected=None):
> - """
> - The expected parameter is for cases when the behavior of the new
> - parse_args differs from the old (but deliberately so).
> - """
> - if expected is None:
> - try:
> - expected = old_parse_args(args)
> - except getopt.GetoptError:
> - # Suppress usage string output when an
> argparse.ArgumentError
> - # error is raised.
> - with support.captured_stderr():
> - self.assertRaises(SystemExit, self._parse_args, args)
> - return
> - # The new parse_args() sorts by long option string.
> - expected[0].sort()
> - actual = self._parse_args(args)
> - self.assertEqual(actual, expected)
> + def test_help(self):
> + for opt in '-h', '--help':
> + with self.subTest(opt=opt):
> + with support.captured_stdout() as out, \
> + self.assertRaises(SystemExit):
> + regrtest._parse_args([opt])
> + self.assertIn('Run Python regression tests.',
> out.getvalue())
> +
> + def test_timeout(self):
> + ns = regrtest._parse_args(['--timeout', '4.2'])
> + self.assertEqual(ns.timeout, 4.2)
> + self.checkError(['--timeout'], 'expected one argument')
> + self.checkError(['--timeout', 'foo'], 'invalid float value')
> +
> + def test_wait(self):
> + ns = regrtest._parse_args(['--wait'])
> + self.assertTrue(ns.wait)
> +
> + def test_slaveargs(self):
> + ns = regrtest._parse_args(['--slaveargs', '[[], {}]'])
> + self.assertEqual(ns.slaveargs, '[[], {}]')
> + self.checkError(['--slaveargs'], 'expected one argument')
> +
> + def test_start(self):
> + for opt in '-S', '--start':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt, 'foo'])
> + self.assertEqual(ns.start, 'foo')
> + self.checkError([opt], 'expected one argument')
> +
> + def test_verbose(self):
> + ns = regrtest._parse_args(['-v'])
> + self.assertEqual(ns.verbose, 1)
> + ns = regrtest._parse_args(['-vvv'])
> + self.assertEqual(ns.verbose, 3)
> + ns = regrtest._parse_args(['--verbose'])
> + self.assertEqual(ns.verbose, 1)
> + ns = regrtest._parse_args(['--verbose'] * 3)
> + self.assertEqual(ns.verbose, 3)
> + ns = regrtest._parse_args([])
> + self.assertEqual(ns.verbose, 0)
> +
> + def test_verbose2(self):
> + for opt in '-w', '--verbose2':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt])
> + self.assertTrue(ns.verbose2)
> +
> + def test_verbose3(self):
> + for opt in '-W', '--verbose3':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt])
> + self.assertTrue(ns.verbose3)
> +
> + def test_debug(self):
> + for opt in '-d', '--debug':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt])
> + self.assertTrue(ns.debug)
> +
> + def test_quiet(self):
> + for opt in '-q', '--quiet':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt])
> + self.assertTrue(ns.quiet)
> + self.assertEqual(ns.verbose, 0)
> +
> + def test_slow(self):
> + for opt in '-o', '--slow':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt])
> + self.assertTrue(ns.print_slow)
> +
> + def test_header(self):
> + ns = regrtest._parse_args(['--header'])
> + self.assertTrue(ns.header)
> +
> + def test_randomize(self):
> + for opt in '-r', '--randomize':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt])
> + self.assertTrue(ns.randomize)
> +
> + def test_randseed(self):
> + ns = regrtest._parse_args(['--randseed', '12345'])
> + self.assertEqual(ns.random_seed, 12345)
> + self.assertTrue(ns.randomize)
> + self.checkError(['--randseed'], 'expected one argument')
> + self.checkError(['--randseed', 'foo'], 'invalid int value')
> +
> + def test_fromfile(self):
> + for opt in '-f', '--fromfile':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt, 'foo'])
> + self.assertEqual(ns.fromfile, 'foo')
> + self.checkError([opt], 'expected one argument')
> + self.checkError([opt, 'foo', '-s'], "don't go together")
> +
> + def test_exclude(self):
> + for opt in '-x', '--exclude':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt])
> + self.assertTrue(ns.exclude)
> +
> + def test_single(self):
> + for opt in '-s', '--single':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt])
> + self.assertTrue(ns.single)
> + self.checkError([opt, '-f', 'foo'], "don't go together")
> +
> + def test_match(self):
> + for opt in '-m', '--match':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt, 'pattern'])
> + self.assertEqual(ns.match_tests, 'pattern')
> + self.checkError([opt], 'expected one argument')
> +
> + def test_failfast(self):
> + for opt in '-G', '--failfast':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt, '-v'])
> + self.assertTrue(ns.failfast)
> + ns = regrtest._parse_args([opt, '-W'])
> + self.assertTrue(ns.failfast)
> + self.checkError([opt], '-G/--failfast needs either -v or
> -W')
> +
> + def test_use(self):
> + for opt in '-u', '--use':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt, 'gui,network'])
> + self.assertEqual(ns.use_resources, ['gui', 'network'])
> + ns = regrtest._parse_args([opt, 'gui,none,network'])
> + self.assertEqual(ns.use_resources, ['network'])
> + expected = list(regrtest.RESOURCE_NAMES)
> + expected.remove('gui')
> + ns = regrtest._parse_args([opt, 'all,-gui'])
> + self.assertEqual(ns.use_resources, expected)
> + self.checkError([opt], 'expected one argument')
> + self.checkError([opt, 'foo'], 'invalid resource')
> +
> + def test_memlimit(self):
> + for opt in '-M', '--memlimit':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt, '4G'])
> + self.assertEqual(ns.memlimit, '4G')
> + self.checkError([opt], 'expected one argument')
> +
> + def test_testdir(self):
> + ns = regrtest._parse_args(['--testdir', 'foo'])
> + self.assertEqual(ns.testdir, os.path.join(support.SAVEDCWD,
> 'foo'))
> + self.checkError(['--testdir'], 'expected one argument')
> +
> + def test_findleaks(self):
> + for opt in '-l', '--findleaks':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt])
> + self.assertTrue(ns.findleaks)
> +
> + def test_findleaks(self):
> + for opt in '-L', '--runleaks':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt])
> + self.assertTrue(ns.runleaks)
> +
> + def test_findleaks(self):
> + for opt in '-R', '--huntrleaks':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt, ':'])
> + self.assertEqual(ns.huntrleaks, (5, 4, 'reflog.txt'))
> + ns = regrtest._parse_args([opt, '6:'])
> + self.assertEqual(ns.huntrleaks, (6, 4, 'reflog.txt'))
> + ns = regrtest._parse_args([opt, ':3'])
> + self.assertEqual(ns.huntrleaks, (5, 3, 'reflog.txt'))
> + ns = regrtest._parse_args([opt, '6:3:leaks.log'])
> + self.assertEqual(ns.huntrleaks, (6, 3, 'leaks.log'))
> + self.checkError([opt], 'expected one argument')
> + self.checkError([opt, '6'],
> + 'needs 2 or 3 colon-separated arguments')
> + self.checkError([opt, 'foo:'], 'invalid huntrleaks value')
> + self.checkError([opt, '6:foo'], 'invalid huntrleaks
> value')
> +
> + def test_multiprocess(self):
> + for opt in '-j', '--multiprocess':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt, '2'])
> + self.assertEqual(ns.use_mp, 2)
> + self.checkError([opt], 'expected one argument')
> + self.checkError([opt, 'foo'], 'invalid int value')
> + self.checkError([opt, '2', '-T'], "don't go together")
> + self.checkError([opt, '2', '-l'], "don't go together")
> + self.checkError([opt, '2', '-M', '4G'], "don't go
> together")
> +
> + def test_findleaks(self):
> + for opt in '-T', '--coverage':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt])
> + self.assertTrue(ns.trace)
> +
> + def test_coverdir(self):
> + for opt in '-D', '--coverdir':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt, 'foo'])
> + self.assertEqual(ns.coverdir,
> + os.path.join(support.SAVEDCWD, 'foo'))
> + self.checkError([opt], 'expected one argument')
> +
> + def test_nocoverdir(self):
> + for opt in '-N', '--nocoverdir':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt])
> + self.assertIsNone(ns.coverdir)
> +
> + def test_threshold(self):
> + for opt in '-t', '--threshold':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt, '1000'])
> + self.assertEqual(ns.threshold, 1000)
> + self.checkError([opt], 'expected one argument')
> + self.checkError([opt, 'foo'], 'invalid int value')
> +
> + def test_nowindows(self):
> + for opt in '-n', '--nowindows':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt])
> + self.assertTrue(ns.nowindows)
> +
> + def test_forever(self):
> + for opt in '-F', '--forever':
> + with self.subTest(opt=opt):
> + ns = regrtest._parse_args([opt])
> + self.assertTrue(ns.forever)
> +
>
> def test_unrecognized_argument(self):
> - self._check_args(['--xxx'])
> -
> - def test_value_not_provided(self):
> - self._check_args(['--start'])
> -
> - def test_short_option(self):
> - # getopt returns the short option whereas argparse returns the
> long.
> - expected = ([('--quiet', '')], [])
> - self._check_args(['-q'], expected=expected)
> -
> - def test_long_option(self):
> - self._check_args(['--quiet'])
> + self.checkError(['--xxx'], 'usage:')
>
> def test_long_option__partial(self):
> - self._check_args(['--qui'])
> + ns = regrtest._parse_args(['--qui'])
> + self.assertTrue(ns.quiet)
> + self.assertEqual(ns.verbose, 0)
>
> def test_two_options(self):
> - self._check_args(['--quiet', '--exclude'])
> -
> - def test_option_with_value(self):
> - self._check_args(['--start', 'foo'])
> + ns = regrtest._parse_args(['--quiet', '--exclude'])
> + self.assertTrue(ns.quiet)
> + self.assertEqual(ns.verbose, 0)
> + self.assertTrue(ns.exclude)
>
> def test_option_with_empty_string_value(self):
> - self._check_args(['--start', ''])
> + ns = regrtest._parse_args(['--start', ''])
> + self.assertEqual(ns.start, '')
>
> def test_arg(self):
> - self._check_args(['foo'])
> + ns = regrtest._parse_args(['foo'])
> + self.assertEqual(ns.args, ['foo'])
>
> def test_option_and_arg(self):
> - self._check_args(['--quiet', 'foo'])
> + ns = regrtest._parse_args(['--quiet', 'foo'])
> + self.assertTrue(ns.quiet)
> + self.assertEqual(ns.verbose, 0)
> + self.assertEqual(ns.args, ['foo'])
>
> - def test_fromfile(self):
> - self._check_args(['--fromfile', 'file'])
> -
> - def test_match(self):
> - self._check_args(['--match', 'pattern'])
> -
> - def test_randomize(self):
> - self._check_args(['--randomize'])
> -
> -
> -def test_main():
> - support.run_unittest(__name__)
>
> if __name__ == '__main__':
> - test_main()
> + unittest.main()
> diff --git a/Misc/NEWS b/Misc/NEWS
> --- a/Misc/NEWS
> +++ b/Misc/NEWS
> @@ -153,6 +153,9 @@
> Tests
> -----
>
> +- Issue #16799: Switched from getopt to argparse style in regrtest's
> argument
> + parsing. Added more tests for regrtest's argument parsing.
> +
> - Issue #18792: Use "127.0.0.1" or "::1" instead of "localhost" as much as
> possible, since "localhost" goes through a DNS lookup under recent
> Windows
> versions.
>
> --
> Repository URL: http://hg.python.org/cpython
>
> _______________________________________________
> Python-checkins mailing list
> Python-checkins at python.org
> http://mail.python.org/mailman/listinfo/python-checkins
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20130829/aa4f6185/attachment-0001.html>
More information about the Python-Dev
mailing list