summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2013-08-29 09:26:23 (GMT)
committerSerhiy Storchaka <storchaka@gmail.com>2013-08-29 09:26:23 (GMT)
commit64f7c4e4ca5141a8db28a2825f655e863d1c264f (patch)
tree383fa081d6f9b349cb71a1c15b14a7683d812dca
parent48e6a8c88a69adbdc7c3bf9adef5696828b1c5fe (diff)
downloadcpython-64f7c4e4ca5141a8db28a2825f655e863d1c264f.zip
cpython-64f7c4e4ca5141a8db28a2825f655e863d1c264f.tar.gz
cpython-64f7c4e4ca5141a8db28a2825f655e863d1c264f.tar.bz2
Issue #16799: Switched from getopt to argparse style in regrtest's argument
parsing. Added more tests for regrtest's argument parsing.
-rwxr-xr-xLib/test/regrtest.py533
-rw-r--r--Lib/test/test_regrtest.py322
-rw-r--r--Misc/NEWS3
3 files changed, 499 insertions, 359 deletions
diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py
index b9945d7..fab1764 100755
--- a/Lib/test/regrtest.py
+++ b/Lib/test/regrtest.py
@@ -233,18 +233,20 @@ def _create_parser():
# 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 @@ def _create_parser():
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 @@ def _create_parser():
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 @@ def _create_parser():
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.
-
- 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':
- 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:
+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)
+
+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
- 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 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
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 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
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
-
- # 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...")
+ ns = _parse_args(sys.argv[1:], **kwargs)
+
+ 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 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
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 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
#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 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
# 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 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
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 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
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 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
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 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
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 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
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 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
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 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
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 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
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
index 5b972ca..a5143c2 100644
--- a/Lib/test/test_regrtest.py
+++ b/Lib/test/test_regrtest.py
@@ -4,97 +4,281 @@ Tests of regrtest.py.
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.
+class ParseArgsTestCase(unittest.TestCase):
- 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='])
+ """Test regrtest's argument parsing."""
-class ParseArgsTestCase(unittest.TestCase):
+ def checkError(self, args, msg):
+ with support.captured_stderr() as err, self.assertRaises(SystemExit):
+ regrtest._parse_args(args)
+ self.assertIn(msg, err.getvalue())
- """Test that regrtest's parsing code matches the prior getopt behavior."""
-
- 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 _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_unrecognized_argument(self):
- self._check_args(['--xxx'])
+ 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_value_not_provided(self):
- self._check_args(['--start'])
+ def test_wait(self):
+ ns = regrtest._parse_args(['--wait'])
+ self.assertTrue(ns.wait)
- 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_slaveargs(self):
+ ns = regrtest._parse_args(['--slaveargs', '[[], {}]'])
+ self.assertEqual(ns.slaveargs, '[[], {}]')
+ self.checkError(['--slaveargs'], 'expected one argument')
- def test_long_option(self):
- self._check_args(['--quiet'])
+ 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_long_option__partial(self):
- self._check_args(['--qui'])
+ 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_two_options(self):
- self._check_args(['--quiet', '--exclude'])
+ 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_option_with_value(self):
- self._check_args(['--start', 'foo'])
+ 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_option_with_empty_string_value(self):
- self._check_args(['--start', ''])
+ 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_arg(self):
- self._check_args(['foo'])
+ 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_option_and_arg(self):
- self._check_args(['--quiet', 'foo'])
+ 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):
- self._check_args(['--fromfile', 'file'])
+ 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):
- self._check_args(['--match', 'pattern'])
+ 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_randomize(self):
- self._check_args(['--randomize'])
+ 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.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'])
-def test_main():
- support.run_unittest(__name__)
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Misc/NEWS b/Misc/NEWS
index 3870fcc..eba4288 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -153,6 +153,9 @@ Library
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.