[Python-Dev] [Python-checkins] cpython: Issue #16799: Switched from getopt to argparse style in regrtest's argument

Eli Bendersky eliben at gmail.com
Thu Aug 29 18:50:08 CEST 2013


Great job, Serhiy. In general, eating our own dogfood is a great idea. The
more we use new Python features in our own stuff, the better.

Eli


On Thu, Aug 29, 2013 at 2:27 AM, serhiy.storchaka <
python-checkins at python.org> wrote:

> http://hg.python.org/cpython/rev/997de0edc5bd
> changeset:   85444:997de0edc5bd
> parent:      85442:676bbd5b0254
> user:        Serhiy Storchaka <storchaka at gmail.com>
> date:        Thu Aug 29 12:26:23 2013 +0300
> summary:
>   Issue #16799: Switched from getopt to argparse style in regrtest's
> argument
> parsing.  Added more tests for regrtest's argument parsing.
>
> files:
>   Lib/test/regrtest.py      |  529 +++++++++++--------------
>   Lib/test/test_regrtest.py |  328 ++++++++++++---
>   Misc/NEWS                 |    3 +
>   3 files changed, 500 insertions(+), 360 deletions(-)
>
>
> diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py
> --- a/Lib/test/regrtest.py
> +++ b/Lib/test/regrtest.py
> @@ -233,18 +233,20 @@
>      # 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 @@
>                         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 @@
>      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 @@
>
>      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.
> +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)
>
> -    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':
> +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
> -        val = args_dict[key]
> -        # Don't continue if val equals '' because this means an option
> -        # accepting a value was provided the empty string.  Such values
> should
> -        # show up in the returned opts list.
> -        if val is None or val is False:
> -            continue
> -        if val is True:
> -            # Then an option with action store_true was passed. getopt
> -            # includes these with value '' in the opts list.
> -            val = ''
> -        opts.append(('--' + key, val))
> -    return opts
> +        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 @@
>      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 @@
>
>      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
> +    ns = _parse_args(sys.argv[1:], **kwargs)
>
> -    # 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...")
> +    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 @@
>      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 @@
>              #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 @@
>
>      # 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 @@
>              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 @@
>          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 @@
>                          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 @@
>              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 @@
>              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 @@
>          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 @@
>          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 @@
>              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
> --- a/Lib/test/test_regrtest.py
> +++ b/Lib/test/test_regrtest.py
> @@ -4,97 +4,281 @@
>
>  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.
> -
> -    Raises getopt.GetoptError on bad arguments.
> -    """
> -    return getopt.getopt(args, 'hvqxsoS:rf:lu:t:TD:NLR:FdwWM:nj:Gm:',
> -        ['help', 'verbose', 'verbose2', 'verbose3', 'quiet',
> -         'exclude', 'single', 'slow', 'randomize', 'fromfile=',
> 'findleaks',
> -         'use=', 'threshold=', 'coverdir=', 'nocoverdir',
> -         'runleaks', 'huntrleaks=', 'memlimit=', 'randseed=',
> -         'multiprocess=', 'coverage', 'slaveargs=', 'forever', 'debug',
> -         'start=', 'nowindows', 'header', 'testdir=', 'timeout=', 'wait',
> -         'failfast', 'match='])
> -
>  class ParseArgsTestCase(unittest.TestCase):
>
> -    """Test that regrtest's parsing code matches the prior getopt
> behavior."""
> +    """Test regrtest's argument parsing."""
>
> -    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 checkError(self, args, msg):
> +        with support.captured_stderr() as err,
> self.assertRaises(SystemExit):
> +            regrtest._parse_args(args)
> +        self.assertIn(msg, err.getvalue())
>
> -    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_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_wait(self):
> +        ns = regrtest._parse_args(['--wait'])
> +        self.assertTrue(ns.wait)
> +
> +    def test_slaveargs(self):
> +        ns = regrtest._parse_args(['--slaveargs', '[[], {}]'])
> +        self.assertEqual(ns.slaveargs, '[[], {}]')
> +        self.checkError(['--slaveargs'], 'expected one argument')
> +
> +    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_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_verbose2(self):
> +        for opt in '-w', '--verbose2':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt])
> +                self.assertTrue(ns.verbose2)
> +
> +    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_debug(self):
> +        for opt in '-d', '--debug':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt])
> +                self.assertTrue(ns.debug)
> +
> +    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_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):
> +        for opt in '-f', '--fromfile':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt, 'foo'])
> +                self.assertEqual(ns.fromfile, 'foo')
> +                self.checkError([opt], 'expected one argument')
> +                self.checkError([opt, 'foo', '-s'], "don't go together")
> +
> +    def test_exclude(self):
> +        for opt in '-x', '--exclude':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt])
> +                self.assertTrue(ns.exclude)
> +
> +    def test_single(self):
> +        for opt in '-s', '--single':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt])
> +                self.assertTrue(ns.single)
> +                self.checkError([opt, '-f', 'foo'], "don't go together")
> +
> +    def test_match(self):
> +        for opt in '-m', '--match':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt, 'pattern'])
> +                self.assertEqual(ns.match_tests, 'pattern')
> +                self.checkError([opt], 'expected one argument')
> +
> +    def test_failfast(self):
> +        for opt in '-G', '--failfast':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt, '-v'])
> +                self.assertTrue(ns.failfast)
> +                ns = regrtest._parse_args([opt, '-W'])
> +                self.assertTrue(ns.failfast)
> +                self.checkError([opt], '-G/--failfast needs either -v or
> -W')
> +
> +    def test_use(self):
> +        for opt in '-u', '--use':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt, 'gui,network'])
> +                self.assertEqual(ns.use_resources, ['gui', 'network'])
> +                ns = regrtest._parse_args([opt, 'gui,none,network'])
> +                self.assertEqual(ns.use_resources, ['network'])
> +                expected = list(regrtest.RESOURCE_NAMES)
> +                expected.remove('gui')
> +                ns = regrtest._parse_args([opt, 'all,-gui'])
> +                self.assertEqual(ns.use_resources, expected)
> +                self.checkError([opt], 'expected one argument')
> +                self.checkError([opt, 'foo'], 'invalid resource')
> +
> +    def test_memlimit(self):
> +        for opt in '-M', '--memlimit':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt, '4G'])
> +                self.assertEqual(ns.memlimit, '4G')
> +                self.checkError([opt], 'expected one argument')
> +
> +    def test_testdir(self):
> +        ns = regrtest._parse_args(['--testdir', 'foo'])
> +        self.assertEqual(ns.testdir, os.path.join(support.SAVEDCWD,
> 'foo'))
> +        self.checkError(['--testdir'], 'expected one argument')
> +
> +    def test_findleaks(self):
> +        for opt in '-l', '--findleaks':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt])
> +                self.assertTrue(ns.findleaks)
> +
> +    def test_findleaks(self):
> +        for opt in '-L', '--runleaks':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt])
> +                self.assertTrue(ns.runleaks)
> +
> +    def test_findleaks(self):
> +        for opt in '-R', '--huntrleaks':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt, ':'])
> +                self.assertEqual(ns.huntrleaks, (5, 4, 'reflog.txt'))
> +                ns = regrtest._parse_args([opt, '6:'])
> +                self.assertEqual(ns.huntrleaks, (6, 4, 'reflog.txt'))
> +                ns = regrtest._parse_args([opt, ':3'])
> +                self.assertEqual(ns.huntrleaks, (5, 3, 'reflog.txt'))
> +                ns = regrtest._parse_args([opt, '6:3:leaks.log'])
> +                self.assertEqual(ns.huntrleaks, (6, 3, 'leaks.log'))
> +                self.checkError([opt], 'expected one argument')
> +                self.checkError([opt, '6'],
> +                                'needs 2 or 3 colon-separated arguments')
> +                self.checkError([opt, 'foo:'], 'invalid huntrleaks value')
> +                self.checkError([opt, '6:foo'], 'invalid huntrleaks
> value')
> +
> +    def test_multiprocess(self):
> +        for opt in '-j', '--multiprocess':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt, '2'])
> +                self.assertEqual(ns.use_mp, 2)
> +                self.checkError([opt], 'expected one argument')
> +                self.checkError([opt, 'foo'], 'invalid int value')
> +                self.checkError([opt, '2', '-T'], "don't go together")
> +                self.checkError([opt, '2', '-l'], "don't go together")
> +                self.checkError([opt, '2', '-M', '4G'], "don't go
> together")
> +
> +    def test_findleaks(self):
> +        for opt in '-T', '--coverage':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt])
> +                self.assertTrue(ns.trace)
> +
> +    def test_coverdir(self):
> +        for opt in '-D', '--coverdir':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt, 'foo'])
> +                self.assertEqual(ns.coverdir,
> +                                 os.path.join(support.SAVEDCWD, 'foo'))
> +                self.checkError([opt], 'expected one argument')
> +
> +    def test_nocoverdir(self):
> +        for opt in '-N', '--nocoverdir':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt])
> +                self.assertIsNone(ns.coverdir)
> +
> +    def test_threshold(self):
> +        for opt in '-t', '--threshold':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt, '1000'])
> +                self.assertEqual(ns.threshold, 1000)
> +                self.checkError([opt], 'expected one argument')
> +                self.checkError([opt, 'foo'], 'invalid int value')
> +
> +    def test_nowindows(self):
> +        for opt in '-n', '--nowindows':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt])
> +                self.assertTrue(ns.nowindows)
> +
> +    def test_forever(self):
> +        for opt in '-F', '--forever':
> +            with self.subTest(opt=opt):
> +                ns = regrtest._parse_args([opt])
> +                self.assertTrue(ns.forever)
> +
>
>      def test_unrecognized_argument(self):
> -        self._check_args(['--xxx'])
> -
> -    def test_value_not_provided(self):
> -        self._check_args(['--start'])
> -
> -    def test_short_option(self):
> -        # getopt returns the short option whereas argparse returns the
> long.
> -        expected = ([('--quiet', '')], [])
> -        self._check_args(['-q'], expected=expected)
> -
> -    def test_long_option(self):
> -        self._check_args(['--quiet'])
> +        self.checkError(['--xxx'], 'usage:')
>
>      def test_long_option__partial(self):
> -        self._check_args(['--qui'])
> +        ns = regrtest._parse_args(['--qui'])
> +        self.assertTrue(ns.quiet)
> +        self.assertEqual(ns.verbose, 0)
>
>      def test_two_options(self):
> -        self._check_args(['--quiet', '--exclude'])
> -
> -    def test_option_with_value(self):
> -        self._check_args(['--start', 'foo'])
> +        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):
> -        self._check_args(['--start', ''])
> +        ns = regrtest._parse_args(['--start', ''])
> +        self.assertEqual(ns.start, '')
>
>      def test_arg(self):
> -        self._check_args(['foo'])
> +        ns = regrtest._parse_args(['foo'])
> +        self.assertEqual(ns.args, ['foo'])
>
>      def test_option_and_arg(self):
> -        self._check_args(['--quiet', 'foo'])
> +        ns = regrtest._parse_args(['--quiet', 'foo'])
> +        self.assertTrue(ns.quiet)
> +        self.assertEqual(ns.verbose, 0)
> +        self.assertEqual(ns.args, ['foo'])
>
> -    def test_fromfile(self):
> -        self._check_args(['--fromfile', 'file'])
> -
> -    def test_match(self):
> -        self._check_args(['--match', 'pattern'])
> -
> -    def test_randomize(self):
> -        self._check_args(['--randomize'])
> -
> -
> -def test_main():
> -    support.run_unittest(__name__)
>
>  if __name__ == '__main__':
> -    test_main()
> +    unittest.main()
> diff --git a/Misc/NEWS b/Misc/NEWS
> --- a/Misc/NEWS
> +++ b/Misc/NEWS
> @@ -153,6 +153,9 @@
>  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.
>
> --
> Repository URL: http://hg.python.org/cpython
>
> _______________________________________________
> Python-checkins mailing list
> Python-checkins at python.org
> http://mail.python.org/mailman/listinfo/python-checkins
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20130829/aa4f6185/attachment-0001.html>


More information about the Python-Dev mailing list