Issue #15302: Switch regrtest from using getopt to using argparse.
This is the first step in refactoring regrtest to use argparse. The regrtest module's main() function still expects a getopt-style return value rather than an argparse.Namespace instance.
This commit is contained in:
parent
f7cd05d7af
commit
d6c18dcd20
|
@ -1,11 +1,18 @@
|
|||
#! /usr/bin/env python3
|
||||
|
||||
"""
|
||||
Usage:
|
||||
Script to run Python regression tests.
|
||||
|
||||
Run this script with -h or --help for documentation.
|
||||
"""
|
||||
|
||||
USAGE = """\
|
||||
python -m test [options] [test_name1 [test_name2 ...]]
|
||||
python path/to/Lib/test/regrtest.py [options] [test_name1 [test_name2 ...]]
|
||||
"""
|
||||
|
||||
DESCRIPTION = """\
|
||||
Run Python regression tests.
|
||||
|
||||
If no arguments or options are provided, finds all files matching
|
||||
the pattern "test_*" in the Lib/test subdirectory and runs
|
||||
|
@ -15,63 +22,10 @@ For more rigorous testing, it is useful to use the following
|
|||
command line:
|
||||
|
||||
python -E -Wd -m test [options] [test_name1 ...]
|
||||
"""
|
||||
|
||||
|
||||
Options:
|
||||
|
||||
-h/--help -- print this text and exit
|
||||
--timeout TIMEOUT
|
||||
-- dump the traceback and exit if a test takes more
|
||||
than TIMEOUT seconds; disabled if TIMEOUT is negative
|
||||
or equals to zero
|
||||
--wait -- wait for user input, e.g., allow a debugger to be attached
|
||||
|
||||
Verbosity
|
||||
|
||||
-v/--verbose -- run tests in verbose mode with output to stdout
|
||||
-w/--verbose2 -- re-run failed tests in verbose mode
|
||||
-W/--verbose3 -- display test output on failure
|
||||
-d/--debug -- print traceback for failed tests
|
||||
-q/--quiet -- no output unless one or more tests fail
|
||||
-o/--slow -- print the slowest 10 tests
|
||||
--header -- print header with interpreter info
|
||||
|
||||
Selecting tests
|
||||
|
||||
-r/--randomize -- randomize test execution order (see below)
|
||||
--randseed -- pass a random seed to reproduce a previous random run
|
||||
-f/--fromfile -- read names of tests to run from a file (see below)
|
||||
-x/--exclude -- arguments are tests to *exclude*
|
||||
-s/--single -- single step through a set of tests (see below)
|
||||
-m/--match PAT -- match test cases and methods with glob pattern PAT
|
||||
-G/--failfast -- fail as soon as a test fails (only with -v or -W)
|
||||
-u/--use RES1,RES2,...
|
||||
-- specify which special resource intensive tests to run
|
||||
-M/--memlimit LIMIT
|
||||
-- run very large memory-consuming tests
|
||||
--testdir DIR
|
||||
-- execute test files in the specified directory (instead
|
||||
of the Python stdlib test suite)
|
||||
|
||||
Special runs
|
||||
|
||||
-l/--findleaks -- if GC is available detect tests that leak memory
|
||||
-L/--runleaks -- run the leaks(1) command just before exit
|
||||
-R/--huntrleaks RUNCOUNTS
|
||||
-- search for reference leaks (needs debug build, v. slow)
|
||||
-j/--multiprocess PROCESSES
|
||||
-- run PROCESSES processes at once
|
||||
-T/--coverage -- turn on code coverage tracing using the trace module
|
||||
-D/--coverdir DIRECTORY
|
||||
-- Directory where coverage files are put
|
||||
-N/--nocoverdir -- Put coverage files alongside modules
|
||||
-t/--threshold THRESHOLD
|
||||
-- call gc.set_threshold(THRESHOLD)
|
||||
-n/--nowindows -- suppress error message boxes on Windows
|
||||
-F/--forever -- run the specified tests in a loop, until an error happens
|
||||
|
||||
|
||||
Additional Option Details:
|
||||
EPILOG = """\
|
||||
Additional option details:
|
||||
|
||||
-r randomizes test execution order. You can use --randseed=int to provide a
|
||||
int seed value for the randomizer; this is useful for reproducing troublesome
|
||||
|
@ -168,9 +122,9 @@ option '-uall,-gui'.
|
|||
# We import importlib *ASAP* in order to test #15386
|
||||
import importlib
|
||||
|
||||
import argparse
|
||||
import builtins
|
||||
import faulthandler
|
||||
import getopt
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
|
@ -248,10 +202,138 @@ RESOURCE_NAMES = ('audio', 'curses', 'largefile', 'network',
|
|||
|
||||
TEMPDIR = os.path.abspath(tempfile.gettempdir())
|
||||
|
||||
def usage(msg):
|
||||
print(msg, file=sys.stderr)
|
||||
print("Use --help for usage", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
def _create_parser():
|
||||
# Set prog to prevent the uninformative "__main__.py" from displaying in
|
||||
# error messages when using "python -m test ...".
|
||||
parser = argparse.ArgumentParser(prog='regrtest.py',
|
||||
usage=USAGE,
|
||||
description=DESCRIPTION,
|
||||
epilog=EPILOG,
|
||||
add_help=False,
|
||||
formatter_class=
|
||||
argparse.RawDescriptionHelpFormatter)
|
||||
|
||||
# Arguments with this clause added to its help are described further in
|
||||
# the epilog's "Additional option details" section.
|
||||
more_details = ' See the section at bottom for more details.'
|
||||
|
||||
group = parser.add_argument_group('General options')
|
||||
# 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',
|
||||
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('--slaveargs', metavar='ARGS')
|
||||
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',
|
||||
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')
|
||||
group.add_argument('-W', '--verbose3', action='store_true',
|
||||
help='display test output on failure')
|
||||
group.add_argument('-d', '--debug', action='store_true',
|
||||
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',
|
||||
help='print the slowest 10 tests')
|
||||
group.add_argument('--header', action='store_true',
|
||||
help='print header with interpreter info')
|
||||
|
||||
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('-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('--testdir', metavar='DIR',
|
||||
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', '--runleaks', action='store_true',
|
||||
help='run the leaks(1) command just before exit.' +
|
||||
more_details)
|
||||
group.add_argument('-R', '--huntrleaks', metavar='RUNCOUNTS',
|
||||
help='search for reference leaks (needs debug build, '
|
||||
'very slow).' + more_details)
|
||||
group.add_argument('-j', '--multiprocess', metavar='PROCESSES',
|
||||
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('-D', '--coverdir', metavar='DIR',
|
||||
help='directory where coverage files are put')
|
||||
group.add_argument('-N', '--nocoverdir', action='store_true',
|
||||
help='put coverage files alongside modules')
|
||||
group.add_argument('-t', '--threshold', metavar='THRESHOLD',
|
||||
help='call gc.set_threshold(THRESHOLD)')
|
||||
group.add_argument('-n', '--nowindows', action='store_true',
|
||||
help='suppress error message boxes on Windows')
|
||||
group.add_argument('-F', '--forever', action='store_true',
|
||||
help='run the specified tests in a loop, until an '
|
||||
'error happens')
|
||||
|
||||
parser.add_argument('args', nargs=argparse.REMAINDER,
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
return parser
|
||||
|
||||
def _convert_namespace_to_getopt(ns):
|
||||
"""Convert an argparse.Namespace object to a getopt-style (opts, args)."""
|
||||
opts = []
|
||||
args_dict = vars(ns)
|
||||
for key in sorted(args_dict.keys()):
|
||||
if key == 'args':
|
||||
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, ns.args
|
||||
|
||||
# This function has a getopt-style return value because regrtest.main()
|
||||
# was originally written using getopt.
|
||||
# TODO: switch this to return an argparse.Namespace instance.
|
||||
def _parse_args(args=None):
|
||||
"""Parse arguments, and return a getopt-style (opts, args).
|
||||
|
||||
This method mimics the return value of getopt.getopt(). In addition,
|
||||
the (option, value) pairs in opts are sorted by option and use the long
|
||||
option string.
|
||||
"""
|
||||
parser = _create_parser()
|
||||
ns = parser.parse_args(args=args)
|
||||
return _convert_namespace_to_getopt(ns)
|
||||
|
||||
|
||||
def main(tests=None, testdir=None, verbose=0, quiet=False,
|
||||
|
@ -298,17 +380,8 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
|
|||
replace_stdout()
|
||||
|
||||
support.record_original_stdout(sys.stdout)
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], '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='])
|
||||
except getopt.error as msg:
|
||||
usage(msg)
|
||||
|
||||
opts, args = _parse_args()
|
||||
|
||||
# Defaults
|
||||
if random_seed is None:
|
||||
|
@ -319,10 +392,7 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
|
|||
start = None
|
||||
timeout = None
|
||||
for o, a in opts:
|
||||
if o in ('-h', '--help'):
|
||||
print(__doc__)
|
||||
return
|
||||
elif o in ('-v', '--verbose'):
|
||||
if o in ('-v', '--verbose'):
|
||||
verbose += 1
|
||||
elif o in ('-w', '--verbose2'):
|
||||
verbose2 = True
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
"""
|
||||
Tests of regrtest.py.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import getopt
|
||||
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._parse_args() matches the prior getopt behavior."""
|
||||
|
||||
def _parse_args(self, args):
|
||||
return regrtest._parse_args(args=args)
|
||||
|
||||
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_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'])
|
||||
|
||||
def test_long_option__partial(self):
|
||||
self._check_args(['--qui'])
|
||||
|
||||
def test_two_options(self):
|
||||
self._check_args(['--quiet', '--exclude'])
|
||||
|
||||
def test_option_with_value(self):
|
||||
self._check_args(['--start', 'foo'])
|
||||
|
||||
def test_option_with_empty_string_value(self):
|
||||
self._check_args(['--start', ''])
|
||||
|
||||
def test_arg(self):
|
||||
self._check_args(['foo'])
|
||||
|
||||
def test_option_and_arg(self):
|
||||
self._check_args(['--quiet', '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()
|
|
@ -534,6 +534,8 @@ Tests
|
|||
- Issue #10646: Tests rearranged for os.samefile/samestat to check for not
|
||||
just symlinks but also hard links.
|
||||
|
||||
- Issue #15302: Switch regrtest from using getopt to using argparse.
|
||||
|
||||
- Issue #15324: Fix regrtest parsing of --fromfile, --match, and --randomize
|
||||
options.
|
||||
|
||||
|
|
Loading…
Reference in New Issue