diff options
Diffstat (limited to 'Lib/test/regrtest.py')
| -rwxr-xr-x | Lib/test/regrtest.py | 117 | 
1 files changed, 87 insertions, 30 deletions
diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py index 08c9916..968b59d 100755 --- a/Lib/test/regrtest.py +++ b/Lib/test/regrtest.py @@ -20,6 +20,11 @@ python -E -Wd -m test [options] [test_name1 ...]  Options:  -h/--help       -- print this text and exit +--timeout TIMEOUT +                -- dump the traceback and exit if a test takes more +                   than TIMEOUT seconds; disabled if TIMEOUT is negative +                   or equals to zero +--wait          -- wait for user input, e.g., allow a debugger to be attached  Verbosity @@ -42,6 +47,9 @@ Selecting tests                  -- specify which special resource intensive tests to run  -M/--memlimit LIMIT                  -- run very large memory-consuming tests +   --testdir DIR +                -- execute test files in the specified directory (instead +                   of the Python stdlib test suite)  Special runs @@ -154,6 +162,7 @@ option '-uall,-gui'.  """  import builtins +import faulthandler  import getopt  import json  import os @@ -213,6 +222,7 @@ ENV_CHANGED = -1  SKIPPED = -2  RESOURCE_DENIED = -3  INTERRUPTED = -4 +CHILD_ERROR = -5   # error in a child process  from test import support @@ -256,6 +266,9 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,      on the command line.      """ +    # Display the Python traceback fatal errors (e.g. segfault) +    faulthandler.enable(all_threads=True) +      replace_stdout()      support.record_original_stdout(sys.stdout) @@ -266,7 +279,7 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,               'use=', 'threshold=', 'trace', 'coverdir=', 'nocoverdir',               'runleaks', 'huntrleaks=', 'memlimit=', 'randseed=',               'multiprocess=', 'coverage', 'slaveargs=', 'forever', 'debug', -             'start=', 'nowindows', 'header']) +             'start=', 'nowindows', 'header', 'testdir=', 'timeout=', 'wait'])      except getopt.error as msg:          usage(msg) @@ -277,6 +290,7 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,          use_resources = []      debug = False      start = None +    timeout = None      for o, a in opts:          if o in ('-h', '--help'):              print(__doc__) @@ -316,7 +330,9 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,          elif o in ('-T', '--coverage'):              trace = True          elif o in ('-D', '--coverdir'): -            coverdir = os.path.join(os.getcwd(), a) +            # 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'): @@ -375,6 +391,13 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,              forever = True          elif o in ('-j', '--multiprocess'):              use_mp = int(a) +            if use_mp <= 0: +                try: +                    import multiprocessing +                    # Use all cores + extras for tests that like to sleep +                    use_mp = 2 + multiprocessing.cpu_count() +                except (ImportError, NotImplementedError): +                    use_mp = 3          elif o == '--header':              header = True          elif o == '--slaveargs': @@ -387,6 +410,21 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,              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_tracebacks_later'): +                timeout = float(a) +                if timeout <= 0: +                    timeout = None +            else: +                print("Warning: The timeout option requires " +                      "faulthandler.dump_tracebacks_later") +                timeout = None +        elif o == '--wait': +            input("Press any key to continue...")          else:              print(("No handler for option {}.  Please report this as a bug "                     "at http://bugs.python.org.").format(o), file=sys.stderr) @@ -461,7 +499,13 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,          print("==  ", os.getcwd())          print("Testing with flags:", sys.flags) -    alltests = findtests(testdir, stdtests, nottests) +    # 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()) +    else: +        alltests = findtests(testdir, stdtests, nottests) +      selected = tests or args or alltests      if single:          selected = selected[:1] @@ -536,7 +580,7 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,                  args_tuple = (                      (test, verbose, quiet),                      dict(huntrleaks=huntrleaks, use_resources=use_resources, -                         debug=debug, rerun_failed=verbose3) +                         debug=debug, rerun_failed=verbose3, timeout=timeout)                  )                  yield (test, args_tuple)          pending = tests_and_args() @@ -557,10 +601,15 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,                                     universal_newlines=True,                                     close_fds=(os.name != 'nt'))                      stdout, stderr = popen.communicate() +                    retcode = popen.wait()                      # Strip last refcount output line if it exists, since it                      # comes from the shutdown of the interpreter in the subcommand.                      stderr = debug_output_pat.sub("", stderr)                      stdout, _, result = stdout.strip().rpartition("\n") +                    if retcode != 0: +                        result = (CHILD_ERROR, "Exit code %s" % retcode) +                        output.put((test, stdout.rstrip(), stderr.rstrip(), result)) +                        return                      if not result:                          output.put((None, None, None, None))                          return @@ -593,6 +642,8 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,                  if result[0] == INTERRUPTED:                      assert result[1] == 'KeyboardInterrupt'                      raise KeyboardInterrupt   # What else? +                if result[0] == CHILD_ERROR: +                    raise Exception("Child error on {}: {}".format(test, result[1]))                  test_index += 1          except KeyboardInterrupt:              interrupted = True @@ -609,12 +660,12 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,              if 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)', +                tracer.runctx('runtest(test, verbose, quiet, timeout=timeout)',                                globals=globals(), locals=vars())              else:                  try:                      result = runtest(test, verbose, quiet, huntrleaks, debug, -                                     rerun_failed=verbose3) +                                     rerun_failed=verbose3, timeout=timeout)                      accumulate_result(test, result)                  except KeyboardInterrupt:                      interrupted = True @@ -685,7 +736,7 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,              sys.stdout.flush()              try:                  verbose = True -                ok = runtest(test, True, quiet, huntrleaks, debug) +                ok = runtest(test, True, quiet, huntrleaks, debug, timeout=timeout)              except KeyboardInterrupt:                  # print a newline separate from the ^C                  print() @@ -710,6 +761,8 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,      sys.exit(len(bad) > 0 or interrupted) +# small set of tests to determine if we have a basically functioning interpreter +# (i.e. if any of these fail, then anything else is likely to follow)  STDTESTS = [      'test_grammar',      'test_opcodes', @@ -722,10 +775,8 @@ STDTESTS = [      'test_doctest2',  ] -NOTTESTS = { -    'test_future1', -    'test_future2', -} +# set of tests that we don't want to be executed when using regrtest +NOTTESTS = set()  def findtests(testdir=None, stdtests=STDTESTS, nottests=NOTTESTS):      """Return a list of all applicable test modules.""" @@ -734,25 +785,22 @@ def findtests(testdir=None, stdtests=STDTESTS, nottests=NOTTESTS):      tests = []      others = set(stdtests) | nottests      for name in names: -        modname, ext = os.path.splitext(name) -        if modname[:5] == "test_" and ext == ".py" and modname not in others: -            tests.append(modname) +        mod, ext = os.path.splitext(name) +        if mod[:5] == "test_" and ext in (".py", "") and mod not in others: +            tests.append(mod)      return stdtests + sorted(tests)  def replace_stdout():      """Set stdout encoder error handler to backslashreplace (as stderr error      handler) to avoid UnicodeEncodeError when printing a traceback""" -    if os.name == "nt": -        # Replace sys.stdout breaks the stdout newlines on Windows: issue #8533 -        return -      import atexit      stdout = sys.stdout      sys.stdout = open(stdout.fileno(), 'w',          encoding=stdout.encoding,          errors="backslashreplace", -        closefd=False) +        closefd=False, +        newline='\n')      def restore_stdout():          sys.stdout.close() @@ -761,7 +809,7 @@ def replace_stdout():  def runtest(test, verbose, quiet,              huntrleaks=False, debug=False, use_resources=None, -            rerun_failed=False): +            rerun_failed=False, timeout=None):      """Run a single test.      test -- the name of the test @@ -771,6 +819,8 @@ def runtest(test, verbose, quiet,      huntrleaks -- run multiple times to test for leaks; requires a debug                    build; a triple corresponding to -R's three arguments      rerun_failed -- if true, re-run in verbose mode when failed +    timeout -- dump the traceback and exit if a test takes more than +               timeout seconds      Returns one of the test result constants:          INTERRUPTED      KeyboardInterrupt when run under -j @@ -784,6 +834,9 @@ def runtest(test, verbose, quiet,      support.verbose = verbose  # Tell tests to be moderately quiet      if use_resources is not None:          support.use_resources = use_resources +    use_timeout = (timeout is not None) +    if use_timeout: +        faulthandler.dump_tracebacks_later(timeout, exit=True)      try:          result = runtest_inner(test, verbose, quiet, huntrleaks, debug)          if result[0] == FAILED and rerun_failed: @@ -791,9 +844,11 @@ def runtest(test, verbose, quiet,              sys.stdout.flush()              sys.stderr.flush()              print("Re-running test {} in verbose mode".format(test)) -            runtest(test, True, quiet, huntrleaks, debug) +            runtest(test, True, quiet, huntrleaks, debug, timeout=timeout)          return result      finally: +        if use_timeout: +            faulthandler.cancel_dump_tracebacks_later()          cleanup_test_droppings(test, verbose)  # Unit tests are supposed to leave the execution environment unchanged @@ -838,7 +893,7 @@ class saved_test_environment:      resources = ('sys.argv', 'cwd', 'sys.stdin', 'sys.stdout', 'sys.stderr',                   'os.environ', 'sys.path', 'sys.path_hooks', '__import__',                   'warnings.filters', 'asyncore.socket_map', -                 'logging._handlers', 'logging._handlerList', +                 'logging._handlers', 'logging._handlerList', 'sys.gettrace',                   'sys.warnoptions')      def get_sys_argv(self): @@ -886,6 +941,11 @@ class saved_test_environment:          sys.path_hooks = saved_hooks[1]          sys.path_hooks[:] = saved_hooks[2] +    def get_sys_gettrace(self): +        return sys.gettrace() +    def restore_sys_gettrace(self, trace_fxn): +        sys.settrace(trace_fxn) +      def get___import__(self):          return builtins.__import__      def restore___import__(self, import_): @@ -962,10 +1022,6 @@ class saved_test_environment:  def runtest_inner(test, verbose, quiet, huntrleaks=False, debug=False):      support.unload(test) -    if verbose: -        capture_stdout = None -    else: -        capture_stdout = io.StringIO()      test_time = 0.0      refleak = False  # True if the test leaked references. @@ -1066,7 +1122,8 @@ def dash_R(the_module, test, indirect_test, huntrleaks):          False if the test didn't leak references; True if we detected refleaks.      """      # This code is hackish and inelegant, but it seems to do the job. -    import copyreg, _abcoll +    import copyreg +    import collections.abc      if not hasattr(sys, 'gettotalrefcount'):          raise Exception("Tracking reference leaks requires a debug build " @@ -1083,7 +1140,7 @@ def dash_R(the_module, test, indirect_test, huntrleaks):      else:          zdc = zipimport._zip_directory_cache.copy()      abcs = {} -    for abc in [getattr(_abcoll, a) for a in _abcoll.__all__]: +    for abc in [getattr(collections.abc, a) for a in collections.abc.__all__]:          if not isabstract(abc):              continue          for obj in abc.__subclasses__() + [abc]: @@ -1129,7 +1186,7 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs):      import gc, copyreg      import _strptime, linecache      import urllib.parse, urllib.request, mimetypes, doctest -    import struct, filecmp, _abcoll +    import struct, filecmp, collections.abc      from distutils.dir_util import _path_created      from weakref import WeakSet @@ -1156,7 +1213,7 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs):      sys._clear_type_cache()      # Clear ABC registries, restoring previously saved ABC registries. -    for abc in [getattr(_abcoll, a) for a in _abcoll.__all__]: +    for abc in [getattr(collections.abc, a) for a in collections.abc.__all__]:          if not isabstract(abc):              continue          for obj in abc.__subclasses__() + [abc]:  | 
