Issue #16799: Switched from getopt to argparse style in regrtest's argument

parsing.  Added more tests for regrtest's argument parsing.
This commit is contained in:
Serhiy Storchaka 2013-08-29 12:26:23 +03:00
parent 48e6a8c88a
commit 64f7c4e4ca
3 changed files with 498 additions and 358 deletions

View file

@ -233,18 +233,20 @@ def _create_parser():
# We add help explicitly to control what argument group it renders under. # We add help explicitly to control what argument group it renders under.
group.add_argument('-h', '--help', action='help', group.add_argument('-h', '--help', action='help',
help='show this help message and exit') 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 ' help='dump the traceback and exit if a test takes '
'more than TIMEOUT seconds; disabled if TIMEOUT ' 'more than TIMEOUT seconds; disabled if TIMEOUT '
'is negative or equals to zero') 'is negative or equals to zero')
group.add_argument('--wait', action='store_true', help='wait for user ' group.add_argument('--wait', action='store_true',
'input, e.g., allow a debugger to be attached') help='wait for user input, e.g., allow a debugger '
'to be attached')
group.add_argument('--slaveargs', metavar='ARGS') group.add_argument('--slaveargs', metavar='ARGS')
group.add_argument('-S', '--start', metavar='START', help='the name of ' group.add_argument('-S', '--start', metavar='START',
'the test at which to start.' + more_details) help='the name of the test at which to start.' +
more_details)
group = parser.add_argument_group('Verbosity') 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') help='run tests in verbose mode with output to stdout')
group.add_argument('-w', '--verbose2', action='store_true', group.add_argument('-w', '--verbose2', action='store_true',
help='re-run failed tests in verbose mode') help='re-run failed tests in verbose mode')
@ -254,7 +256,7 @@ def _create_parser():
help='print traceback for failed tests') help='print traceback for failed tests')
group.add_argument('-q', '--quiet', action='store_true', group.add_argument('-q', '--quiet', action='store_true',
help='no output unless one or more tests fail') 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') help='print the slowest 10 tests')
group.add_argument('--header', action='store_true', group.add_argument('--header', action='store_true',
help='print header with interpreter info') help='print header with interpreter info')
@ -262,45 +264,60 @@ def _create_parser():
group = parser.add_argument_group('Selecting tests') group = parser.add_argument_group('Selecting tests')
group.add_argument('-r', '--randomize', action='store_true', group.add_argument('-r', '--randomize', action='store_true',
help='randomize test execution order.' + more_details) help='randomize test execution order.' + more_details)
group.add_argument('--randseed', metavar='SEED', help='pass a random seed ' group.add_argument('--randseed', metavar='SEED',
'to reproduce a previous random run') dest='random_seed', type=int,
group.add_argument('-f', '--fromfile', metavar='FILE', help='read names ' help='pass a random seed to reproduce a previous '
'of tests to run from a file.' + more_details) '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', group.add_argument('-x', '--exclude', action='store_true',
help='arguments are tests to *exclude*') help='arguments are tests to *exclude*')
group.add_argument('-s', '--single', action='store_true', help='single ' group.add_argument('-s', '--single', action='store_true',
'step through a set of tests.' + more_details) help='single step through a set of tests.' +
group.add_argument('-m', '--match', metavar='PAT', help='match test cases ' more_details)
'and methods with glob pattern PAT') group.add_argument('-m', '--match', metavar='PAT',
group.add_argument('-G', '--failfast', action='store_true', help='fail as ' dest='match_tests',
'soon as a test fails (only with -v or -W)') help='match test cases and methods with glob pattern PAT')
group.add_argument('-u', '--use', metavar='RES1,RES2,...', help='specify ' group.add_argument('-G', '--failfast', action='store_true',
'which special resource intensive tests to run.' + 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) more_details)
group.add_argument('-M', '--memlimit', metavar='LIMIT', help='run very '
'large memory-consuming tests.' + more_details)
group.add_argument('--testdir', metavar='DIR', group.add_argument('--testdir', metavar='DIR',
type=relative_filename,
help='execute test files in the specified directory ' help='execute test files in the specified directory '
'(instead of the Python stdlib test suite)') '(instead of the Python stdlib test suite)')
group = parser.add_argument_group('Special runs') group = parser.add_argument_group('Special runs')
group.add_argument('-l', '--findleaks', action='store_true', help='if GC ' group.add_argument('-l', '--findleaks', action='store_true',
'is available detect tests that leak memory') help='if GC is available detect tests that leak memory')
group.add_argument('-L', '--runleaks', action='store_true', group.add_argument('-L', '--runleaks', action='store_true',
help='run the leaks(1) command just before exit.' + help='run the leaks(1) command just before exit.' +
more_details) more_details)
group.add_argument('-R', '--huntrleaks', metavar='RUNCOUNTS', group.add_argument('-R', '--huntrleaks', metavar='RUNCOUNTS',
type=huntrleaks,
help='search for reference leaks (needs debug build, ' help='search for reference leaks (needs debug build, '
'very slow).' + more_details) 'very slow).' + more_details)
group.add_argument('-j', '--multiprocess', metavar='PROCESSES', group.add_argument('-j', '--multiprocess', metavar='PROCESSES',
dest='use_mp', type=int,
help='run PROCESSES processes at once') help='run PROCESSES processes at once')
group.add_argument('-T', '--coverage', action='store_true', help='turn on ' group.add_argument('-T', '--coverage', action='store_true',
'code coverage tracing using the trace module') dest='trace',
help='turn on code coverage tracing using the trace '
'module')
group.add_argument('-D', '--coverdir', metavar='DIR', group.add_argument('-D', '--coverdir', metavar='DIR',
type=relative_filename,
help='directory where coverage files are put') 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') help='put coverage files alongside modules')
group.add_argument('-t', '--threshold', metavar='THRESHOLD', group.add_argument('-t', '--threshold', metavar='THRESHOLD',
type=int,
help='call gc.set_threshold(THRESHOLD)') help='call gc.set_threshold(THRESHOLD)')
group.add_argument('-n', '--nowindows', action='store_true', group.add_argument('-n', '--nowindows', action='store_true',
help='suppress error message boxes on Windows') help='suppress error message boxes on Windows')
@ -313,43 +330,103 @@ def _create_parser():
return parser return parser
# TODO: remove this function as described in issue #16799, for example. def relative_filename(string):
# We use this function since regrtest.main() was originally written to use # CWD is replaced with a temporary dir before calling main(), so we
# getopt for parsing. # join it with the saved CWD so it ends up where the user expects.
def _convert_namespace_to_getopt(ns): return os.path.join(support.SAVEDCWD, string)
"""Convert an argparse.Namespace object to a getopt-style opts list.
The return value of this function mimics the first element of def huntrleaks(string):
getopt.getopt()'s (opts, args) return value. In addition, the (option, args = string.split(':')
value) pairs in the opts list are sorted by option and use the long if len(args) not in (2, 3):
option string. The args part of (opts, args) can be mimicked by the raise argparse.ArgumentTypeError(
args attribute of the Namespace object we are using in regrtest. 'needs 2 or 3 colon-separated arguments')
""" nwarmup = int(args[0]) if args[0] else 5
opts = [] ntracked = int(args[1]) if args[1] else 4
args_dict = vars(ns) fname = args[2] if len(args) > 2 and args[2] else 'reflog.txt'
for key in sorted(args_dict.keys()): return nwarmup, ntracked, fname
if key == 'args':
def resources_list(string):
u = [x.lower() for x in string.split(',')]
for r in u:
if r == 'all' or r == 'none':
continue continue
val = args_dict[key] if r[0] == '-':
# Don't continue if val equals '' because this means an option r = r[1:]
# accepting a value was provided the empty string. Such values should if r not in RESOURCE_NAMES:
# show up in the returned opts list. raise argparse.ArgumentTypeError('invalid resource: ' + r)
if val is None or val is False: return u
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
def _parse_args(args, **kwargs):
def main(tests=None, testdir=None, verbose=0, quiet=False, # Defaults
ns = argparse.Namespace(testdir=None, verbose=0, quiet=False,
exclude=False, single=False, randomize=False, fromfile=None, exclude=False, single=False, randomize=False, fromfile=None,
findleaks=False, use_resources=None, trace=False, coverdir='coverage', findleaks=False, use_resources=None, trace=False, coverdir='coverage',
runleaks=False, huntrleaks=False, verbose2=False, print_slow=False, runleaks=False, huntrleaks=False, verbose2=False, print_slow=False,
random_seed=None, use_mp=None, verbose3=False, forever=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. """Execute a test suite.
This also parses command-line options and modifies its behavior This also parses command-line options and modifies its behavior
@ -372,7 +449,6 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
directly to set the values that would normally be set by flags directly to set the values that would normally be set by flags
on the command line. on the command line.
""" """
# Display the Python traceback on fatal errors (e.g. segfault) # Display the Python traceback on fatal errors (e.g. segfault)
faulthandler.enable(all_threads=True) faulthandler.enable(all_threads=True)
@ -389,107 +465,18 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
support.record_original_stdout(sys.stdout) support.record_original_stdout(sys.stdout)
parser = _create_parser() ns = _parse_args(sys.argv[1:], **kwargs)
ns = parser.parse_args()
opts = _convert_namespace_to_getopt(ns)
args = ns.args
usage = parser.error
# Defaults if ns.huntrleaks:
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 # Avoid false positives due to various caches
# filling slowly with random data: # filling slowly with random data:
warm_caches() warm_caches()
elif o in ('-M', '--memlimit'): if ns.memlimit is not None:
support.set_memlimit(a) support.set_memlimit(ns.memlimit)
elif o in ('-u', '--use'): if ns.threshold is not None:
u = [x.lower() for x in a.split(',')] import gc
for r in u: gc.set_threshold(ns.threshold)
if r == 'all': if ns.nowindows:
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 import msvcrt
msvcrt.SetErrorMode(msvcrt.SEM_FAILCRITICALERRORS| msvcrt.SetErrorMode(msvcrt.SEM_FAILCRITICALERRORS|
msvcrt.SEM_NOALIGNMENTFAULTEXCEPT| msvcrt.SEM_NOALIGNMENTFAULTEXCEPT|
@ -504,19 +491,11 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
for m in [msvcrt.CRT_WARN, msvcrt.CRT_ERROR, msvcrt.CRT_ASSERT]: for m in [msvcrt.CRT_WARN, msvcrt.CRT_ERROR, msvcrt.CRT_ASSERT]:
msvcrt.CrtSetReportMode(m, msvcrt.CRTDBG_MODE_FILE) msvcrt.CrtSetReportMode(m, msvcrt.CRTDBG_MODE_FILE)
msvcrt.CrtSetReportFile(m, msvcrt.CRTDBG_FILE_STDERR) msvcrt.CrtSetReportFile(m, msvcrt.CRTDBG_FILE_STDERR)
elif o in ('-F', '--forever'): if ns.wait:
forever = True input("Press any key to continue...")
elif o in ('-j', '--multiprocess'):
use_mp = int(a) if ns.slaveargs is not None:
if use_mp <= 0: args, kwargs = json.loads(ns.slaveargs)
# 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: try:
result = runtest(*args, **kwargs) result = runtest(*args, **kwargs)
except KeyboardInterrupt: except KeyboardInterrupt:
@ -528,35 +507,6 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
print() # Force a newline (just in case) print() # Force a newline (just in case)
print(json.dumps(result)) print(json.dumps(result))
sys.exit(0) 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...")
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")
good = [] good = []
bad = [] bad = []
@ -565,12 +515,12 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
environment_changed = [] environment_changed = []
interrupted = False interrupted = False
if findleaks: if ns.findleaks:
try: try:
import gc import gc
except ImportError: except ImportError:
print('No GC available, disabling findleaks.') print('No GC available, disabling findleaks.')
findleaks = False ns.findleaks = False
else: else:
# Uncomment the line below to report garbage that is not # Uncomment the line below to report garbage that is not
# freeable by reference counting alone. By default only # freeable by reference counting alone. By default only
@ -578,42 +528,40 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
#gc.set_debug(gc.DEBUG_SAVEALL) #gc.set_debug(gc.DEBUG_SAVEALL)
found_garbage = [] found_garbage = []
if single: if ns.single:
filename = os.path.join(TEMPDIR, 'pynexttest') filename = os.path.join(TEMPDIR, 'pynexttest')
try: try:
fp = open(filename, 'r') with open(filename, 'r') as fp:
next_test = fp.read().strip() next_test = fp.read().strip()
tests = [next_test] tests = [next_test]
fp.close()
except OSError: except OSError:
pass pass
if fromfile: if ns.fromfile:
tests = [] tests = []
fp = open(os.path.join(support.SAVEDCWD, fromfile)) with open(os.path.join(support.SAVEDCWD, ns.fromfile)) as fp:
count_pat = re.compile(r'\[\s*\d+/\s*\d+\]') count_pat = re.compile(r'\[\s*\d+/\s*\d+\]')
for line in fp: for line in fp:
line = count_pat.sub('', line) line = count_pat.sub('', line)
guts = line.split() # assuming no test has whitespace in its name guts = line.split() # assuming no test has whitespace in its name
if guts and not guts[0].startswith('#'): if guts and not guts[0].startswith('#'):
tests.extend(guts) tests.extend(guts)
fp.close()
# Strip .py extensions. # Strip .py extensions.
removepy(args) removepy(ns.args)
removepy(tests) removepy(tests)
stdtests = STDTESTS[:] stdtests = STDTESTS[:]
nottests = NOTTESTS.copy() nottests = NOTTESTS.copy()
if exclude: if ns.exclude:
for arg in args: for arg in ns.args:
if arg in stdtests: if arg in stdtests:
stdtests.remove(arg) stdtests.remove(arg)
nottests.add(arg) nottests.add(arg)
args = [] ns.args = []
# For a partial run, we do not need to clutter the output. # 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 basic platform information
print("==", platform.python_implementation(), *sys.version.split()) print("==", platform.python_implementation(), *sys.version.split())
print("== ", platform.platform(aliased=True), print("== ", platform.platform(aliased=True),
@ -623,37 +571,39 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
# if testdir is set, then we are not running the python tests suite, so # 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) # don't add default tests to be executed or skipped (pass empty values)
if testdir: if ns.testdir:
alltests = findtests(testdir, list(), set()) alltests = findtests(ns.testdir, list(), set())
else: else:
alltests = findtests(testdir, stdtests, nottests) alltests = findtests(ns.testdir, stdtests, nottests)
selected = tests or args or alltests selected = tests or ns.args or alltests
if single: if ns.single:
selected = selected[:1] selected = selected[:1]
try: try:
next_single_test = alltests[alltests.index(selected[0])+1] next_single_test = alltests[alltests.index(selected[0])+1]
except IndexError: except IndexError:
next_single_test = None next_single_test = None
# Remove all the selected tests that precede start if it's set. # Remove all the selected tests that precede start if it's set.
if start: if ns.start:
try: try:
del selected[:selected.index(start)] del selected[:selected.index(ns.start)]
except ValueError: except ValueError:
print("Couldn't find starting test (%s), using all tests" % start) print("Couldn't find starting test (%s), using all tests" % ns.start)
if randomize: if ns.randomize:
random.seed(random_seed) if ns.random_seed is None:
print("Using random seed", random_seed) ns.random_seed = random.randrange(10000000)
random.seed(ns.random_seed)
print("Using random seed", ns.random_seed)
random.shuffle(selected) random.shuffle(selected)
if trace: if ns.trace:
import trace, tempfile import trace, tempfile
tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix, tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,
tempfile.gettempdir()], tempfile.gettempdir()],
trace=False, count=True) trace=False, count=True)
test_times = [] test_times = []
support.verbose = verbose # Tell tests to be moderately quiet support.verbose = ns.verbose # Tell tests to be moderately quiet
support.use_resources = use_resources support.use_resources = ns.use_resources
save_modules = sys.modules.keys() save_modules = sys.modules.keys()
def accumulate_result(test, result): def accumulate_result(test, result):
@ -671,7 +621,7 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
skipped.append(test) skipped.append(test)
resource_denieds.append(test) resource_denieds.append(test)
if forever: if ns.forever:
def test_forever(tests=list(selected)): def test_forever(tests=list(selected)):
while True: while True:
for test in tests: for test in tests:
@ -686,7 +636,7 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
test_count = '/{}'.format(len(selected)) test_count = '/{}'.format(len(selected))
test_count_width = len(test_count) - 1 test_count_width = len(test_count) - 1
if use_mp: if ns.use_mp:
try: try:
from threading import Thread from threading import Thread
except ImportError: except ImportError:
@ -710,11 +660,12 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
output.put((None, None, None, None)) output.put((None, None, None, None))
return return
args_tuple = ( args_tuple = (
(test, verbose, quiet), (test, ns.verbose, ns.quiet),
dict(huntrleaks=huntrleaks, use_resources=use_resources, dict(huntrleaks=ns.huntrleaks,
debug=debug, output_on_failure=verbose3, use_resources=ns.use_resources,
timeout=timeout, failfast=failfast, debug=ns.debug, output_on_failure=ns.verbose3,
match_tests=match_tests) timeout=ns.timeout, failfast=ns.failfast,
match_tests=ns.match_tests)
) )
# -E is needed by some tests, e.g. test_import # -E is needed by some tests, e.g. test_import
# Running the child from the same working directory ensures # Running the child from the same working directory ensures
@ -743,19 +694,19 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
except BaseException: except BaseException:
output.put((None, None, None, None)) output.put((None, None, None, None))
raise 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: for worker in workers:
worker.start() worker.start()
finished = 0 finished = 0
test_index = 1 test_index = 1
try: try:
while finished < use_mp: while finished < ns.use_mp:
test, stdout, stderr, result = output.get() test, stdout, stderr, result = output.get()
if test is None: if test is None:
finished += 1 finished += 1
continue continue
accumulate_result(test, result) accumulate_result(test, result)
if not quiet: if not ns.quiet:
fmt = "[{1:{0}}{2}/{3}] {4}" if bad else "[{1:{0}}{2}] {4}" fmt = "[{1:{0}}{2}/{3}] {4}" if bad else "[{1:{0}}{2}] {4}"
print(fmt.format( print(fmt.format(
test_count_width, test_index, test_count, test_count_width, test_index, test_count,
@ -778,29 +729,30 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
worker.join() worker.join()
else: else:
for test_index, test in enumerate(tests, 1): 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}" fmt = "[{1:{0}}{2}/{3}] {4}" if bad else "[{1:{0}}{2}] {4}"
print(fmt.format( print(fmt.format(
test_count_width, test_index, test_count, len(bad), test)) test_count_width, test_index, test_count, len(bad), test))
sys.stdout.flush() sys.stdout.flush()
if trace: if ns.trace:
# If we're tracing code coverage, then we don't exit with status # If we're tracing code coverage, then we don't exit with status
# if on a false return value from main. # 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()) globals=globals(), locals=vars())
else: else:
try: try:
result = runtest(test, verbose, quiet, huntrleaks, debug, result = runtest(test, ns.verbose, ns.quiet,
output_on_failure=verbose3, ns.huntrleaks, ns.debug,
timeout=timeout, failfast=failfast, output_on_failure=ns.verbose3,
match_tests=match_tests) timeout=ns.timeout, failfast=ns.failfast,
match_tests=ns.match_tests)
accumulate_result(test, result) accumulate_result(test, result)
except KeyboardInterrupt: except KeyboardInterrupt:
interrupted = True interrupted = True
break break
except: except:
raise raise
if findleaks: if ns.findleaks:
gc.collect() gc.collect()
if gc.garbage: if gc.garbage:
print("Warning: test created", len(gc.garbage), end=' ') print("Warning: test created", len(gc.garbage), end=' ')
@ -821,11 +773,11 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
omitted = set(selected) - set(good) - set(bad) - set(skipped) omitted = set(selected) - set(good) - set(bad) - set(skipped)
print(count(len(omitted), "test"), "omitted:") print(count(len(omitted), "test"), "omitted:")
printlist(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: if not bad and not skipped and not interrupted and len(good) > 1:
print("All", end=' ') print("All", end=' ')
print(count(len(good), "test"), "OK.") print(count(len(good), "test"), "OK.")
if print_slow: if ns.print_slow:
test_times.sort(reverse=True) test_times.sort(reverse=True)
print("10 slowest tests:") print("10 slowest tests:")
for time, test in test_times[:10]: for time, test in test_times[:10]:
@ -839,18 +791,19 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
print("{} altered the execution environment:".format( print("{} altered the execution environment:".format(
count(len(environment_changed), "test"))) count(len(environment_changed), "test")))
printlist(environment_changed) printlist(environment_changed)
if skipped and not quiet: if skipped and not ns.quiet:
print(count(len(skipped), "test"), "skipped:") print(count(len(skipped), "test"), "skipped:")
printlist(skipped) printlist(skipped)
if verbose2 and bad: if ns.verbose2 and bad:
print("Re-running failed tests in verbose mode") print("Re-running failed tests in verbose mode")
for test in bad: for test in bad:
print("Re-running test %r in verbose mode" % test) print("Re-running test %r in verbose mode" % test)
sys.stdout.flush() sys.stdout.flush()
try: try:
verbose = True ns.verbose = True
ok = runtest(test, True, quiet, huntrleaks, debug, timeout=timeout) ok = runtest(test, True, ns.quiet, ns.huntrleaks, ns.debug,
timeout=ns.timeout)
except KeyboardInterrupt: except KeyboardInterrupt:
# print a newline separate from the ^C # print a newline separate from the ^C
print() print()
@ -858,18 +811,18 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
except: except:
raise raise
if single: if ns.single:
if next_single_test: if next_single_test:
with open(filename, 'w') as fp: with open(filename, 'w') as fp:
fp.write(next_single_test + '\n') fp.write(next_single_test + '\n')
else: else:
os.unlink(filename) os.unlink(filename)
if trace: if ns.trace:
r = tracer.results() 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()) os.system("leaks %d" % os.getpid())
sys.exit(len(bad) > 0 or interrupted) sys.exit(len(bad) > 0 or interrupted)

View file

@ -4,97 +4,281 @@ Tests of regrtest.py.
import argparse import argparse
import getopt import getopt
import os.path
import unittest import unittest
from test import regrtest, support 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): 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): def checkError(self, args, msg):
# This is the same logic as that used in regrtest.main() with support.captured_stderr() as err, self.assertRaises(SystemExit):
parser = regrtest._create_parser() regrtest._parse_args(args)
ns = parser.parse_args(args=args) self.assertIn(msg, err.getvalue())
opts = regrtest._convert_namespace_to_getopt(ns)
return opts, ns.args
def _check_args(self, args, expected=None): def test_help(self):
""" for opt in '-h', '--help':
The expected parameter is for cases when the behavior of the new with self.subTest(opt=opt):
parse_args differs from the old (but deliberately so). with support.captured_stdout() as out, \
""" self.assertRaises(SystemExit):
if expected is None: regrtest._parse_args([opt])
try: self.assertIn('Run Python regression tests.', out.getvalue())
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): def test_timeout(self):
self._check_args(['--xxx']) 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_value_not_provided(self): def test_wait(self):
self._check_args(['--start']) ns = regrtest._parse_args(['--wait'])
self.assertTrue(ns.wait)
def test_short_option(self): def test_slaveargs(self):
# getopt returns the short option whereas argparse returns the long. ns = regrtest._parse_args(['--slaveargs', '[[], {}]'])
expected = ([('--quiet', '')], []) self.assertEqual(ns.slaveargs, '[[], {}]')
self._check_args(['-q'], expected=expected) self.checkError(['--slaveargs'], 'expected one argument')
def test_long_option(self): def test_start(self):
self._check_args(['--quiet']) 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_long_option__partial(self): def test_verbose(self):
self._check_args(['--qui']) 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_two_options(self): def test_verbose2(self):
self._check_args(['--quiet', '--exclude']) for opt in '-w', '--verbose2':
with self.subTest(opt=opt):
ns = regrtest._parse_args([opt])
self.assertTrue(ns.verbose2)
def test_option_with_value(self): def test_verbose3(self):
self._check_args(['--start', 'foo']) for opt in '-W', '--verbose3':
with self.subTest(opt=opt):
ns = regrtest._parse_args([opt])
self.assertTrue(ns.verbose3)
def test_option_with_empty_string_value(self): def test_debug(self):
self._check_args(['--start', '']) for opt in '-d', '--debug':
with self.subTest(opt=opt):
ns = regrtest._parse_args([opt])
self.assertTrue(ns.debug)
def test_arg(self): def test_quiet(self):
self._check_args(['foo']) 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_option_and_arg(self): def test_slow(self):
self._check_args(['--quiet', 'foo']) for opt in '-o', '--slow':
with self.subTest(opt=opt):
ns = regrtest._parse_args([opt])
self.assertTrue(ns.print_slow)
def test_fromfile(self): def test_header(self):
self._check_args(['--fromfile', 'file']) ns = regrtest._parse_args(['--header'])
self.assertTrue(ns.header)
def test_match(self):
self._check_args(['--match', 'pattern'])
def test_randomize(self): def test_randomize(self):
self._check_args(['--randomize']) 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_main(): def test_unrecognized_argument(self):
support.run_unittest(__name__) self.checkError(['--xxx'], 'usage:')
def test_long_option__partial(self):
ns = regrtest._parse_args(['--qui'])
self.assertTrue(ns.quiet)
self.assertEqual(ns.verbose, 0)
def test_two_options(self):
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):
ns = regrtest._parse_args(['--start', ''])
self.assertEqual(ns.start, '')
def test_arg(self):
ns = regrtest._parse_args(['foo'])
self.assertEqual(ns.args, ['foo'])
def test_option_and_arg(self):
ns = regrtest._parse_args(['--quiet', 'foo'])
self.assertTrue(ns.quiet)
self.assertEqual(ns.verbose, 0)
self.assertEqual(ns.args, ['foo'])
if __name__ == '__main__': if __name__ == '__main__':
test_main() unittest.main()

View file

@ -153,6 +153,9 @@ Library
Tests 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 - 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 possible, since "localhost" goes through a DNS lookup under recent Windows
versions. versions.