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:
Chris Jerdonek 2012-12-27 18:53:12 -08:00
parent f7cd05d7af
commit d6c18dcd20
3 changed files with 245 additions and 77 deletions

View File

@ -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

96
Lib/test/test_regrtest.py Normal file
View File

@ -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()

View File

@ -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.