]> granicus.if.org Git - python/commitdiff
Issue #16799: Switched from getopt to argparse style in regrtest's argument
authorSerhiy Storchaka <storchaka@gmail.com>
Thu, 29 Aug 2013 09:26:23 +0000 (12:26 +0300)
committerSerhiy Storchaka <storchaka@gmail.com>
Thu, 29 Aug 2013 09:26:23 +0000 (12:26 +0300)
parsing.  Added more tests for regrtest's argument parsing.

Lib/test/regrtest.py
Lib/test/test_regrtest.py
Misc/NEWS

index b9945d7bc2bbfb3a1930912ffb15f1f21664dfa2..fab1764836fab005e5f64f30b8f115f16c615552 100755 (executable)
@@ -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)
index 5b972caa03380cef284174dd892916b967768dd3..a5143c2d96083d7013b4311b37ae237818c73650 100644 (file)
@@ -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()
index 3870fccfac0c08bb3295e6edaf10fd6c6aea88ae..eba42883ad471c64d7bc79cc5078167f61c102f4 100644 (file)
--- 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.