diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2013-08-29 09:26:23 (GMT) |
---|---|---|
committer | Serhiy Storchaka <storchaka@gmail.com> | 2013-08-29 09:26:23 (GMT) |
commit | 64f7c4e4ca5141a8db28a2825f655e863d1c264f (patch) | |
tree | 383fa081d6f9b349cb71a1c15b14a7683d812dca | |
parent | 48e6a8c88a69adbdc7c3bf9adef5696828b1c5fe (diff) | |
download | cpython-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-x | Lib/test/regrtest.py | 533 | ||||
-rw-r--r-- | Lib/test/test_regrtest.py | 322 | ||||
-rw-r--r-- | Misc/NEWS | 3 |
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() @@ -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. |