tests/run-tests.py
changeset 43076 2372284d9457
parent 43073 5c9c71cde1c9
child 43283 96eb9ef777a8
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
    73 except ImportError:
    73 except ImportError:
    74     import queue
    74     import queue
    75 
    75 
    76 try:
    76 try:
    77     import shlex
    77     import shlex
       
    78 
    78     shellquote = shlex.quote
    79     shellquote = shlex.quote
    79 except (ImportError, AttributeError):
    80 except (ImportError, AttributeError):
    80     import pipes
    81     import pipes
       
    82 
    81     shellquote = pipes.quote
    83     shellquote = pipes.quote
    82 
    84 
    83 processlock = threading.Lock()
    85 processlock = threading.Lock()
    84 
    86 
    85 pygmentspresent = False
    87 pygmentspresent = False
    86 # ANSI color is unsupported prior to Windows 10
    88 # ANSI color is unsupported prior to Windows 10
    87 if os.name != 'nt':
    89 if os.name != 'nt':
    88     try: # is pygments installed
    90     try:  # is pygments installed
    89         import pygments
    91         import pygments
    90         import pygments.lexers as lexers
    92         import pygments.lexers as lexers
    91         import pygments.lexer as lexer
    93         import pygments.lexer as lexer
    92         import pygments.formatters as formatters
    94         import pygments.formatters as formatters
    93         import pygments.token as token
    95         import pygments.token as token
    94         import pygments.style as style
    96         import pygments.style as style
       
    97 
    95         pygmentspresent = True
    98         pygmentspresent = True
    96         difflexer = lexers.DiffLexer()
    99         difflexer = lexers.DiffLexer()
    97         terminal256formatter = formatters.Terminal256Formatter()
   100         terminal256formatter = formatters.Terminal256Formatter()
    98     except ImportError:
   101     except ImportError:
    99         pass
   102         pass
   100 
   103 
   101 if pygmentspresent:
   104 if pygmentspresent:
       
   105 
   102     class TestRunnerStyle(style.Style):
   106     class TestRunnerStyle(style.Style):
   103         default_style = ""
   107         default_style = ""
   104         skipped = token.string_to_tokentype("Token.Generic.Skipped")
   108         skipped = token.string_to_tokentype("Token.Generic.Skipped")
   105         failed = token.string_to_tokentype("Token.Generic.Failed")
   109         failed = token.string_to_tokentype("Token.Generic.Failed")
   106         skippedname = token.string_to_tokentype("Token.Generic.SName")
   110         skippedname = token.string_to_tokentype("Token.Generic.SName")
   107         failedname = token.string_to_tokentype("Token.Generic.FName")
   111         failedname = token.string_to_tokentype("Token.Generic.FName")
   108         styles = {
   112         styles = {
   109             skipped:         '#e5e5e5',
   113             skipped: '#e5e5e5',
   110             skippedname:     '#00ffff',
   114             skippedname: '#00ffff',
   111             failed:          '#7f0000',
   115             failed: '#7f0000',
   112             failedname:      '#ff0000',
   116             failedname: '#ff0000',
   113         }
   117         }
   114 
   118 
   115     class TestRunnerLexer(lexer.RegexLexer):
   119     class TestRunnerLexer(lexer.RegexLexer):
   116         testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
   120         testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
   117         tokens = {
   121         tokens = {
   125                 (r':.*', token.Generic.Skipped),
   129                 (r':.*', token.Generic.Skipped),
   126             ],
   130             ],
   127             'failed': [
   131             'failed': [
   128                 (testpattern, token.Generic.FName),
   132                 (testpattern, token.Generic.FName),
   129                 (r'(:| ).*', token.Generic.Failed),
   133                 (r'(:| ).*', token.Generic.Failed),
   130             ]
   134             ],
   131         }
   135         }
   132 
   136 
   133     runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
   137     runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
   134     runnerlexer = TestRunnerLexer()
   138     runnerlexer = TestRunnerLexer()
   135 
   139 
   136 origenviron = os.environ.copy()
   140 origenviron = os.environ.copy()
   137 
   141 
   138 if sys.version_info > (3, 5, 0):
   142 if sys.version_info > (3, 5, 0):
   139     PYTHON3 = True
   143     PYTHON3 = True
   140     xrange = range # we use xrange in one place, and we'd rather not use range
   144     xrange = range  # we use xrange in one place, and we'd rather not use range
       
   145 
   141     def _bytespath(p):
   146     def _bytespath(p):
   142         if p is None:
   147         if p is None:
   143             return p
   148             return p
   144         return p.encode('utf-8')
   149         return p.encode('utf-8')
   145 
   150 
   156         class environbytes(object):
   161         class environbytes(object):
   157             def __init__(self, strenv):
   162             def __init__(self, strenv):
   158                 self.__len__ = strenv.__len__
   163                 self.__len__ = strenv.__len__
   159                 self.clear = strenv.clear
   164                 self.clear = strenv.clear
   160                 self._strenv = strenv
   165                 self._strenv = strenv
       
   166 
   161             def __getitem__(self, k):
   167             def __getitem__(self, k):
   162                 v = self._strenv.__getitem__(_strpath(k))
   168                 v = self._strenv.__getitem__(_strpath(k))
   163                 return _bytespath(v)
   169                 return _bytespath(v)
       
   170 
   164             def __setitem__(self, k, v):
   171             def __setitem__(self, k, v):
   165                 self._strenv.__setitem__(_strpath(k), _strpath(v))
   172                 self._strenv.__setitem__(_strpath(k), _strpath(v))
       
   173 
   166             def __delitem__(self, k):
   174             def __delitem__(self, k):
   167                 self._strenv.__delitem__(_strpath(k))
   175                 self._strenv.__delitem__(_strpath(k))
       
   176 
   168             def __contains__(self, k):
   177             def __contains__(self, k):
   169                 return self._strenv.__contains__(_strpath(k))
   178                 return self._strenv.__contains__(_strpath(k))
       
   179 
   170             def __iter__(self):
   180             def __iter__(self):
   171                 return iter([_bytespath(k) for k in iter(self._strenv)])
   181                 return iter([_bytespath(k) for k in iter(self._strenv)])
       
   182 
   172             def get(self, k, default=None):
   183             def get(self, k, default=None):
   173                 v = self._strenv.get(_strpath(k), _strpath(default))
   184                 v = self._strenv.get(_strpath(k), _strpath(default))
   174                 return _bytespath(v)
   185                 return _bytespath(v)
       
   186 
   175             def pop(self, k, default=None):
   187             def pop(self, k, default=None):
   176                 v = self._strenv.pop(_strpath(k), _strpath(default))
   188                 v = self._strenv.pop(_strpath(k), _strpath(default))
   177                 return _bytespath(v)
   189                 return _bytespath(v)
   178 
   190 
   179         osenvironb = environbytes(os.environ)
   191         osenvironb = environbytes(os.environ)
   181     getcwdb = getattr(os, 'getcwdb')
   193     getcwdb = getattr(os, 'getcwdb')
   182     if not getcwdb or os.name == 'nt':
   194     if not getcwdb or os.name == 'nt':
   183         getcwdb = lambda: _bytespath(os.getcwd())
   195         getcwdb = lambda: _bytespath(os.getcwd())
   184 
   196 
   185 elif sys.version_info >= (3, 0, 0):
   197 elif sys.version_info >= (3, 0, 0):
   186     print('%s is only supported on Python 3.5+ and 2.7, not %s' %
   198     print(
   187           (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
   199         '%s is only supported on Python 3.5+ and 2.7, not %s'
   188     sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
   200         % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
       
   201     )
       
   202     sys.exit(70)  # EX_SOFTWARE from `man 3 sysexit`
   189 else:
   203 else:
   190     PYTHON3 = False
   204     PYTHON3 = False
   191 
   205 
   192     # In python 2.x, path operations are generally done using
   206     # In python 2.x, path operations are generally done using
   193     # bytestrings by default, so we don't have to do any extra
   207     # bytestrings by default, so we don't have to do any extra
   226         else:
   240         else:
   227             raise
   241             raise
   228     else:
   242     else:
   229         return False
   243         return False
   230 
   244 
       
   245 
   231 # useipv6 will be set by parseargs
   246 # useipv6 will be set by parseargs
   232 useipv6 = None
   247 useipv6 = None
       
   248 
   233 
   249 
   234 def checkportisavailable(port):
   250 def checkportisavailable(port):
   235     """return true if a port seems free to bind on localhost"""
   251     """return true if a port seems free to bind on localhost"""
   236     if useipv6:
   252     if useipv6:
   237         family = socket.AF_INET6
   253         family = socket.AF_INET6
   241         s = socket.socket(family, socket.SOCK_STREAM)
   257         s = socket.socket(family, socket.SOCK_STREAM)
   242         s.bind(('localhost', port))
   258         s.bind(('localhost', port))
   243         s.close()
   259         s.close()
   244         return True
   260         return True
   245     except socket.error as exc:
   261     except socket.error as exc:
   246         if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
   262         if exc.errno not in (
   247                              errno.EPROTONOSUPPORT):
   263             errno.EADDRINUSE,
       
   264             errno.EADDRNOTAVAIL,
       
   265             errno.EPROTONOSUPPORT,
       
   266         ):
   248             raise
   267             raise
   249     return False
   268     return False
   250 
   269 
       
   270 
   251 closefds = os.name == 'posix'
   271 closefds = os.name == 'posix'
       
   272 
       
   273 
   252 def Popen4(cmd, wd, timeout, env=None):
   274 def Popen4(cmd, wd, timeout, env=None):
   253     processlock.acquire()
   275     processlock.acquire()
   254     p = subprocess.Popen(_strpath(cmd), shell=True, bufsize=-1,
   276     p = subprocess.Popen(
   255                          cwd=_strpath(wd), env=env,
   277         _strpath(cmd),
   256                          close_fds=closefds,
   278         shell=True,
   257                          stdin=subprocess.PIPE, stdout=subprocess.PIPE,
   279         bufsize=-1,
   258                          stderr=subprocess.STDOUT)
   280         cwd=_strpath(wd),
       
   281         env=env,
       
   282         close_fds=closefds,
       
   283         stdin=subprocess.PIPE,
       
   284         stdout=subprocess.PIPE,
       
   285         stderr=subprocess.STDOUT,
       
   286     )
   259     processlock.release()
   287     processlock.release()
   260 
   288 
   261     p.fromchild = p.stdout
   289     p.fromchild = p.stdout
   262     p.tochild = p.stdin
   290     p.tochild = p.stdin
   263     p.childerr = p.stderr
   291     p.childerr = p.stderr
   264 
   292 
   265     p.timeout = False
   293     p.timeout = False
   266     if timeout:
   294     if timeout:
       
   295 
   267         def t():
   296         def t():
   268             start = time.time()
   297             start = time.time()
   269             while time.time() - start < timeout and p.returncode is None:
   298             while time.time() - start < timeout and p.returncode is None:
   270                 time.sleep(.1)
   299                 time.sleep(0.1)
   271             p.timeout = True
   300             p.timeout = True
   272             if p.returncode is None:
   301             if p.returncode is None:
   273                 terminate(p)
   302                 terminate(p)
       
   303 
   274         threading.Thread(target=t).start()
   304         threading.Thread(target=t).start()
   275 
   305 
   276     return p
   306     return p
       
   307 
   277 
   308 
   278 if sys.executable:
   309 if sys.executable:
   279     sysexecutable = sys.executable
   310     sysexecutable = sys.executable
   280 elif os.environ.get('PYTHONEXECUTABLE'):
   311 elif os.environ.get('PYTHONEXECUTABLE'):
   281     sysexecutable = os.environ['PYTHONEXECUTABLE']
   312     sysexecutable = os.environ['PYTHONEXECUTABLE']
   295     'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
   326     'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
   296     'port': ('HGTEST_PORT', 20059),
   327     'port': ('HGTEST_PORT', 20059),
   297     'shell': ('HGTEST_SHELL', 'sh'),
   328     'shell': ('HGTEST_SHELL', 'sh'),
   298 }
   329 }
   299 
   330 
       
   331 
   300 def canonpath(path):
   332 def canonpath(path):
   301     return os.path.realpath(os.path.expanduser(path))
   333     return os.path.realpath(os.path.expanduser(path))
       
   334 
   302 
   335 
   303 def parselistfiles(files, listtype, warn=True):
   336 def parselistfiles(files, listtype, warn=True):
   304     entries = dict()
   337     entries = dict()
   305     for filename in files:
   338     for filename in files:
   306         try:
   339         try:
   319                 entries[line] = filename
   352                 entries[line] = filename
   320 
   353 
   321         f.close()
   354         f.close()
   322     return entries
   355     return entries
   323 
   356 
       
   357 
   324 def parsettestcases(path):
   358 def parsettestcases(path):
   325     """read a .t test file, return a set of test case names
   359     """read a .t test file, return a set of test case names
   326 
   360 
   327     If path does not exist, return an empty set.
   361     If path does not exist, return an empty set.
   328     """
   362     """
   335     except IOError as ex:
   369     except IOError as ex:
   336         if ex.errno != errno.ENOENT:
   370         if ex.errno != errno.ENOENT:
   337             raise
   371             raise
   338     return cases
   372     return cases
   339 
   373 
       
   374 
   340 def getparser():
   375 def getparser():
   341     """Obtain the OptionParser used by the CLI."""
   376     """Obtain the OptionParser used by the CLI."""
   342     parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
   377     parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
   343 
   378 
   344     selection = parser.add_argument_group('Test Selection')
   379     selection = parser.add_argument_group('Test Selection')
   345     selection.add_argument('--allow-slow-tests', action='store_true',
   380     selection.add_argument(
   346         help='allow extremely slow tests')
   381         '--allow-slow-tests',
   347     selection.add_argument("--blacklist", action="append",
   382         action='store_true',
   348         help="skip tests listed in the specified blacklist file")
   383         help='allow extremely slow tests',
   349     selection.add_argument("--changed",
   384     )
   350         help="run tests that are changed in parent rev or working directory")
   385     selection.add_argument(
   351     selection.add_argument("-k", "--keywords",
   386         "--blacklist",
   352         help="run tests matching keywords")
   387         action="append",
   353     selection.add_argument("-r", "--retest", action="store_true",
   388         help="skip tests listed in the specified blacklist file",
   354         help = "retest failed tests")
   389     )
   355     selection.add_argument("--test-list", action="append",
   390     selection.add_argument(
   356         help="read tests to run from the specified file")
   391         "--changed",
   357     selection.add_argument("--whitelist", action="append",
   392         help="run tests that are changed in parent rev or working directory",
   358         help="always run tests listed in the specified whitelist file")
   393     )
   359     selection.add_argument('tests', metavar='TESTS', nargs='*',
   394     selection.add_argument(
   360                         help='Tests to run')
   395         "-k", "--keywords", help="run tests matching keywords"
       
   396     )
       
   397     selection.add_argument(
       
   398         "-r", "--retest", action="store_true", help="retest failed tests"
       
   399     )
       
   400     selection.add_argument(
       
   401         "--test-list",
       
   402         action="append",
       
   403         help="read tests to run from the specified file",
       
   404     )
       
   405     selection.add_argument(
       
   406         "--whitelist",
       
   407         action="append",
       
   408         help="always run tests listed in the specified whitelist file",
       
   409     )
       
   410     selection.add_argument(
       
   411         'tests', metavar='TESTS', nargs='*', help='Tests to run'
       
   412     )
   361 
   413 
   362     harness = parser.add_argument_group('Test Harness Behavior')
   414     harness = parser.add_argument_group('Test Harness Behavior')
   363     harness.add_argument('--bisect-repo',
   415     harness.add_argument(
   364                         metavar='bisect_repo',
   416         '--bisect-repo',
   365                         help=("Path of a repo to bisect. Use together with "
   417         metavar='bisect_repo',
   366                               "--known-good-rev"))
   418         help=(
   367     harness.add_argument("-d", "--debug", action="store_true",
   419             "Path of a repo to bisect. Use together with " "--known-good-rev"
       
   420         ),
       
   421     )
       
   422     harness.add_argument(
       
   423         "-d",
       
   424         "--debug",
       
   425         action="store_true",
   368         help="debug mode: write output of test scripts to console"
   426         help="debug mode: write output of test scripts to console"
   369              " rather than capturing and diffing it (disables timeout)")
   427         " rather than capturing and diffing it (disables timeout)",
   370     harness.add_argument("-f", "--first", action="store_true",
   428     )
   371         help="exit on the first test failure")
   429     harness.add_argument(
   372     harness.add_argument("-i", "--interactive", action="store_true",
   430         "-f",
   373         help="prompt to accept changed output")
   431         "--first",
   374     harness.add_argument("-j", "--jobs", type=int,
   432         action="store_true",
       
   433         help="exit on the first test failure",
       
   434     )
       
   435     harness.add_argument(
       
   436         "-i",
       
   437         "--interactive",
       
   438         action="store_true",
       
   439         help="prompt to accept changed output",
       
   440     )
       
   441     harness.add_argument(
       
   442         "-j",
       
   443         "--jobs",
       
   444         type=int,
   375         help="number of jobs to run in parallel"
   445         help="number of jobs to run in parallel"
   376              " (default: $%s or %d)" % defaults['jobs'])
   446         " (default: $%s or %d)" % defaults['jobs'],
   377     harness.add_argument("--keep-tmpdir", action="store_true",
   447     )
   378         help="keep temporary directory after running tests")
   448     harness.add_argument(
   379     harness.add_argument('--known-good-rev',
   449         "--keep-tmpdir",
   380                         metavar="known_good_rev",
   450         action="store_true",
   381                         help=("Automatically bisect any failures using this "
   451         help="keep temporary directory after running tests",
   382                               "revision as a known-good revision."))
   452     )
   383     harness.add_argument("--list-tests", action="store_true",
   453     harness.add_argument(
   384         help="list tests instead of running them")
   454         '--known-good-rev',
   385     harness.add_argument("--loop", action="store_true",
   455         metavar="known_good_rev",
   386         help="loop tests repeatedly")
   456         help=(
   387     harness.add_argument('--random', action="store_true",
   457             "Automatically bisect any failures using this "
   388         help='run tests in random order')
   458             "revision as a known-good revision."
   389     harness.add_argument('--order-by-runtime', action="store_true",
   459         ),
   390         help='run slowest tests first, according to .testtimes')
   460     )
   391     harness.add_argument("-p", "--port", type=int,
   461     harness.add_argument(
       
   462         "--list-tests",
       
   463         action="store_true",
       
   464         help="list tests instead of running them",
       
   465     )
       
   466     harness.add_argument(
       
   467         "--loop", action="store_true", help="loop tests repeatedly"
       
   468     )
       
   469     harness.add_argument(
       
   470         '--random', action="store_true", help='run tests in random order'
       
   471     )
       
   472     harness.add_argument(
       
   473         '--order-by-runtime',
       
   474         action="store_true",
       
   475         help='run slowest tests first, according to .testtimes',
       
   476     )
       
   477     harness.add_argument(
       
   478         "-p",
       
   479         "--port",
       
   480         type=int,
   392         help="port on which servers should listen"
   481         help="port on which servers should listen"
   393              " (default: $%s or %d)" % defaults['port'])
   482         " (default: $%s or %d)" % defaults['port'],
   394     harness.add_argument('--profile-runner', action='store_true',
   483     )
   395                         help='run statprof on run-tests')
   484     harness.add_argument(
   396     harness.add_argument("-R", "--restart", action="store_true",
   485         '--profile-runner',
   397         help="restart at last error")
   486         action='store_true',
   398     harness.add_argument("--runs-per-test", type=int, dest="runs_per_test",
   487         help='run statprof on run-tests',
   399         help="run each test N times (default=1)", default=1)
   488     )
   400     harness.add_argument("--shell",
   489     harness.add_argument(
   401         help="shell to use (default: $%s or %s)" % defaults['shell'])
   490         "-R", "--restart", action="store_true", help="restart at last error"
   402     harness.add_argument('--showchannels', action='store_true',
   491     )
   403                         help='show scheduling channels')
   492     harness.add_argument(
   404     harness.add_argument("--slowtimeout", type=int,
   493         "--runs-per-test",
       
   494         type=int,
       
   495         dest="runs_per_test",
       
   496         help="run each test N times (default=1)",
       
   497         default=1,
       
   498     )
       
   499     harness.add_argument(
       
   500         "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
       
   501     )
       
   502     harness.add_argument(
       
   503         '--showchannels', action='store_true', help='show scheduling channels'
       
   504     )
       
   505     harness.add_argument(
       
   506         "--slowtimeout",
       
   507         type=int,
   405         help="kill errant slow tests after SLOWTIMEOUT seconds"
   508         help="kill errant slow tests after SLOWTIMEOUT seconds"
   406              " (default: $%s or %d)" % defaults['slowtimeout'])
   509         " (default: $%s or %d)" % defaults['slowtimeout'],
   407     harness.add_argument("-t", "--timeout", type=int,
   510     )
       
   511     harness.add_argument(
       
   512         "-t",
       
   513         "--timeout",
       
   514         type=int,
   408         help="kill errant tests after TIMEOUT seconds"
   515         help="kill errant tests after TIMEOUT seconds"
   409              " (default: $%s or %d)" % defaults['timeout'])
   516         " (default: $%s or %d)" % defaults['timeout'],
   410     harness.add_argument("--tmpdir",
   517     )
       
   518     harness.add_argument(
       
   519         "--tmpdir",
   411         help="run tests in the given temporary directory"
   520         help="run tests in the given temporary directory"
   412              " (implies --keep-tmpdir)")
   521         " (implies --keep-tmpdir)",
   413     harness.add_argument("-v", "--verbose", action="store_true",
   522     )
   414         help="output verbose messages")
   523     harness.add_argument(
       
   524         "-v", "--verbose", action="store_true", help="output verbose messages"
       
   525     )
   415 
   526 
   416     hgconf = parser.add_argument_group('Mercurial Configuration')
   527     hgconf = parser.add_argument_group('Mercurial Configuration')
   417     hgconf.add_argument("--chg", action="store_true",
   528     hgconf.add_argument(
   418         help="install and use chg wrapper in place of hg")
   529         "--chg",
   419     hgconf.add_argument("--compiler",
   530         action="store_true",
   420         help="compiler to build with")
   531         help="install and use chg wrapper in place of hg",
   421     hgconf.add_argument('--extra-config-opt', action="append", default=[],
   532     )
   422         help='set the given config opt in the test hgrc')
   533     hgconf.add_argument("--compiler", help="compiler to build with")
   423     hgconf.add_argument("-l", "--local", action="store_true",
   534     hgconf.add_argument(
       
   535         '--extra-config-opt',
       
   536         action="append",
       
   537         default=[],
       
   538         help='set the given config opt in the test hgrc',
       
   539     )
       
   540     hgconf.add_argument(
       
   541         "-l",
       
   542         "--local",
       
   543         action="store_true",
   424         help="shortcut for --with-hg=<testdir>/../hg, "
   544         help="shortcut for --with-hg=<testdir>/../hg, "
   425              "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
   545         "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
   426     hgconf.add_argument("--ipv6", action="store_true",
   546     )
   427         help="prefer IPv6 to IPv4 for network related tests")
   547     hgconf.add_argument(
   428     hgconf.add_argument("--pure", action="store_true",
   548         "--ipv6",
   429         help="use pure Python code instead of C extensions")
   549         action="store_true",
   430     hgconf.add_argument("-3", "--py3-warnings", action="store_true",
   550         help="prefer IPv6 to IPv4 for network related tests",
   431         help="enable Py3k warnings on Python 2.7+")
   551     )
   432     hgconf.add_argument("--with-chg", metavar="CHG",
   552     hgconf.add_argument(
   433         help="use specified chg wrapper in place of hg")
   553         "--pure",
   434     hgconf.add_argument("--with-hg",
   554         action="store_true",
       
   555         help="use pure Python code instead of C extensions",
       
   556     )
       
   557     hgconf.add_argument(
       
   558         "-3",
       
   559         "--py3-warnings",
       
   560         action="store_true",
       
   561         help="enable Py3k warnings on Python 2.7+",
       
   562     )
       
   563     hgconf.add_argument(
       
   564         "--with-chg",
       
   565         metavar="CHG",
       
   566         help="use specified chg wrapper in place of hg",
       
   567     )
       
   568     hgconf.add_argument(
       
   569         "--with-hg",
   435         metavar="HG",
   570         metavar="HG",
   436         help="test using specified hg script rather than a "
   571         help="test using specified hg script rather than a "
   437              "temporary installation")
   572         "temporary installation",
       
   573     )
   438 
   574 
   439     reporting = parser.add_argument_group('Results Reporting')
   575     reporting = parser.add_argument_group('Results Reporting')
   440     reporting.add_argument("-C", "--annotate", action="store_true",
   576     reporting.add_argument(
   441         help="output files annotated with coverage")
   577         "-C",
   442     reporting.add_argument("--color", choices=["always", "auto", "never"],
   578         "--annotate",
       
   579         action="store_true",
       
   580         help="output files annotated with coverage",
       
   581     )
       
   582     reporting.add_argument(
       
   583         "--color",
       
   584         choices=["always", "auto", "never"],
   443         default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
   585         default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
   444         help="colorisation: always|auto|never (default: auto)")
   586         help="colorisation: always|auto|never (default: auto)",
   445     reporting.add_argument("-c", "--cover", action="store_true",
   587     )
   446         help="print a test coverage report")
   588     reporting.add_argument(
   447     reporting.add_argument('--exceptions', action='store_true',
   589         "-c",
   448         help='log all exceptions and generate an exception report')
   590         "--cover",
   449     reporting.add_argument("-H", "--htmlcov", action="store_true",
   591         action="store_true",
   450         help="create an HTML report of the coverage of the files")
   592         help="print a test coverage report",
   451     reporting.add_argument("--json", action="store_true",
   593     )
   452         help="store test result data in 'report.json' file")
   594     reporting.add_argument(
   453     reporting.add_argument("--outputdir",
   595         '--exceptions',
   454         help="directory to write error logs to (default=test directory)")
   596         action='store_true',
   455     reporting.add_argument("-n", "--nodiff", action="store_true",
   597         help='log all exceptions and generate an exception report',
   456         help="skip showing test changes")
   598     )
   457     reporting.add_argument("-S", "--noskips", action="store_true",
   599     reporting.add_argument(
   458         help="don't report skip tests verbosely")
   600         "-H",
   459     reporting.add_argument("--time", action="store_true",
   601         "--htmlcov",
   460         help="time how long each test takes")
   602         action="store_true",
   461     reporting.add_argument("--view",
   603         help="create an HTML report of the coverage of the files",
   462         help="external diff viewer")
   604     )
   463     reporting.add_argument("--xunit",
   605     reporting.add_argument(
   464         help="record xunit results at specified path")
   606         "--json",
       
   607         action="store_true",
       
   608         help="store test result data in 'report.json' file",
       
   609     )
       
   610     reporting.add_argument(
       
   611         "--outputdir",
       
   612         help="directory to write error logs to (default=test directory)",
       
   613     )
       
   614     reporting.add_argument(
       
   615         "-n", "--nodiff", action="store_true", help="skip showing test changes"
       
   616     )
       
   617     reporting.add_argument(
       
   618         "-S",
       
   619         "--noskips",
       
   620         action="store_true",
       
   621         help="don't report skip tests verbosely",
       
   622     )
       
   623     reporting.add_argument(
       
   624         "--time", action="store_true", help="time how long each test takes"
       
   625     )
       
   626     reporting.add_argument("--view", help="external diff viewer")
       
   627     reporting.add_argument(
       
   628         "--xunit", help="record xunit results at specified path"
       
   629     )
   465 
   630 
   466     for option, (envvar, default) in defaults.items():
   631     for option, (envvar, default) in defaults.items():
   467         defaults[option] = type(default)(os.environ.get(envvar, default))
   632         defaults[option] = type(default)(os.environ.get(envvar, default))
   468     parser.set_defaults(**defaults)
   633     parser.set_defaults(**defaults)
   469 
   634 
   470     return parser
   635     return parser
       
   636 
   471 
   637 
   472 def parseargs(args, parser):
   638 def parseargs(args, parser):
   473     """Parse arguments with our OptionParser and validate results."""
   639     """Parse arguments with our OptionParser and validate results."""
   474     options = parser.parse_args(args)
   640     options = parser.parse_args(args)
   475 
   641 
   486         if options.chg:
   652         if options.chg:
   487             pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
   653             pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
   488         for relpath, attr in pathandattrs:
   654         for relpath, attr in pathandattrs:
   489             binpath = os.path.join(reporootdir, relpath)
   655             binpath = os.path.join(reporootdir, relpath)
   490             if os.name != 'nt' and not os.access(binpath, os.X_OK):
   656             if os.name != 'nt' and not os.access(binpath, os.X_OK):
   491                 parser.error('--local specified, but %r not found or '
   657                 parser.error(
   492                              'not executable' % binpath)
   658                     '--local specified, but %r not found or '
       
   659                     'not executable' % binpath
       
   660                 )
   493             setattr(options, attr, _strpath(binpath))
   661             setattr(options, attr, _strpath(binpath))
   494 
   662 
   495     if options.with_hg:
   663     if options.with_hg:
   496         options.with_hg = canonpath(_bytespath(options.with_hg))
   664         options.with_hg = canonpath(_bytespath(options.with_hg))
   497         if not (os.path.isfile(options.with_hg) and
   665         if not (
   498                 os.access(options.with_hg, os.X_OK)):
   666             os.path.isfile(options.with_hg)
       
   667             and os.access(options.with_hg, os.X_OK)
       
   668         ):
   499             parser.error('--with-hg must specify an executable hg script')
   669             parser.error('--with-hg must specify an executable hg script')
   500         if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
   670         if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
   501             sys.stderr.write('warning: --with-hg should specify an hg script\n')
   671             sys.stderr.write('warning: --with-hg should specify an hg script\n')
   502             sys.stderr.flush()
   672             sys.stderr.flush()
   503 
   673 
   504     if (options.chg or options.with_chg) and os.name == 'nt':
   674     if (options.chg or options.with_chg) and os.name == 'nt':
   505         parser.error('chg does not work on %s' % os.name)
   675         parser.error('chg does not work on %s' % os.name)
   506     if options.with_chg:
   676     if options.with_chg:
   507         options.chg = False  # no installation to temporary location
   677         options.chg = False  # no installation to temporary location
   508         options.with_chg = canonpath(_bytespath(options.with_chg))
   678         options.with_chg = canonpath(_bytespath(options.with_chg))
   509         if not (os.path.isfile(options.with_chg) and
   679         if not (
   510                 os.access(options.with_chg, os.X_OK)):
   680             os.path.isfile(options.with_chg)
       
   681             and os.access(options.with_chg, os.X_OK)
       
   682         ):
   511             parser.error('--with-chg must specify a chg executable')
   683             parser.error('--with-chg must specify a chg executable')
   512     if options.chg and options.with_hg:
   684     if options.chg and options.with_hg:
   513         # chg shares installation location with hg
   685         # chg shares installation location with hg
   514         parser.error('--chg does not work when --with-hg is specified '
   686         parser.error(
   515                      '(use --with-chg instead)')
   687             '--chg does not work when --with-hg is specified '
       
   688             '(use --with-chg instead)'
       
   689         )
   516 
   690 
   517     if options.color == 'always' and not pygmentspresent:
   691     if options.color == 'always' and not pygmentspresent:
   518         sys.stderr.write('warning: --color=always ignored because '
   692         sys.stderr.write(
   519                          'pygments is not installed\n')
   693             'warning: --color=always ignored because '
       
   694             'pygments is not installed\n'
       
   695         )
   520 
   696 
   521     if options.bisect_repo and not options.known_good_rev:
   697     if options.bisect_repo and not options.known_good_rev:
   522         parser.error("--bisect-repo cannot be used without --known-good-rev")
   698         parser.error("--bisect-repo cannot be used without --known-good-rev")
   523 
   699 
   524     global useipv6
   700     global useipv6
   525     if options.ipv6:
   701     if options.ipv6:
   526         useipv6 = checksocketfamily('AF_INET6')
   702         useipv6 = checksocketfamily('AF_INET6')
   527     else:
   703     else:
   528         # only use IPv6 if IPv4 is unavailable and IPv6 is available
   704         # only use IPv6 if IPv4 is unavailable and IPv6 is available
   529         useipv6 = ((not checksocketfamily('AF_INET'))
   705         useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
   530                    and checksocketfamily('AF_INET6'))
   706             'AF_INET6'
       
   707         )
   531 
   708 
   532     options.anycoverage = options.cover or options.annotate or options.htmlcov
   709     options.anycoverage = options.cover or options.annotate or options.htmlcov
   533     if options.anycoverage:
   710     if options.anycoverage:
   534         try:
   711         try:
   535             import coverage
   712             import coverage
       
   713 
   536             covver = version.StrictVersion(coverage.__version__).version
   714             covver = version.StrictVersion(coverage.__version__).version
   537             if covver < (3, 3):
   715             if covver < (3, 3):
   538                 parser.error('coverage options require coverage 3.3 or later')
   716                 parser.error('coverage options require coverage 3.3 or later')
   539         except ImportError:
   717         except ImportError:
   540             parser.error('coverage options now require the coverage package')
   718             parser.error('coverage options now require the coverage package')
   541 
   719 
   542     if options.anycoverage and options.local:
   720     if options.anycoverage and options.local:
   543         # this needs some path mangling somewhere, I guess
   721         # this needs some path mangling somewhere, I guess
   544         parser.error("sorry, coverage options do not work when --local "
   722         parser.error(
   545                      "is specified")
   723             "sorry, coverage options do not work when --local " "is specified"
       
   724         )
   546 
   725 
   547     if options.anycoverage and options.with_hg:
   726     if options.anycoverage and options.with_hg:
   548         parser.error("sorry, coverage options do not work when --with-hg "
   727         parser.error(
   549                      "is specified")
   728             "sorry, coverage options do not work when --with-hg " "is specified"
       
   729         )
   550 
   730 
   551     global verbose
   731     global verbose
   552     if options.verbose:
   732     if options.verbose:
   553         verbose = ''
   733         verbose = ''
   554 
   734 
   559         parser.error('--jobs must be positive')
   739         parser.error('--jobs must be positive')
   560     if options.interactive and options.debug:
   740     if options.interactive and options.debug:
   561         parser.error("-i/--interactive and -d/--debug are incompatible")
   741         parser.error("-i/--interactive and -d/--debug are incompatible")
   562     if options.debug:
   742     if options.debug:
   563         if options.timeout != defaults['timeout']:
   743         if options.timeout != defaults['timeout']:
   564             sys.stderr.write(
   744             sys.stderr.write('warning: --timeout option ignored with --debug\n')
   565                 'warning: --timeout option ignored with --debug\n')
       
   566         if options.slowtimeout != defaults['slowtimeout']:
   745         if options.slowtimeout != defaults['slowtimeout']:
   567             sys.stderr.write(
   746             sys.stderr.write(
   568                 'warning: --slowtimeout option ignored with --debug\n')
   747                 'warning: --slowtimeout option ignored with --debug\n'
       
   748             )
   569         options.timeout = 0
   749         options.timeout = 0
   570         options.slowtimeout = 0
   750         options.slowtimeout = 0
   571     if options.py3_warnings:
   751     if options.py3_warnings:
   572         if PYTHON3:
   752         if PYTHON3:
   573             parser.error(
   753             parser.error('--py3-warnings can only be used on Python 2.7')
   574                 '--py3-warnings can only be used on Python 2.7')
       
   575 
   754 
   576     if options.blacklist:
   755     if options.blacklist:
   577         options.blacklist = parselistfiles(options.blacklist, 'blacklist')
   756         options.blacklist = parselistfiles(options.blacklist, 'blacklist')
   578     if options.whitelist:
   757     if options.whitelist:
   579         options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
   758         options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
   583     if options.showchannels:
   762     if options.showchannels:
   584         options.nodiff = True
   763         options.nodiff = True
   585 
   764 
   586     return options
   765     return options
   587 
   766 
       
   767 
   588 def rename(src, dst):
   768 def rename(src, dst):
   589     """Like os.rename(), trade atomicity and opened files friendliness
   769     """Like os.rename(), trade atomicity and opened files friendliness
   590     for existing destination support.
   770     for existing destination support.
   591     """
   771     """
   592     shutil.copy(src, dst)
   772     shutil.copy(src, dst)
   593     os.remove(src)
   773     os.remove(src)
       
   774 
   594 
   775 
   595 def makecleanable(path):
   776 def makecleanable(path):
   596     """Try to fix directory permission recursively so that the entire tree
   777     """Try to fix directory permission recursively so that the entire tree
   597     can be deleted"""
   778     can be deleted"""
   598     for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
   779     for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
   601             try:
   782             try:
   602                 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700)  # chmod u+rwx
   783                 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700)  # chmod u+rwx
   603             except OSError:
   784             except OSError:
   604                 pass
   785                 pass
   605 
   786 
       
   787 
   606 _unified_diff = difflib.unified_diff
   788 _unified_diff = difflib.unified_diff
   607 if PYTHON3:
   789 if PYTHON3:
   608     import functools
   790     import functools
       
   791 
   609     _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
   792     _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
       
   793 
   610 
   794 
   611 def getdiff(expected, output, ref, err):
   795 def getdiff(expected, output, ref, err):
   612     servefail = False
   796     servefail = False
   613     lines = []
   797     lines = []
   614     for line in _unified_diff(expected, output, ref, err):
   798     for line in _unified_diff(expected, output, ref, err):
   616             line = line.replace(b'\\', b'/')
   800             line = line.replace(b'\\', b'/')
   617             if line.endswith(b' \n'):
   801             if line.endswith(b' \n'):
   618                 line = line[:-2] + b'\n'
   802                 line = line[:-2] + b'\n'
   619         lines.append(line)
   803         lines.append(line)
   620         if not servefail and line.startswith(
   804         if not servefail and line.startswith(
   621                              b'+  abort: child process failed to start'):
   805             b'+  abort: child process failed to start'
       
   806         ):
   622             servefail = True
   807             servefail = True
   623 
   808 
   624     return servefail, lines
   809     return servefail, lines
   625 
   810 
       
   811 
   626 verbose = False
   812 verbose = False
       
   813 
       
   814 
   627 def vlog(*msg):
   815 def vlog(*msg):
   628     """Log only when in verbose mode."""
   816     """Log only when in verbose mode."""
   629     if verbose is False:
   817     if verbose is False:
   630         return
   818         return
   631 
   819 
   632     return log(*msg)
   820     return log(*msg)
       
   821 
   633 
   822 
   634 # Bytes that break XML even in a CDATA block: control characters 0-31
   823 # Bytes that break XML even in a CDATA block: control characters 0-31
   635 # sans \t, \n and \r
   824 # sans \t, \n and \r
   636 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
   825 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
   637 
   826 
   638 # Match feature conditionalized output lines in the form, capturing the feature
   827 # Match feature conditionalized output lines in the form, capturing the feature
   639 # list in group 2, and the preceeding line output in group 1:
   828 # list in group 2, and the preceeding line output in group 1:
   640 #
   829 #
   641 #   output..output (feature !)\n
   830 #   output..output (feature !)\n
   642 optline = re.compile(br'(.*) \((.+?) !\)\n$')
   831 optline = re.compile(br'(.*) \((.+?) !\)\n$')
       
   832 
   643 
   833 
   644 def cdatasafe(data):
   834 def cdatasafe(data):
   645     """Make a string safe to include in a CDATA block.
   835     """Make a string safe to include in a CDATA block.
   646 
   836 
   647     Certain control characters are illegal in a CDATA block, and
   837     Certain control characters are illegal in a CDATA block, and
   648     there's no way to include a ]]> in a CDATA either. This function
   838     there's no way to include a ]]> in a CDATA either. This function
   649     replaces illegal bytes with ? and adds a space between the ]] so
   839     replaces illegal bytes with ? and adds a space between the ]] so
   650     that it won't break the CDATA block.
   840     that it won't break the CDATA block.
   651     """
   841     """
   652     return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
   842     return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
       
   843 
   653 
   844 
   654 def log(*msg):
   845 def log(*msg):
   655     """Log something to stdout.
   846     """Log something to stdout.
   656 
   847 
   657     Arguments are strings to print.
   848     Arguments are strings to print.
   662         for m in msg:
   853         for m in msg:
   663             print(m, end=' ')
   854             print(m, end=' ')
   664         print()
   855         print()
   665         sys.stdout.flush()
   856         sys.stdout.flush()
   666 
   857 
       
   858 
   667 def highlightdiff(line, color):
   859 def highlightdiff(line, color):
   668     if not color:
   860     if not color:
   669         return line
   861         return line
   670     assert pygmentspresent
   862     assert pygmentspresent
   671     return pygments.highlight(line.decode('latin1'), difflexer,
   863     return pygments.highlight(
   672                               terminal256formatter).encode('latin1')
   864         line.decode('latin1'), difflexer, terminal256formatter
       
   865     ).encode('latin1')
       
   866 
   673 
   867 
   674 def highlightmsg(msg, color):
   868 def highlightmsg(msg, color):
   675     if not color:
   869     if not color:
   676         return msg
   870         return msg
   677     assert pygmentspresent
   871     assert pygmentspresent
   678     return pygments.highlight(msg, runnerlexer, runnerformatter)
   872     return pygments.highlight(msg, runnerlexer, runnerformatter)
       
   873 
   679 
   874 
   680 def terminate(proc):
   875 def terminate(proc):
   681     """Terminate subprocess"""
   876     """Terminate subprocess"""
   682     vlog('# Terminating process %d' % proc.pid)
   877     vlog('# Terminating process %d' % proc.pid)
   683     try:
   878     try:
   684         proc.terminate()
   879         proc.terminate()
   685     except OSError:
   880     except OSError:
   686         pass
   881         pass
   687 
   882 
       
   883 
   688 def killdaemons(pidfile):
   884 def killdaemons(pidfile):
   689     import killdaemons as killmod
   885     import killdaemons as killmod
   690     return killmod.killdaemons(pidfile, tryhard=False, remove=True,
   886 
   691                                logfn=vlog)
   887     return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
       
   888 
   692 
   889 
   693 class Test(unittest.TestCase):
   890 class Test(unittest.TestCase):
   694     """Encapsulates a single, runnable test.
   891     """Encapsulates a single, runnable test.
   695 
   892 
   696     While this class conforms to the unittest.TestCase API, it differs in that
   893     While this class conforms to the unittest.TestCase API, it differs in that
   699     """
   896     """
   700 
   897 
   701     # Status code reserved for skipped tests (used by hghave).
   898     # Status code reserved for skipped tests (used by hghave).
   702     SKIPPED_STATUS = 80
   899     SKIPPED_STATUS = 80
   703 
   900 
   704     def __init__(self, path, outputdir, tmpdir, keeptmpdir=False,
   901     def __init__(
   705                  debug=False,
   902         self,
   706                  first=False,
   903         path,
   707                  timeout=None,
   904         outputdir,
   708                  startport=None, extraconfigopts=None,
   905         tmpdir,
   709                  py3warnings=False, shell=None, hgcommand=None,
   906         keeptmpdir=False,
   710                  slowtimeout=None, usechg=False,
   907         debug=False,
   711                  useipv6=False):
   908         first=False,
       
   909         timeout=None,
       
   910         startport=None,
       
   911         extraconfigopts=None,
       
   912         py3warnings=False,
       
   913         shell=None,
       
   914         hgcommand=None,
       
   915         slowtimeout=None,
       
   916         usechg=False,
       
   917         useipv6=False,
       
   918     ):
   712         """Create a test from parameters.
   919         """Create a test from parameters.
   713 
   920 
   714         path is the full path to the file defining the test.
   921         path is the full path to the file defining the test.
   715 
   922 
   716         tmpdir is the main temporary directory to use for this test.
   923         tmpdir is the main temporary directory to use for this test.
   781     def readrefout(self):
   988     def readrefout(self):
   782         """read reference output"""
   989         """read reference output"""
   783         # If we're not in --debug mode and reference output file exists,
   990         # If we're not in --debug mode and reference output file exists,
   784         # check test output against it.
   991         # check test output against it.
   785         if self._debug:
   992         if self._debug:
   786             return None # to match "out is None"
   993             return None  # to match "out is None"
   787         elif os.path.exists(self.refpath):
   994         elif os.path.exists(self.refpath):
   788             with open(self.refpath, 'rb') as f:
   995             with open(self.refpath, 'rb') as f:
   789                 return f.read().splitlines(True)
   996                 return f.read().splitlines(True)
   790         else:
   997         else:
   791             return []
   998             return []
   828                 # file.
  1035                 # file.
   829                 if e.errno != errno.ENOENT:
  1036                 if e.errno != errno.ENOENT:
   830                     raise
  1037                     raise
   831 
  1038 
   832         if self._usechg:
  1039         if self._usechg:
   833             self._chgsockdir = os.path.join(self._threadtmp,
  1040             self._chgsockdir = os.path.join(
   834                                             b'%s.chgsock' % name)
  1041                 self._threadtmp, b'%s.chgsock' % name
       
  1042             )
   835             os.mkdir(self._chgsockdir)
  1043             os.mkdir(self._chgsockdir)
   836 
  1044 
   837     def run(self, result):
  1045     def run(self, result):
   838         """Run this test and report results against a TestResult instance."""
  1046         """Run this test and report results against a TestResult instance."""
   839         # This function is extremely similar to unittest.TestCase.run(). Once
  1047         # This function is extremely similar to unittest.TestCase.run(). Once
   912             return 'returned error code %d' % ret
  1120             return 'returned error code %d' % ret
   913 
  1121 
   914         self._skipped = False
  1122         self._skipped = False
   915 
  1123 
   916         if ret == self.SKIPPED_STATUS:
  1124         if ret == self.SKIPPED_STATUS:
   917             if out is None: # Debug mode, nothing to parse.
  1125             if out is None:  # Debug mode, nothing to parse.
   918                 missing = ['unknown']
  1126                 missing = ['unknown']
   919                 failed = None
  1127                 failed = None
   920             else:
  1128             else:
   921                 missing, failed = TTest.parsehghaveoutput(out)
  1129                 missing, failed = TTest.parsehghaveoutput(out)
   922 
  1130 
   932             self.fail('timed out')
  1140             self.fail('timed out')
   933         elif ret is False:
  1141         elif ret is False:
   934             self.fail('no result code from test')
  1142             self.fail('no result code from test')
   935         elif out != self._refout:
  1143         elif out != self._refout:
   936             # Diff generation may rely on written .err file.
  1144             # Diff generation may rely on written .err file.
   937             if ((ret != 0 or out != self._refout) and not self._skipped
  1145             if (
   938                 and not self._debug):
  1146                 (ret != 0 or out != self._refout)
       
  1147                 and not self._skipped
       
  1148                 and not self._debug
       
  1149             ):
   939                 with open(self.errpath, 'wb') as f:
  1150                 with open(self.errpath, 'wb') as f:
   940                     for line in out:
  1151                     for line in out:
   941                         f.write(line)
  1152                         f.write(line)
   942 
  1153 
   943             # The result object handles diff calculation for us.
  1154             # The result object handles diff calculation for us.
   963         for entry in self._daemonpids:
  1174         for entry in self._daemonpids:
   964             killdaemons(entry)
  1175             killdaemons(entry)
   965         self._daemonpids = []
  1176         self._daemonpids = []
   966 
  1177 
   967         if self._keeptmpdir:
  1178         if self._keeptmpdir:
   968             log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
  1179             log(
   969                 (self._testtmp.decode('utf-8'),
  1180                 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
   970                  self._threadtmp.decode('utf-8')))
  1181                 % (
       
  1182                     self._testtmp.decode('utf-8'),
       
  1183                     self._threadtmp.decode('utf-8'),
       
  1184                 )
       
  1185             )
   971         else:
  1186         else:
   972             try:
  1187             try:
   973                 shutil.rmtree(self._testtmp)
  1188                 shutil.rmtree(self._testtmp)
   974             except OSError:
  1189             except OSError:
   975                 # unreadable directory may be left in $TESTTMP; fix permission
  1190                 # unreadable directory may be left in $TESTTMP; fix permission
   981         if self._usechg:
  1196         if self._usechg:
   982             # chgservers will stop automatically after they find the socket
  1197             # chgservers will stop automatically after they find the socket
   983             # files are deleted
  1198             # files are deleted
   984             shutil.rmtree(self._chgsockdir, True)
  1199             shutil.rmtree(self._chgsockdir, True)
   985 
  1200 
   986         if ((self._ret != 0 or self._out != self._refout) and not self._skipped
  1201         if (
   987             and not self._debug and self._out):
  1202             (self._ret != 0 or self._out != self._refout)
       
  1203             and not self._skipped
       
  1204             and not self._debug
       
  1205             and self._out
       
  1206         ):
   988             with open(self.errpath, 'wb') as f:
  1207             with open(self.errpath, 'wb') as f:
   989                 for line in self._out:
  1208                 for line in self._out:
   990                     f.write(line)
  1209                     f.write(line)
   991 
  1210 
   992         vlog("# Ret was:", self._ret, '(%s)' % self.name)
  1211         vlog("# Ret was:", self._ret, '(%s)' % self.name)
  1015             self._portmap(0),
  1234             self._portmap(0),
  1016             self._portmap(1),
  1235             self._portmap(1),
  1017             self._portmap(2),
  1236             self._portmap(2),
  1018             (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
  1237             (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
  1019             (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
  1238             (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
  1020             ]
  1239         ]
  1021         r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
  1240         r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
  1022 
  1241 
  1023         replacementfile = os.path.join(self._testdir, b'common-pattern.py')
  1242         replacementfile = os.path.join(self._testdir, b'common-pattern.py')
  1024 
  1243 
  1025         if os.path.exists(replacementfile):
  1244         if os.path.exists(replacementfile):
  1036                     r.append(value)
  1255                     r.append(value)
  1037         return r
  1256         return r
  1038 
  1257 
  1039     def _escapepath(self, p):
  1258     def _escapepath(self, p):
  1040         if os.name == 'nt':
  1259         if os.name == 'nt':
  1041             return (
  1260             return b''.join(
  1042                 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
  1261                 c.isalpha()
  1043                     c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
  1262                 and b'[%s%s]' % (c.lower(), c.upper())
  1044                     for c in [p[i:i + 1] for i in range(len(p))]))
  1263                 or c in b'/\\'
       
  1264                 and br'[/\\]'
       
  1265                 or c.isdigit()
       
  1266                 and c
       
  1267                 or b'\\' + c
       
  1268                 for c in [p[i : i + 1] for i in range(len(p))]
  1045             )
  1269             )
  1046         else:
  1270         else:
  1047             return re.escape(p)
  1271             return re.escape(p)
  1048 
  1272 
  1049     def _localip(self):
  1273     def _localip(self):
  1081                     continue
  1305                     continue
  1082                 envf.write('unset %s\n' % (name,))
  1306                 envf.write('unset %s\n' % (name,))
  1083 
  1307 
  1084     def _getenv(self):
  1308     def _getenv(self):
  1085         """Obtain environment variables to use during test execution."""
  1309         """Obtain environment variables to use during test execution."""
       
  1310 
  1086         def defineport(i):
  1311         def defineport(i):
  1087             offset = '' if i == 0 else '%s' % i
  1312             offset = '' if i == 0 else '%s' % i
  1088             env["HGPORT%s" % offset] = '%s' % (self._startport + i)
  1313             env["HGPORT%s" % offset] = '%s' % (self._startport + i)
       
  1314 
  1089         env = os.environ.copy()
  1315         env = os.environ.copy()
  1090         env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
  1316         env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
  1091         env['HGEMITWARNINGS'] = '1'
  1317         env['HGEMITWARNINGS'] = '1'
  1092         env['TESTTMP'] = _strpath(self._testtmp)
  1318         env['TESTTMP'] = _strpath(self._testtmp)
  1093         env['TESTNAME'] = self.name
  1319         env['TESTNAME'] = self.name
  1095         # This number should match portneeded in _getport
  1321         # This number should match portneeded in _getport
  1096         for port in xrange(3):
  1322         for port in xrange(3):
  1097             # This list should be parallel to _portmap in _getreplacements
  1323             # This list should be parallel to _portmap in _getreplacements
  1098             defineport(port)
  1324             defineport(port)
  1099         env["HGRCPATH"] = _strpath(os.path.join(self._threadtmp, b'.hgrc'))
  1325         env["HGRCPATH"] = _strpath(os.path.join(self._threadtmp, b'.hgrc'))
  1100         env["DAEMON_PIDS"] = _strpath(os.path.join(self._threadtmp,
  1326         env["DAEMON_PIDS"] = _strpath(
  1101                                                    b'daemon.pids'))
  1327             os.path.join(self._threadtmp, b'daemon.pids')
  1102         env["HGEDITOR"] = ('"' + sysexecutable + '"'
  1328         )
  1103                            + ' -c "import sys; sys.exit(0)"')
  1329         env["HGEDITOR"] = (
  1104         env["HGUSER"]   = "test"
  1330             '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
       
  1331         )
       
  1332         env["HGUSER"] = "test"
  1105         env["HGENCODING"] = "ascii"
  1333         env["HGENCODING"] = "ascii"
  1106         env["HGENCODINGMODE"] = "strict"
  1334         env["HGENCODINGMODE"] = "strict"
  1107         env["HGHOSTNAME"] = "test-hostname"
  1335         env["HGHOSTNAME"] = "test-hostname"
  1108         env['HGIPV6'] = str(int(self._useipv6))
  1336         env['HGIPV6'] = str(int(self._useipv6))
  1109         # See contrib/catapipe.py for how to use this functionality.
  1337         # See contrib/catapipe.py for how to use this functionality.
  1110         if 'HGTESTCATAPULTSERVERPIPE' not in env:
  1338         if 'HGTESTCATAPULTSERVERPIPE' not in env:
  1111             # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
  1339             # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
  1112             # non-test one in as a default, otherwise set to devnull
  1340             # non-test one in as a default, otherwise set to devnull
  1113             env['HGTESTCATAPULTSERVERPIPE'] = env.get(
  1341             env['HGTESTCATAPULTSERVERPIPE'] = env.get(
  1114                 'HGCATAPULTSERVERPIPE', os.devnull)
  1342                 'HGCATAPULTSERVERPIPE', os.devnull
       
  1343             )
  1115 
  1344 
  1116         extraextensions = []
  1345         extraextensions = []
  1117         for opt in self._extraconfigopts:
  1346         for opt in self._extraconfigopts:
  1118             section, key = opt.encode('utf-8').split(b'.', 1)
  1347             section, key = opt.encode('utf-8').split(b'.', 1)
  1119             if section != 'extensions':
  1348             if section != 'extensions':
  1185             hgrc.write(b'[defaults]\n')
  1414             hgrc.write(b'[defaults]\n')
  1186             hgrc.write(b'[devel]\n')
  1415             hgrc.write(b'[devel]\n')
  1187             hgrc.write(b'all-warnings = true\n')
  1416             hgrc.write(b'all-warnings = true\n')
  1188             hgrc.write(b'default-date = 0 0\n')
  1417             hgrc.write(b'default-date = 0 0\n')
  1189             hgrc.write(b'[largefiles]\n')
  1418             hgrc.write(b'[largefiles]\n')
  1190             hgrc.write(b'usercache = %s\n' %
  1419             hgrc.write(
  1191                        (os.path.join(self._testtmp, b'.cache/largefiles')))
  1420                 b'usercache = %s\n'
       
  1421                 % (os.path.join(self._testtmp, b'.cache/largefiles'))
       
  1422             )
  1192             hgrc.write(b'[lfs]\n')
  1423             hgrc.write(b'[lfs]\n')
  1193             hgrc.write(b'usercache = %s\n' %
  1424             hgrc.write(
  1194                        (os.path.join(self._testtmp, b'.cache/lfs')))
  1425                 b'usercache = %s\n'
       
  1426                 % (os.path.join(self._testtmp, b'.cache/lfs'))
       
  1427             )
  1195             hgrc.write(b'[web]\n')
  1428             hgrc.write(b'[web]\n')
  1196             hgrc.write(b'address = localhost\n')
  1429             hgrc.write(b'address = localhost\n')
  1197             hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
  1430             hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
  1198             hgrc.write(b'server-header = testing stub value\n')
  1431             hgrc.write(b'server-header = testing stub value\n')
  1199 
  1432 
  1200             for opt in self._extraconfigopts:
  1433             for opt in self._extraconfigopts:
  1201                 section, key = opt.encode('utf-8').split(b'.', 1)
  1434                 section, key = opt.encode('utf-8').split(b'.', 1)
  1202                 assert b'=' in key, ('extra config opt %s must '
  1435                 assert b'=' in key, (
  1203                                      'have an = for assignment' % opt)
  1436                     'extra config opt %s must ' 'have an = for assignment' % opt
       
  1437                 )
  1204                 hgrc.write(b'[%s]\n%s\n' % (section, key))
  1438                 hgrc.write(b'[%s]\n%s\n' % (section, key))
  1205 
  1439 
  1206     def fail(self, msg):
  1440     def fail(self, msg):
  1207         # unittest differentiates between errored and failed.
  1441         # unittest differentiates between errored and failed.
  1208         # Failed is denoted by AssertionError (by default at least).
  1442         # Failed is denoted by AssertionError (by default at least).
  1213         stderr).
  1447         stderr).
  1214 
  1448 
  1215         Return a tuple (exitcode, output). output is None in debug mode.
  1449         Return a tuple (exitcode, output). output is None in debug mode.
  1216         """
  1450         """
  1217         if self._debug:
  1451         if self._debug:
  1218             proc = subprocess.Popen(_strpath(cmd), shell=True,
  1452             proc = subprocess.Popen(
  1219                                     cwd=_strpath(self._testtmp),
  1453                 _strpath(cmd), shell=True, cwd=_strpath(self._testtmp), env=env
  1220                                     env=env)
  1454             )
  1221             ret = proc.wait()
  1455             ret = proc.wait()
  1222             return (ret, None)
  1456             return (ret, None)
  1223 
  1457 
  1224         proc = Popen4(cmd, self._testtmp, self._timeout, env)
  1458         proc = Popen4(cmd, self._testtmp, self._timeout, env)
       
  1459 
  1225         def cleanup():
  1460         def cleanup():
  1226             terminate(proc)
  1461             terminate(proc)
  1227             ret = proc.wait()
  1462             ret = proc.wait()
  1228             if ret == 0:
  1463             if ret == 0:
  1229                 ret = signal.SIGTERM << 8
  1464                 ret = signal.SIGTERM << 8
  1255         if normalizenewlines:
  1490         if normalizenewlines:
  1256             output = output.replace(b'\r\n', b'\n')
  1491             output = output.replace(b'\r\n', b'\n')
  1257 
  1492 
  1258         return ret, output.splitlines(True)
  1493         return ret, output.splitlines(True)
  1259 
  1494 
       
  1495 
  1260 class PythonTest(Test):
  1496 class PythonTest(Test):
  1261     """A Python-based test."""
  1497     """A Python-based test."""
  1262 
  1498 
  1263     @property
  1499     @property
  1264     def refpath(self):
  1500     def refpath(self):
  1268         py3switch = self._py3warnings and b' -3' or b''
  1504         py3switch = self._py3warnings and b' -3' or b''
  1269         # Quote the python(3) executable for Windows
  1505         # Quote the python(3) executable for Windows
  1270         cmd = b'"%s"%s "%s"' % (PYTHON, py3switch, self.path)
  1506         cmd = b'"%s"%s "%s"' % (PYTHON, py3switch, self.path)
  1271         vlog("# Running", cmd)
  1507         vlog("# Running", cmd)
  1272         normalizenewlines = os.name == 'nt'
  1508         normalizenewlines = os.name == 'nt'
  1273         result = self._runcommand(cmd, env,
  1509         result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines)
  1274                                   normalizenewlines=normalizenewlines)
       
  1275         if self._aborted:
  1510         if self._aborted:
  1276             raise KeyboardInterrupt()
  1511             raise KeyboardInterrupt()
  1277 
  1512 
  1278         return result
  1513         return result
       
  1514 
  1279 
  1515 
  1280 # Some glob patterns apply only in some circumstances, so the script
  1516 # Some glob patterns apply only in some circumstances, so the script
  1281 # might want to remove (glob) annotations that otherwise should be
  1517 # might want to remove (glob) annotations that otherwise should be
  1282 # retained.
  1518 # retained.
  1283 checkcodeglobpats = [
  1519 checkcodeglobpats = [
  1299 WARN_YES = 2
  1535 WARN_YES = 2
  1300 WARN_NO = 3
  1536 WARN_NO = 3
  1301 
  1537 
  1302 MARK_OPTIONAL = b" (?)\n"
  1538 MARK_OPTIONAL = b" (?)\n"
  1303 
  1539 
       
  1540 
  1304 def isoptional(line):
  1541 def isoptional(line):
  1305     return line.endswith(MARK_OPTIONAL)
  1542     return line.endswith(MARK_OPTIONAL)
       
  1543 
  1306 
  1544 
  1307 class TTest(Test):
  1545 class TTest(Test):
  1308     """A "t test" is a test backed by a .t file."""
  1546     """A "t test" is a test backed by a .t file."""
  1309 
  1547 
  1310     SKIPPED_PREFIX = b'skipped: '
  1548     SKIPPED_PREFIX = b'skipped: '
  1374             return self._have.get(allreqs)
  1612             return self._have.get(allreqs)
  1375 
  1613 
  1376         # TODO do something smarter when all other uses of hghave are gone.
  1614         # TODO do something smarter when all other uses of hghave are gone.
  1377         runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
  1615         runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
  1378         tdir = runtestdir.replace(b'\\', b'/')
  1616         tdir = runtestdir.replace(b'\\', b'/')
  1379         proc = Popen4(b'%s -c "%s/hghave %s"' %
  1617         proc = Popen4(
  1380                       (self._shell, tdir, allreqs),
  1618             b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
  1381                       self._testtmp, 0, self._getenv())
  1619             self._testtmp,
       
  1620             0,
       
  1621             self._getenv(),
       
  1622         )
  1382         stdout, stderr = proc.communicate()
  1623         stdout, stderr = proc.communicate()
  1383         ret = proc.wait()
  1624         ret = proc.wait()
  1384         if wifexited(ret):
  1625         if wifexited(ret):
  1385             ret = os.WEXITSTATUS(ret)
  1626             ret = os.WEXITSTATUS(ret)
  1386         if ret == 2:
  1627         if ret == 2:
  1417     def _parsetest(self, lines):
  1658     def _parsetest(self, lines):
  1418         # We generate a shell script which outputs unique markers to line
  1659         # We generate a shell script which outputs unique markers to line
  1419         # up script results with our source. These markers include input
  1660         # up script results with our source. These markers include input
  1420         # line number and the last return code.
  1661         # line number and the last return code.
  1421         salt = b"SALT%d" % time.time()
  1662         salt = b"SALT%d" % time.time()
       
  1663 
  1422         def addsalt(line, inpython):
  1664         def addsalt(line, inpython):
  1423             if inpython:
  1665             if inpython:
  1424                 script.append(b'%s %d 0\n' % (salt, line))
  1666                 script.append(b'%s %d 0\n' % (salt, line))
  1425             else:
  1667             else:
  1426                 script.append(b'echo %s %d $?\n' % (salt, line))
  1668                 script.append(b'echo %s %d $?\n' % (salt, line))
       
  1669 
  1427         activetrace = []
  1670         activetrace = []
  1428         session = str(uuid.uuid4())
  1671         session = str(uuid.uuid4())
  1429         if PYTHON3:
  1672         if PYTHON3:
  1430             session = session.encode('ascii')
  1673             session = session.encode('ascii')
  1431         hgcatapult = (os.getenv('HGTESTCATAPULTSERVERPIPE') or
  1674         hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
  1432                       os.getenv('HGCATAPULTSERVERPIPE'))
  1675             'HGCATAPULTSERVERPIPE'
       
  1676         )
       
  1677 
  1433         def toggletrace(cmd=None):
  1678         def toggletrace(cmd=None):
  1434             if not hgcatapult or hgcatapult == os.devnull:
  1679             if not hgcatapult or hgcatapult == os.devnull:
  1435                 return
  1680                 return
  1436 
  1681 
  1437             if activetrace:
  1682             if activetrace:
  1438                 script.append(
  1683                 script.append(
  1439                     b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' % (
  1684                     b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
  1440                         session, activetrace[0]))
  1685                     % (session, activetrace[0])
       
  1686                 )
  1441             if cmd is None:
  1687             if cmd is None:
  1442                 return
  1688                 return
  1443 
  1689 
  1444             if isinstance(cmd, str):
  1690             if isinstance(cmd, str):
  1445                 quoted = shellquote(cmd.strip())
  1691                 quoted = shellquote(cmd.strip())
  1446             else:
  1692             else:
  1447                 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
  1693                 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
  1448             quoted = quoted.replace(b'\\', b'\\\\')
  1694             quoted = quoted.replace(b'\\', b'\\\\')
  1449             script.append(
  1695             script.append(
  1450                 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' % (
  1696                 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
  1451                     session, quoted))
  1697                 % (session, quoted)
       
  1698             )
  1452             activetrace[0:] = [quoted]
  1699             activetrace[0:] = [quoted]
  1453 
  1700 
  1454         script = []
  1701         script = []
  1455 
  1702 
  1456         # After we run the shell script, we re-unify the script output
  1703         # After we run the shell script, we re-unify the script output
  1548                     after.setdefault(pos, []).append('  !!! missing #if\n')
  1795                     after.setdefault(pos, []).append('  !!! missing #if\n')
  1549                 skipping = None
  1796                 skipping = None
  1550                 after.setdefault(pos, []).append(l)
  1797                 after.setdefault(pos, []).append(l)
  1551             elif skipping:
  1798             elif skipping:
  1552                 after.setdefault(pos, []).append(l)
  1799                 after.setdefault(pos, []).append(l)
  1553             elif l.startswith(b'  >>> '): # python inlines
  1800             elif l.startswith(b'  >>> '):  # python inlines
  1554                 after.setdefault(pos, []).append(l)
  1801                 after.setdefault(pos, []).append(l)
  1555                 prepos = pos
  1802                 prepos = pos
  1556                 pos = n
  1803                 pos = n
  1557                 if not inpython:
  1804                 if not inpython:
  1558                     # We've just entered a Python block. Add the header.
  1805                     # We've just entered a Python block. Add the header.
  1559                     inpython = True
  1806                     inpython = True
  1560                     addsalt(prepos, False) # Make sure we report the exit code.
  1807                     addsalt(prepos, False)  # Make sure we report the exit code.
  1561                     script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
  1808                     script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
  1562                 addsalt(n, True)
  1809                 addsalt(n, True)
  1563                 script.append(l[2:])
  1810                 script.append(l[2:])
  1564             elif l.startswith(b'  ... '): # python inlines
  1811             elif l.startswith(b'  ... '):  # python inlines
  1565                 after.setdefault(prepos, []).append(l)
  1812                 after.setdefault(prepos, []).append(l)
  1566                 script.append(l[2:])
  1813                 script.append(l[2:])
  1567             elif l.startswith(b'  $ '): # commands
  1814             elif l.startswith(b'  $ '):  # commands
  1568                 if inpython:
  1815                 if inpython:
  1569                     script.append(b'EOF\n')
  1816                     script.append(b'EOF\n')
  1570                     inpython = False
  1817                     inpython = False
  1571                 after.setdefault(pos, []).append(l)
  1818                 after.setdefault(pos, []).append(l)
  1572                 prepos = pos
  1819                 prepos = pos
  1576                 cmd = rawcmd.split()
  1823                 cmd = rawcmd.split()
  1577                 toggletrace(rawcmd)
  1824                 toggletrace(rawcmd)
  1578                 if len(cmd) == 2 and cmd[0] == b'cd':
  1825                 if len(cmd) == 2 and cmd[0] == b'cd':
  1579                     l = b'  $ cd %s || exit 1\n' % cmd[1]
  1826                     l = b'  $ cd %s || exit 1\n' % cmd[1]
  1580                 script.append(rawcmd)
  1827                 script.append(rawcmd)
  1581             elif l.startswith(b'  > '): # continuations
  1828             elif l.startswith(b'  > '):  # continuations
  1582                 after.setdefault(prepos, []).append(l)
  1829                 after.setdefault(prepos, []).append(l)
  1583                 script.append(l[4:])
  1830                 script.append(l[4:])
  1584             elif l.startswith(b'  '): # results
  1831             elif l.startswith(b'  '):  # results
  1585                 # Queue up a list of expected results.
  1832                 # Queue up a list of expected results.
  1586                 expected.setdefault(pos, []).append(l[2:])
  1833                 expected.setdefault(pos, []).append(l[2:])
  1587             else:
  1834             else:
  1588                 if inpython:
  1835                 if inpython:
  1589                     script.append(b'EOF\n')
  1836                     script.append(b'EOF\n')
  1601             toggletrace()
  1848             toggletrace()
  1602         return salt, script, after, expected
  1849         return salt, script, after, expected
  1603 
  1850 
  1604     def _processoutput(self, exitcode, output, salt, after, expected):
  1851     def _processoutput(self, exitcode, output, salt, after, expected):
  1605         # Merge the script output back into a unified test.
  1852         # Merge the script output back into a unified test.
  1606         warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
  1853         warnonly = WARN_UNDEFINED  # 1: not yet; 2: yes; 3: for sure not
  1607         if exitcode != 0:
  1854         if exitcode != 0:
  1608             warnonly = WARN_NO
  1855             warnonly = WARN_NO
  1609 
  1856 
  1610         pos = -1
  1857         pos = -1
  1611         postout = []
  1858         postout = []
  1612         for out_rawline in output:
  1859         for out_rawline in output:
  1613             out_line, cmd_line = out_rawline, None
  1860             out_line, cmd_line = out_rawline, None
  1614             if salt in out_rawline:
  1861             if salt in out_rawline:
  1615                 out_line, cmd_line = out_rawline.split(salt, 1)
  1862                 out_line, cmd_line = out_rawline.split(salt, 1)
  1616 
  1863 
  1617             pos, postout, warnonly = self._process_out_line(out_line,
  1864             pos, postout, warnonly = self._process_out_line(
  1618                                                             pos,
  1865                 out_line, pos, postout, expected, warnonly
  1619                                                             postout,
  1866             )
  1620                                                             expected,
  1867             pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
  1621                                                             warnonly)
       
  1622             pos, postout = self._process_cmd_line(cmd_line, pos, postout,
       
  1623                                                   after)
       
  1624 
  1868 
  1625         if pos in after:
  1869         if pos in after:
  1626             postout += after.pop(pos)
  1870             postout += after.pop(pos)
  1627 
  1871 
  1628         if warnonly == WARN_YES:
  1872         if warnonly == WARN_YES:
  1629             exitcode = False # Set exitcode to warned.
  1873             exitcode = False  # Set exitcode to warned.
  1630 
  1874 
  1631         return exitcode, postout
  1875         return exitcode, postout
  1632 
  1876 
  1633     def _process_out_line(self, out_line, pos, postout, expected, warnonly):
  1877     def _process_out_line(self, out_line, pos, postout, expected, warnonly):
  1634         while out_line:
  1878         while out_line:
  1646                 if el:
  1890                 if el:
  1647                     r, exact = self.linematch(el, out_line)
  1891                     r, exact = self.linematch(el, out_line)
  1648                 if isinstance(r, str):
  1892                 if isinstance(r, str):
  1649                     if r == '-glob':
  1893                     if r == '-glob':
  1650                         out_line = ''.join(el.rsplit(' (glob)', 1))
  1894                         out_line = ''.join(el.rsplit(' (glob)', 1))
  1651                         r = '' # Warn only this line.
  1895                         r = ''  # Warn only this line.
  1652                     elif r == "retry":
  1896                     elif r == "retry":
  1653                         postout.append(b'  ' + el)
  1897                         postout.append(b'  ' + el)
  1654                     else:
  1898                     else:
  1655                         log('\ninfo, unknown linematch result: %r\n' % r)
  1899                         log('\ninfo, unknown linematch result: %r\n' % r)
  1656                         r = False
  1900                         r = False
  1661                     if isoptional(el):
  1905                     if isoptional(el):
  1662                         optional.append(i)
  1906                         optional.append(i)
  1663                     else:
  1907                     else:
  1664                         m = optline.match(el)
  1908                         m = optline.match(el)
  1665                         if m:
  1909                         if m:
  1666                             conditions = [
  1910                             conditions = [c for c in m.group(2).split(b' ')]
  1667                                 c for c in m.group(2).split(b' ')]
       
  1668 
  1911 
  1669                             if not self._iftest(conditions):
  1912                             if not self._iftest(conditions):
  1670                                 optional.append(i)
  1913                                 optional.append(i)
  1671                     if exact:
  1914                     if exact:
  1672                         # Don't allow line to be matches against a later
  1915                         # Don't allow line to be matches against a later
  1683                 for i in reversed(optional):
  1926                 for i in reversed(optional):
  1684                     del els[i]
  1927                     del els[i]
  1685                 postout.append(b'  ' + el)
  1928                 postout.append(b'  ' + el)
  1686             else:
  1929             else:
  1687                 if self.NEEDESCAPE(out_line):
  1930                 if self.NEEDESCAPE(out_line):
  1688                     out_line = TTest._stringescape(b'%s (esc)\n' %
  1931                     out_line = TTest._stringescape(
  1689                                                out_line.rstrip(b'\n'))
  1932                         b'%s (esc)\n' % out_line.rstrip(b'\n')
  1690                 postout.append(b'  ' + out_line) # Let diff deal with it.
  1933                     )
  1691                 if r != '': # If line failed.
  1934                 postout.append(b'  ' + out_line)  # Let diff deal with it.
       
  1935                 if r != '':  # If line failed.
  1692                     warnonly = WARN_NO
  1936                     warnonly = WARN_NO
  1693                 elif warnonly == WARN_UNDEFINED:
  1937                 elif warnonly == WARN_UNDEFINED:
  1694                     warnonly = WARN_YES
  1938                     warnonly = WARN_YES
  1695             break
  1939             break
  1696         else:
  1940         else:
  1750             return True
  1994             return True
  1751         el = el.replace(b'$LOCALIP', b'*')
  1995         el = el.replace(b'$LOCALIP', b'*')
  1752         i, n = 0, len(el)
  1996         i, n = 0, len(el)
  1753         res = b''
  1997         res = b''
  1754         while i < n:
  1998         while i < n:
  1755             c = el[i:i + 1]
  1999             c = el[i : i + 1]
  1756             i += 1
  2000             i += 1
  1757             if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
  2001             if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
  1758                 res += el[i - 1:i + 1]
  2002                 res += el[i - 1 : i + 1]
  1759                 i += 1
  2003                 i += 1
  1760             elif c == b'*':
  2004             elif c == b'*':
  1761                 res += b'.*'
  2005                 res += b'.*'
  1762             elif c == b'?':
  2006             elif c == b'?':
  1763                 res += b'.'
  2007                 res += b'.'
  1766             else:
  2010             else:
  1767                 res += re.escape(c)
  2011                 res += re.escape(c)
  1768         return TTest.rematch(res, l)
  2012         return TTest.rematch(res, l)
  1769 
  2013 
  1770     def linematch(self, el, l):
  2014     def linematch(self, el, l):
  1771         if el == l: # perfect match (fast)
  2015         if el == l:  # perfect match (fast)
  1772             return True, True
  2016             return True, True
  1773         retry = False
  2017         retry = False
  1774         if isoptional(el):
  2018         if isoptional(el):
  1775             retry = "retry"
  2019             retry = "retry"
  1776             el = el[:-len(MARK_OPTIONAL)] + b"\n"
  2020             el = el[: -len(MARK_OPTIONAL)] + b"\n"
  1777         else:
  2021         else:
  1778             m = optline.match(el)
  2022             m = optline.match(el)
  1779             if m:
  2023             if m:
  1780                 conditions = [c for c in m.group(2).split(b' ')]
  2024                 conditions = [c for c in m.group(2).split(b' ')]
  1781 
  2025 
  1815         missing = []
  2059         missing = []
  1816         failed = []
  2060         failed = []
  1817         for line in lines:
  2061         for line in lines:
  1818             if line.startswith(TTest.SKIPPED_PREFIX):
  2062             if line.startswith(TTest.SKIPPED_PREFIX):
  1819                 line = line.splitlines()[0]
  2063                 line = line.splitlines()[0]
  1820                 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
  2064                 missing.append(
       
  2065                     line[len(TTest.SKIPPED_PREFIX) :].decode('utf-8')
       
  2066                 )
  1821             elif line.startswith(TTest.FAILED_PREFIX):
  2067             elif line.startswith(TTest.FAILED_PREFIX):
  1822                 line = line.splitlines()[0]
  2068                 line = line.splitlines()[0]
  1823                 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
  2069                 failed.append(line[len(TTest.FAILED_PREFIX) :].decode('utf-8'))
  1824 
  2070 
  1825         return missing, failed
  2071         return missing, failed
  1826 
  2072 
  1827     @staticmethod
  2073     @staticmethod
  1828     def _escapef(m):
  2074     def _escapef(m):
  1830 
  2076 
  1831     @staticmethod
  2077     @staticmethod
  1832     def _stringescape(s):
  2078     def _stringescape(s):
  1833         return TTest.ESCAPESUB(TTest._escapef, s)
  2079         return TTest.ESCAPESUB(TTest._escapef, s)
  1834 
  2080 
       
  2081 
  1835 iolock = threading.RLock()
  2082 iolock = threading.RLock()
  1836 firstlock = threading.RLock()
  2083 firstlock = threading.RLock()
  1837 firsterror = False
  2084 firsterror = False
  1838 
  2085 
       
  2086 
  1839 class TestResult(unittest._TextTestResult):
  2087 class TestResult(unittest._TextTestResult):
  1840     """Holds results when executing via unittest."""
  2088     """Holds results when executing via unittest."""
       
  2089 
  1841     # Don't worry too much about accessing the non-public _TextTestResult.
  2090     # Don't worry too much about accessing the non-public _TextTestResult.
  1842     # It is relatively common in Python testing tools.
  2091     # It is relatively common in Python testing tools.
  1843     def __init__(self, options, *args, **kwargs):
  2092     def __init__(self, options, *args, **kwargs):
  1844         super(TestResult, self).__init__(*args, **kwargs)
  2093         super(TestResult, self).__init__(*args, **kwargs)
  1845 
  2094 
  1862 
  2111 
  1863         if options.color == 'auto':
  2112         if options.color == 'auto':
  1864             self.color = pygmentspresent and self.stream.isatty()
  2113             self.color = pygmentspresent and self.stream.isatty()
  1865         elif options.color == 'never':
  2114         elif options.color == 'never':
  1866             self.color = False
  2115             self.color = False
  1867         else: # 'always', for testing purposes
  2116         else:  # 'always', for testing purposes
  1868             self.color = pygmentspresent
  2117             self.color = pygmentspresent
  1869 
  2118 
  1870     def onStart(self, test):
  2119     def onStart(self, test):
  1871         """ Can be overriden by custom TestResult
  2120         """ Can be overriden by custom TestResult
  1872         """
  2121         """
  1940         with iolock:
  2189         with iolock:
  1941             if self._options.nodiff:
  2190             if self._options.nodiff:
  1942                 pass
  2191                 pass
  1943             elif self._options.view:
  2192             elif self._options.view:
  1944                 v = self._options.view
  2193                 v = self._options.view
  1945                 subprocess.call(r'"%s" "%s" "%s"' %
  2194                 subprocess.call(
  1946                                 (v, _strpath(test.refpath),
  2195                     r'"%s" "%s" "%s"'
  1947                                  _strpath(test.errpath)), shell=True)
  2196                     % (v, _strpath(test.refpath), _strpath(test.errpath)),
       
  2197                     shell=True,
       
  2198                 )
  1948             else:
  2199             else:
  1949                 servefail, lines = getdiff(expected, got,
  2200                 servefail, lines = getdiff(
  1950                                            test.refpath, test.errpath)
  2201                     expected, got, test.refpath, test.errpath
       
  2202                 )
  1951                 self.stream.write('\n')
  2203                 self.stream.write('\n')
  1952                 for line in lines:
  2204                 for line in lines:
  1953                     line = highlightdiff(line, self.color)
  2205                     line = highlightdiff(line, self.color)
  1954                     if PYTHON3:
  2206                     if PYTHON3:
  1955                         self.stream.flush()
  2207                         self.stream.flush()
  1959                         self.stream.write(line)
  2211                         self.stream.write(line)
  1960                         self.stream.flush()
  2212                         self.stream.flush()
  1961 
  2213 
  1962                 if servefail:
  2214                 if servefail:
  1963                     raise test.failureException(
  2215                     raise test.failureException(
  1964                         'server failed to start (HGPORT=%s)' % test._startport)
  2216                         'server failed to start (HGPORT=%s)' % test._startport
       
  2217                     )
  1965 
  2218 
  1966             # handle interactive prompt without releasing iolock
  2219             # handle interactive prompt without releasing iolock
  1967             if self._options.interactive:
  2220             if self._options.interactive:
  1968                 if test.readrefout() != expected:
  2221                 if test.readrefout() != expected:
  1969                     self.stream.write(
  2222                     self.stream.write(
  1970                         'Reference output has changed (run again to prompt '
  2223                         'Reference output has changed (run again to prompt '
  1971                         'changes)')
  2224                         'changes)'
       
  2225                     )
  1972                 else:
  2226                 else:
  1973                     self.stream.write('Accept this change? [n] ')
  2227                     self.stream.write('Accept this change? [n] ')
  1974                     self.stream.flush()
  2228                     self.stream.flush()
  1975                     answer = sys.stdin.readline().strip()
  2229                     answer = sys.stdin.readline().strip()
  1976                     if answer.lower() in ('y', 'yes'):
  2230                     if answer.lower() in ('y', 'yes'):
  1990         # os.times module computes the user time and system time spent by
  2244         # os.times module computes the user time and system time spent by
  1991         # child's processes along with real elapsed time taken by a process.
  2245         # child's processes along with real elapsed time taken by a process.
  1992         # This module has one limitation. It can only work for Linux user
  2246         # This module has one limitation. It can only work for Linux user
  1993         # and not for Windows.
  2247         # and not for Windows.
  1994         test.started = os.times()
  2248         test.started = os.times()
  1995         if self._firststarttime is None: # thread racy but irrelevant
  2249         if self._firststarttime is None:  # thread racy but irrelevant
  1996             self._firststarttime = test.started[4]
  2250             self._firststarttime = test.started[4]
  1997 
  2251 
  1998     def stopTest(self, test, interrupted=False):
  2252     def stopTest(self, test, interrupted=False):
  1999         super(TestResult, self).stopTest(test)
  2253         super(TestResult, self).stopTest(test)
  2000 
  2254 
  2001         test.stopped = os.times()
  2255         test.stopped = os.times()
  2002 
  2256 
  2003         starttime = test.started
  2257         starttime = test.started
  2004         endtime = test.stopped
  2258         endtime = test.stopped
  2005         origin = self._firststarttime
  2259         origin = self._firststarttime
  2006         self.times.append((test.name,
  2260         self.times.append(
  2007                            endtime[2] - starttime[2], # user space CPU time
  2261             (
  2008                            endtime[3] - starttime[3], # sys  space CPU time
  2262                 test.name,
  2009                            endtime[4] - starttime[4], # real time
  2263                 endtime[2] - starttime[2],  # user space CPU time
  2010                            starttime[4] - origin, # start date in run context
  2264                 endtime[3] - starttime[3],  # sys  space CPU time
  2011                            endtime[4] - origin, # end date in run context
  2265                 endtime[4] - starttime[4],  # real time
  2012                            ))
  2266                 starttime[4] - origin,  # start date in run context
       
  2267                 endtime[4] - origin,  # end date in run context
       
  2268             )
       
  2269         )
  2013 
  2270 
  2014         if interrupted:
  2271         if interrupted:
  2015             with iolock:
  2272             with iolock:
  2016                 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
  2273                 self.stream.writeln(
  2017                     test.name, self.times[-1][3]))
  2274                     'INTERRUPTED: %s (after %d seconds)'
       
  2275                     % (test.name, self.times[-1][3])
       
  2276                 )
       
  2277 
  2018 
  2278 
  2019 def getTestResult():
  2279 def getTestResult():
  2020     """
  2280     """
  2021     Returns the relevant test result
  2281     Returns the relevant test result
  2022     """
  2282     """
  2024         testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
  2284         testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
  2025         return testresultmodule.TestResult
  2285         return testresultmodule.TestResult
  2026     else:
  2286     else:
  2027         return TestResult
  2287         return TestResult
  2028 
  2288 
       
  2289 
  2029 class TestSuite(unittest.TestSuite):
  2290 class TestSuite(unittest.TestSuite):
  2030     """Custom unittest TestSuite that knows how to execute Mercurial tests."""
  2291     """Custom unittest TestSuite that knows how to execute Mercurial tests."""
  2031 
  2292 
  2032     def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
  2293     def __init__(
  2033                  retest=False, keywords=None, loop=False, runs_per_test=1,
  2294         self,
  2034                  loadtest=None, showchannels=False,
  2295         testdir,
  2035                  *args, **kwargs):
  2296         jobs=1,
       
  2297         whitelist=None,
       
  2298         blacklist=None,
       
  2299         retest=False,
       
  2300         keywords=None,
       
  2301         loop=False,
       
  2302         runs_per_test=1,
       
  2303         loadtest=None,
       
  2304         showchannels=False,
       
  2305         *args,
       
  2306         **kwargs
       
  2307     ):
  2036         """Create a new instance that can run tests with a configuration.
  2308         """Create a new instance that can run tests with a configuration.
  2037 
  2309 
  2038         testdir specifies the directory where tests are executed from. This
  2310         testdir specifies the directory where tests are executed from. This
  2039         is typically the ``tests`` directory from Mercurial's source
  2311         is typically the ``tests`` directory from Mercurial's source
  2040         repository.
  2312         repository.
  2077         # here instead of inside Test because it makes the running logic for
  2349         # here instead of inside Test because it makes the running logic for
  2078         # Test simpler.
  2350         # Test simpler.
  2079         tests = []
  2351         tests = []
  2080         num_tests = [0]
  2352         num_tests = [0]
  2081         for test in self._tests:
  2353         for test in self._tests:
       
  2354 
  2082             def get():
  2355             def get():
  2083                 num_tests[0] += 1
  2356                 num_tests[0] += 1
  2084                 if getattr(test, 'should_reload', False):
  2357                 if getattr(test, 'should_reload', False):
  2085                     return self._loadtest(test, num_tests[0])
  2358                     return self._loadtest(test, num_tests[0])
  2086                 return test
  2359                 return test
       
  2360 
  2087             if not os.path.exists(test.path):
  2361             if not os.path.exists(test.path):
  2088                 result.addSkip(test, "Doesn't exist")
  2362                 result.addSkip(test, "Doesn't exist")
  2089                 continue
  2363                 continue
  2090 
  2364 
  2091             if not (self._whitelist and test.bname in self._whitelist):
  2365             if not (self._whitelist and test.bname in self._whitelist):
  2129             try:
  2403             try:
  2130                 test(result)
  2404                 test(result)
  2131                 done.put(None)
  2405                 done.put(None)
  2132             except KeyboardInterrupt:
  2406             except KeyboardInterrupt:
  2133                 pass
  2407                 pass
  2134             except: # re-raises
  2408             except:  # re-raises
  2135                 done.put(('!', test, 'run-test raised an error, see traceback'))
  2409                 done.put(('!', test, 'run-test raised an error, see traceback'))
  2136                 raise
  2410                 raise
  2137             finally:
  2411             finally:
  2138                 try:
  2412                 try:
  2139                     channels[channel] = ''
  2413                     channels[channel] = ''
  2154                 with iolock:
  2428                 with iolock:
  2155                     sys.stdout.write(d + '  ')
  2429                     sys.stdout.write(d + '  ')
  2156                     sys.stdout.flush()
  2430                     sys.stdout.flush()
  2157                 for x in xrange(10):
  2431                 for x in xrange(10):
  2158                     if channels:
  2432                     if channels:
  2159                         time.sleep(.1)
  2433                         time.sleep(0.1)
  2160                 count += 1
  2434                 count += 1
  2161 
  2435 
  2162         stoppedearly = False
  2436         stoppedearly = False
  2163 
  2437 
  2164         if self._showchannels:
  2438         if self._showchannels:
  2179                 if tests and not running == self._jobs:
  2453                 if tests and not running == self._jobs:
  2180                     test = tests.pop(0)
  2454                     test = tests.pop(0)
  2181                     if self._loop:
  2455                     if self._loop:
  2182                         if getattr(test, 'should_reload', False):
  2456                         if getattr(test, 'should_reload', False):
  2183                             num_tests[0] += 1
  2457                             num_tests[0] += 1
  2184                             tests.append(
  2458                             tests.append(self._loadtest(test, num_tests[0]))
  2185                                 self._loadtest(test, num_tests[0]))
       
  2186                         else:
  2459                         else:
  2187                             tests.append(test)
  2460                             tests.append(test)
  2188                     if self._jobs == 1:
  2461                     if self._jobs == 1:
  2189                         job(test, result)
  2462                         job(test, result)
  2190                     else:
  2463                     else:
  2191                         t = threading.Thread(target=job, name=test.name,
  2464                         t = threading.Thread(
  2192                                              args=(test, result))
  2465                             target=job, name=test.name, args=(test, result)
       
  2466                         )
  2193                         t.start()
  2467                         t.start()
  2194                     running += 1
  2468                     running += 1
  2195 
  2469 
  2196             # If we stop early we still need to wait on started tests to
  2470             # If we stop early we still need to wait on started tests to
  2197             # finish. Otherwise, there is a race between the test completing
  2471             # finish. Otherwise, there is a race between the test completing
  2210 
  2484 
  2211         channels = []
  2485         channels = []
  2212 
  2486 
  2213         return result
  2487         return result
  2214 
  2488 
       
  2489 
  2215 # Save the most recent 5 wall-clock runtimes of each test to a
  2490 # Save the most recent 5 wall-clock runtimes of each test to a
  2216 # human-readable text file named .testtimes. Tests are sorted
  2491 # human-readable text file named .testtimes. Tests are sorted
  2217 # alphabetically, while times for each test are listed from oldest to
  2492 # alphabetically, while times for each test are listed from oldest to
  2218 # newest.
  2493 # newest.
       
  2494 
  2219 
  2495 
  2220 def loadtimes(outputdir):
  2496 def loadtimes(outputdir):
  2221     times = []
  2497     times = []
  2222     try:
  2498     try:
  2223         with open(os.path.join(outputdir, b'.testtimes')) as fp:
  2499         with open(os.path.join(outputdir, b'.testtimes')) as fp:
  2224             for line in fp:
  2500             for line in fp:
  2225                 m = re.match('(.*?) ([0-9. ]+)', line)
  2501                 m = re.match('(.*?) ([0-9. ]+)', line)
  2226                 times.append((m.group(1),
  2502                 times.append(
  2227                               [float(t) for t in m.group(2).split()]))
  2503                     (m.group(1), [float(t) for t in m.group(2).split()])
       
  2504                 )
  2228     except IOError as err:
  2505     except IOError as err:
  2229         if err.errno != errno.ENOENT:
  2506         if err.errno != errno.ENOENT:
  2230             raise
  2507             raise
  2231     return times
  2508     return times
       
  2509 
  2232 
  2510 
  2233 def savetimes(outputdir, result):
  2511 def savetimes(outputdir, result):
  2234     saved = dict(loadtimes(outputdir))
  2512     saved = dict(loadtimes(outputdir))
  2235     maxruns = 5
  2513     maxruns = 5
  2236     skipped = set([str(t[0]) for t in result.skipped])
  2514     skipped = set([str(t[0]) for t in result.skipped])
  2239         if test not in skipped:
  2517         if test not in skipped:
  2240             ts = saved.setdefault(test, [])
  2518             ts = saved.setdefault(test, [])
  2241             ts.append(real)
  2519             ts.append(real)
  2242             ts[:] = ts[-maxruns:]
  2520             ts[:] = ts[-maxruns:]
  2243 
  2521 
  2244     fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
  2522     fd, tmpname = tempfile.mkstemp(
  2245                                    dir=outputdir, text=True)
  2523         prefix=b'.testtimes', dir=outputdir, text=True
       
  2524     )
  2246     with os.fdopen(fd, 'w') as fp:
  2525     with os.fdopen(fd, 'w') as fp:
  2247         for name, ts in sorted(saved.items()):
  2526         for name, ts in sorted(saved.items()):
  2248             fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
  2527             fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
  2249     timepath = os.path.join(outputdir, b'.testtimes')
  2528     timepath = os.path.join(outputdir, b'.testtimes')
  2250     try:
  2529     try:
  2254     try:
  2533     try:
  2255         os.rename(tmpname, timepath)
  2534         os.rename(tmpname, timepath)
  2256     except OSError:
  2535     except OSError:
  2257         pass
  2536         pass
  2258 
  2537 
       
  2538 
  2259 class TextTestRunner(unittest.TextTestRunner):
  2539 class TextTestRunner(unittest.TextTestRunner):
  2260     """Custom unittest test runner that uses appropriate settings."""
  2540     """Custom unittest test runner that uses appropriate settings."""
  2261 
  2541 
  2262     def __init__(self, runner, *args, **kwargs):
  2542     def __init__(self, runner, *args, **kwargs):
  2263         super(TextTestRunner, self).__init__(*args, **kwargs)
  2543         super(TextTestRunner, self).__init__(*args, **kwargs)
  2264 
  2544 
  2265         self._runner = runner
  2545         self._runner = runner
  2266 
  2546 
  2267         self._result = getTestResult()(self._runner.options, self.stream,
  2547         self._result = getTestResult()(
  2268                                        self.descriptions, self.verbosity)
  2548             self._runner.options, self.stream, self.descriptions, self.verbosity
       
  2549         )
  2269 
  2550 
  2270     def listtests(self, test):
  2551     def listtests(self, test):
  2271         test = sorted(test, key=lambda t: t.name)
  2552         test = sorted(test, key=lambda t: t.name)
  2272 
  2553 
  2273         self._result.onStart(test)
  2554         self._result.onStart(test)
  2297 
  2578 
  2298         with iolock:
  2579         with iolock:
  2299             self.stream.writeln('')
  2580             self.stream.writeln('')
  2300 
  2581 
  2301             if not self._runner.options.noskips:
  2582             if not self._runner.options.noskips:
  2302                 for test, msg in sorted(self._result.skipped,
  2583                 for test, msg in sorted(
  2303                                         key=lambda s: s[0].name):
  2584                     self._result.skipped, key=lambda s: s[0].name
       
  2585                 ):
  2304                     formatted = 'Skipped %s: %s\n' % (test.name, msg)
  2586                     formatted = 'Skipped %s: %s\n' % (test.name, msg)
  2305                     msg = highlightmsg(formatted, self._result.color)
  2587                     msg = highlightmsg(formatted, self._result.color)
  2306                     self.stream.write(msg)
  2588                     self.stream.write(msg)
  2307             for test, msg in sorted(self._result.failures,
  2589             for test, msg in sorted(
  2308                                     key=lambda f: f[0].name):
  2590                 self._result.failures, key=lambda f: f[0].name
       
  2591             ):
  2309                 formatted = 'Failed %s: %s\n' % (test.name, msg)
  2592                 formatted = 'Failed %s: %s\n' % (test.name, msg)
  2310                 self.stream.write(highlightmsg(formatted, self._result.color))
  2593                 self.stream.write(highlightmsg(formatted, self._result.color))
  2311             for test, msg in sorted(self._result.errors,
  2594             for test, msg in sorted(
  2312                                     key=lambda e: e[0].name):
  2595                 self._result.errors, key=lambda e: e[0].name
       
  2596             ):
  2313                 self.stream.writeln('Errored %s: %s' % (test.name, msg))
  2597                 self.stream.writeln('Errored %s: %s' % (test.name, msg))
  2314 
  2598 
  2315             if self._runner.options.xunit:
  2599             if self._runner.options.xunit:
  2316                 with open(self._runner.options.xunit, "wb") as xuf:
  2600                 with open(self._runner.options.xunit, "wb") as xuf:
  2317                     self._writexunit(self._result, xuf)
  2601                     self._writexunit(self._result, xuf)
  2327 
  2611 
  2328             if failed and self._runner.options.known_good_rev:
  2612             if failed and self._runner.options.known_good_rev:
  2329                 self._bisecttests(t for t, m in self._result.failures)
  2613                 self._bisecttests(t for t, m in self._result.failures)
  2330             self.stream.writeln(
  2614             self.stream.writeln(
  2331                 '# Ran %d tests, %d skipped, %d failed.'
  2615                 '# Ran %d tests, %d skipped, %d failed.'
  2332                 % (self._result.testsRun, skipped + ignored, failed))
  2616                 % (self._result.testsRun, skipped + ignored, failed)
       
  2617             )
  2333             if failed:
  2618             if failed:
  2334                 self.stream.writeln('python hash seed: %s' %
  2619                 self.stream.writeln(
  2335                     os.environ['PYTHONHASHSEED'])
  2620                     'python hash seed: %s' % os.environ['PYTHONHASHSEED']
       
  2621                 )
  2336             if self._runner.options.time:
  2622             if self._runner.options.time:
  2337                 self.printtimes(self._result.times)
  2623                 self.printtimes(self._result.times)
  2338 
  2624 
  2339             if self._runner.options.exceptions:
  2625             if self._runner.options.exceptions:
  2340                 exceptions = aggregateexceptions(
  2626                 exceptions = aggregateexceptions(
  2341                     os.path.join(self._runner._outputdir, b'exceptions'))
  2627                     os.path.join(self._runner._outputdir, b'exceptions')
       
  2628                 )
  2342 
  2629 
  2343                 self.stream.writeln('Exceptions Report:')
  2630                 self.stream.writeln('Exceptions Report:')
  2344                 self.stream.writeln('%d total from %d frames' %
  2631                 self.stream.writeln(
  2345                                     (exceptions['total'],
  2632                     '%d total from %d frames'
  2346                                      len(exceptions['exceptioncounts'])))
  2633                     % (exceptions['total'], len(exceptions['exceptioncounts']))
       
  2634                 )
  2347                 combined = exceptions['combined']
  2635                 combined = exceptions['combined']
  2348                 for key in sorted(combined, key=combined.get, reverse=True):
  2636                 for key in sorted(combined, key=combined.get, reverse=True):
  2349                     frame, line, exc = key
  2637                     frame, line, exc = key
  2350                     totalcount, testcount, leastcount, leasttest = combined[key]
  2638                     totalcount, testcount, leastcount, leasttest = combined[key]
  2351 
  2639 
  2352                     self.stream.writeln('%d (%d tests)\t%s: %s (%s - %d total)'
  2640                     self.stream.writeln(
  2353                                         % (totalcount,
  2641                         '%d (%d tests)\t%s: %s (%s - %d total)'
  2354                                            testcount,
  2642                         % (
  2355                                            frame, exc,
  2643                             totalcount,
  2356                                            leasttest, leastcount))
  2644                             testcount,
       
  2645                             frame,
       
  2646                             exc,
       
  2647                             leasttest,
       
  2648                             leastcount,
       
  2649                         )
       
  2650                     )
  2357 
  2651 
  2358             self.stream.flush()
  2652             self.stream.flush()
  2359 
  2653 
  2360         return self._result
  2654         return self._result
  2361 
  2655 
  2362     def _bisecttests(self, tests):
  2656     def _bisecttests(self, tests):
  2363         bisectcmd = ['hg', 'bisect']
  2657         bisectcmd = ['hg', 'bisect']
  2364         bisectrepo = self._runner.options.bisect_repo
  2658         bisectrepo = self._runner.options.bisect_repo
  2365         if bisectrepo:
  2659         if bisectrepo:
  2366             bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
  2660             bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
       
  2661 
  2367         def pread(args):
  2662         def pread(args):
  2368             env = os.environ.copy()
  2663             env = os.environ.copy()
  2369             env['HGPLAIN'] = '1'
  2664             env['HGPLAIN'] = '1'
  2370             p = subprocess.Popen(args, stderr=subprocess.STDOUT,
  2665             p = subprocess.Popen(
  2371                                  stdout=subprocess.PIPE, env=env)
  2666                 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
       
  2667             )
  2372             data = p.stdout.read()
  2668             data = p.stdout.read()
  2373             p.wait()
  2669             p.wait()
  2374             return data
  2670             return data
       
  2671 
  2375         for test in tests:
  2672         for test in tests:
  2376             pread(bisectcmd + ['--reset']),
  2673             pread(bisectcmd + ['--reset']),
  2377             pread(bisectcmd + ['--bad', '.'])
  2674             pread(bisectcmd + ['--bad', '.'])
  2378             pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
  2675             pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
  2379             # TODO: we probably need to forward more options
  2676             # TODO: we probably need to forward more options
  2380             # that alter hg's behavior inside the tests.
  2677             # that alter hg's behavior inside the tests.
  2381             opts = ''
  2678             opts = ''
  2382             withhg = self._runner.options.with_hg
  2679             withhg = self._runner.options.with_hg
  2383             if withhg:
  2680             if withhg:
  2384                 opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
  2681                 opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
  2385             rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts,
  2682             rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
  2386                                    test)
       
  2387             data = pread(bisectcmd + ['--command', rtc])
  2683             data = pread(bisectcmd + ['--command', rtc])
  2388             m = re.search(
  2684             m = re.search(
  2389                 (br'\nThe first (?P<goodbad>bad|good) revision '
  2685                 (
  2390                  br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
  2686                     br'\nThe first (?P<goodbad>bad|good) revision '
  2391                  br'summary: +(?P<summary>[^\n]+)\n'),
  2687                     br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
  2392                 data, (re.MULTILINE | re.DOTALL))
  2688                     br'summary: +(?P<summary>[^\n]+)\n'
       
  2689                 ),
       
  2690                 data,
       
  2691                 (re.MULTILINE | re.DOTALL),
       
  2692             )
  2393             if m is None:
  2693             if m is None:
  2394                 self.stream.writeln(
  2694                 self.stream.writeln(
  2395                     'Failed to identify failure point for %s' % test)
  2695                     'Failed to identify failure point for %s' % test
       
  2696                 )
  2396                 continue
  2697                 continue
  2397             dat = m.groupdict()
  2698             dat = m.groupdict()
  2398             verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
  2699             verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
  2399             self.stream.writeln(
  2700             self.stream.writeln(
  2400                 '%s %s by %s (%s)' % (
  2701                 '%s %s by %s (%s)'
  2401                     test, verb, dat['node'].decode('ascii'),
  2702                 % (
  2402                     dat['summary'].decode('utf8', 'ignore')))
  2703                     test,
       
  2704                     verb,
       
  2705                     dat['node'].decode('ascii'),
       
  2706                     dat['summary'].decode('utf8', 'ignore'),
       
  2707                 )
       
  2708             )
  2403 
  2709 
  2404     def printtimes(self, times):
  2710     def printtimes(self, times):
  2405         # iolock held by run
  2711         # iolock held by run
  2406         self.stream.writeln('# Producing time report')
  2712         self.stream.writeln('# Producing time report')
  2407         times.sort(key=lambda t: (t[3]))
  2713         times.sort(key=lambda t: (t[3]))
  2408         cols = '%7.3f %7.3f %7.3f %7.3f %7.3f   %s'
  2714         cols = '%7.3f %7.3f %7.3f %7.3f %7.3f   %s'
  2409         self.stream.writeln('%-7s %-7s %-7s %-7s %-7s   %s' %
  2715         self.stream.writeln(
  2410                             ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
  2716             '%-7s %-7s %-7s %-7s %-7s   %s'
       
  2717             % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
       
  2718         )
  2411         for tdata in times:
  2719         for tdata in times:
  2412             test = tdata[0]
  2720             test = tdata[0]
  2413             cuser, csys, real, start, end = tdata[1:6]
  2721             cuser, csys, real, start, end = tdata[1:6]
  2414             self.stream.writeln(cols % (start, end, cuser, csys, real, test))
  2722             self.stream.writeln(cols % (start, end, cuser, csys, real, test))
  2415 
  2723 
  2417     def _writexunit(result, outf):
  2725     def _writexunit(result, outf):
  2418         # See http://llg.cubic.org/docs/junit/ for a reference.
  2726         # See http://llg.cubic.org/docs/junit/ for a reference.
  2419         timesd = dict((t[0], t[3]) for t in result.times)
  2727         timesd = dict((t[0], t[3]) for t in result.times)
  2420         doc = minidom.Document()
  2728         doc = minidom.Document()
  2421         s = doc.createElement('testsuite')
  2729         s = doc.createElement('testsuite')
  2422         s.setAttribute('errors', "0") # TODO
  2730         s.setAttribute('errors', "0")  # TODO
  2423         s.setAttribute('failures', str(len(result.failures)))
  2731         s.setAttribute('failures', str(len(result.failures)))
  2424         s.setAttribute('name', 'run-tests')
  2732         s.setAttribute('name', 'run-tests')
  2425         s.setAttribute('skipped', str(len(result.skipped) +
  2733         s.setAttribute(
  2426                                       len(result.ignored)))
  2734             'skipped', str(len(result.skipped) + len(result.ignored))
       
  2735         )
  2427         s.setAttribute('tests', str(result.testsRun))
  2736         s.setAttribute('tests', str(result.testsRun))
  2428         doc.appendChild(s)
  2737         doc.appendChild(s)
  2429         for tc in result.successes:
  2738         for tc in result.successes:
  2430             t = doc.createElement('testcase')
  2739             t = doc.createElement('testcase')
  2431             t.setAttribute('name', tc.name)
  2740             t.setAttribute('name', tc.name)
  2472         for tdata in result.times:
  2781         for tdata in result.times:
  2473             test = tdata[0]
  2782             test = tdata[0]
  2474             timesd[test] = tdata[1:]
  2783             timesd[test] = tdata[1:]
  2475 
  2784 
  2476         outcome = {}
  2785         outcome = {}
  2477         groups = [('success', ((tc, None)
  2786         groups = [
  2478                    for tc in result.successes)),
  2787             ('success', ((tc, None) for tc in result.successes)),
  2479                   ('failure', result.failures),
  2788             ('failure', result.failures),
  2480                   ('skip', result.skipped)]
  2789             ('skip', result.skipped),
       
  2790         ]
  2481         for res, testcases in groups:
  2791         for res, testcases in groups:
  2482             for tc, __ in testcases:
  2792             for tc, __ in testcases:
  2483                 if tc.name in timesd:
  2793                 if tc.name in timesd:
  2484                     diff = result.faildata.get(tc.name, b'')
  2794                     diff = result.faildata.get(tc.name, b'')
  2485                     try:
  2795                     try:
  2486                         diff = diff.decode('unicode_escape')
  2796                         diff = diff.decode('unicode_escape')
  2487                     except UnicodeDecodeError as e:
  2797                     except UnicodeDecodeError as e:
  2488                         diff = '%r decoding diff, sorry' % e
  2798                         diff = '%r decoding diff, sorry' % e
  2489                     tres = {'result': res,
  2799                     tres = {
  2490                             'time': ('%0.3f' % timesd[tc.name][2]),
  2800                         'result': res,
  2491                             'cuser': ('%0.3f' % timesd[tc.name][0]),
  2801                         'time': ('%0.3f' % timesd[tc.name][2]),
  2492                             'csys': ('%0.3f' % timesd[tc.name][1]),
  2802                         'cuser': ('%0.3f' % timesd[tc.name][0]),
  2493                             'start': ('%0.3f' % timesd[tc.name][3]),
  2803                         'csys': ('%0.3f' % timesd[tc.name][1]),
  2494                             'end': ('%0.3f' % timesd[tc.name][4]),
  2804                         'start': ('%0.3f' % timesd[tc.name][3]),
  2495                             'diff': diff,
  2805                         'end': ('%0.3f' % timesd[tc.name][4]),
  2496                             }
  2806                         'diff': diff,
       
  2807                     }
  2497                 else:
  2808                 else:
  2498                     # blacklisted test
  2809                     # blacklisted test
  2499                     tres = {'result': res}
  2810                     tres = {'result': res}
  2500 
  2811 
  2501                 outcome[tc.name] = tres
  2812                 outcome[tc.name] = tres
  2502         jsonout = json.dumps(outcome, sort_keys=True, indent=4,
  2813         jsonout = json.dumps(
  2503                              separators=(',', ': '))
  2814             outcome, sort_keys=True, indent=4, separators=(',', ': ')
       
  2815         )
  2504         outf.writelines(("testreport =", jsonout))
  2816         outf.writelines(("testreport =", jsonout))
       
  2817 
  2505 
  2818 
  2506 def sorttests(testdescs, previoustimes, shuffle=False):
  2819 def sorttests(testdescs, previoustimes, shuffle=False):
  2507     """Do an in-place sort of tests."""
  2820     """Do an in-place sort of tests."""
  2508     if shuffle:
  2821     if shuffle:
  2509         random.shuffle(testdescs)
  2822         random.shuffle(testdescs)
  2510         return
  2823         return
  2511 
  2824 
  2512     if previoustimes:
  2825     if previoustimes:
       
  2826 
  2513         def sortkey(f):
  2827         def sortkey(f):
  2514             f = f['path']
  2828             f = f['path']
  2515             if f in previoustimes:
  2829             if f in previoustimes:
  2516                 # Use most recent time as estimate
  2830                 # Use most recent time as estimate
  2517                 return -previoustimes[f][-1]
  2831                 return -(previoustimes[f][-1])
  2518             else:
  2832             else:
  2519                 # Default to a rather arbitrary value of 1 second for new tests
  2833                 # Default to a rather arbitrary value of 1 second for new tests
  2520                 return -1.0
  2834                 return -1.0
       
  2835 
  2521     else:
  2836     else:
  2522         # keywords for slow tests
  2837         # keywords for slow tests
  2523         slow = {b'svn': 10,
  2838         slow = {
  2524                 b'cvs': 10,
  2839             b'svn': 10,
  2525                 b'hghave': 10,
  2840             b'cvs': 10,
  2526                 b'largefiles-update': 10,
  2841             b'hghave': 10,
  2527                 b'run-tests': 10,
  2842             b'largefiles-update': 10,
  2528                 b'corruption': 10,
  2843             b'run-tests': 10,
  2529                 b'race': 10,
  2844             b'corruption': 10,
  2530                 b'i18n': 10,
  2845             b'race': 10,
  2531                 b'check': 100,
  2846             b'i18n': 10,
  2532                 b'gendoc': 100,
  2847             b'check': 100,
  2533                 b'contrib-perf': 200,
  2848             b'gendoc': 100,
  2534                 b'merge-combination': 100,
  2849             b'contrib-perf': 200,
  2535                 }
  2850             b'merge-combination': 100,
       
  2851         }
  2536         perf = {}
  2852         perf = {}
  2537 
  2853 
  2538         def sortkey(f):
  2854         def sortkey(f):
  2539             # run largest tests first, as they tend to take the longest
  2855             # run largest tests first, as they tend to take the longest
  2540             f = f['path']
  2856             f = f['path']
  2556                 perf[f] = val / 1000.0
  2872                 perf[f] = val / 1000.0
  2557                 return perf[f]
  2873                 return perf[f]
  2558 
  2874 
  2559     testdescs.sort(key=sortkey)
  2875     testdescs.sort(key=sortkey)
  2560 
  2876 
       
  2877 
  2561 class TestRunner(object):
  2878 class TestRunner(object):
  2562     """Holds context for executing tests.
  2879     """Holds context for executing tests.
  2563 
  2880 
  2564     Tests rely on a lot of state. This object holds it for them.
  2881     Tests rely on a lot of state. This object holds it for them.
  2565     """
  2882     """
  2612 
  2929 
  2613             self._checktools()
  2930             self._checktools()
  2614             testdescs = self.findtests(tests)
  2931             testdescs = self.findtests(tests)
  2615             if options.profile_runner:
  2932             if options.profile_runner:
  2616                 import statprof
  2933                 import statprof
       
  2934 
  2617                 statprof.start()
  2935                 statprof.start()
  2618             result = self._run(testdescs)
  2936             result = self._run(testdescs)
  2619             if options.profile_runner:
  2937             if options.profile_runner:
  2620                 statprof.stop()
  2938                 statprof.stop()
  2621                 statprof.display()
  2939                 statprof.display()
  2666                 # without this, we get the default temp dir location, but
  2984                 # without this, we get the default temp dir location, but
  2667                 # in all lowercase, which causes troubles with paths (issue3490)
  2985                 # in all lowercase, which causes troubles with paths (issue3490)
  2668                 d = osenvironb.get(b'TMP', None)
  2986                 d = osenvironb.get(b'TMP', None)
  2669             tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
  2987             tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
  2670 
  2988 
  2671         self._hgtmp = osenvironb[b'HGTMP'] = (
  2989         self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
  2672             os.path.realpath(tmpdir))
       
  2673 
  2990 
  2674         if self.options.with_hg:
  2991         if self.options.with_hg:
  2675             self._installdir = None
  2992             self._installdir = None
  2676             whg = self.options.with_hg
  2993             whg = self.options.with_hg
  2677             self._bindir = os.path.dirname(os.path.realpath(whg))
  2994             self._bindir = os.path.dirname(os.path.realpath(whg))
  2789                 os.unlink(os.path.join(exceptionsdir, f))
  3106                 os.unlink(os.path.join(exceptionsdir, f))
  2790 
  3107 
  2791             osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
  3108             osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
  2792             logexceptions = os.path.join(self._testdir, b'logexceptions.py')
  3109             logexceptions = os.path.join(self._testdir, b'logexceptions.py')
  2793             self.options.extra_config_opt.append(
  3110             self.options.extra_config_opt.append(
  2794                 'extensions.logexceptions=%s' % logexceptions.decode('utf-8'))
  3111                 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
       
  3112             )
  2795 
  3113 
  2796         vlog("# Using TESTDIR", self._testdir)
  3114         vlog("# Using TESTDIR", self._testdir)
  2797         vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
  3115         vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
  2798         vlog("# Using HGTMP", self._hgtmp)
  3116         vlog("# Using HGTMP", self._hgtmp)
  2799         vlog("# Using PATH", os.environ["PATH"])
  3117         vlog("# Using PATH", os.environ["PATH"])
  2801         vlog("# Writing to directory", self._outputdir)
  3119         vlog("# Writing to directory", self._outputdir)
  2802 
  3120 
  2803         try:
  3121         try:
  2804             return self._runtests(testdescs) or 0
  3122             return self._runtests(testdescs) or 0
  2805         finally:
  3123         finally:
  2806             time.sleep(.1)
  3124             time.sleep(0.1)
  2807             self._cleanup()
  3125             self._cleanup()
  2808 
  3126 
  2809     def findtests(self, args):
  3127     def findtests(self, args):
  2810         """Finds possible test files from arguments.
  3128         """Finds possible test files from arguments.
  2811 
  3129 
  2812         If you wish to inject custom tests into the test harness, this would
  3130         If you wish to inject custom tests into the test harness, this would
  2813         be a good function to monkeypatch or override in a derived class.
  3131         be a good function to monkeypatch or override in a derived class.
  2814         """
  3132         """
  2815         if not args:
  3133         if not args:
  2816             if self.options.changed:
  3134             if self.options.changed:
  2817                 proc = Popen4(b'hg st --rev "%s" -man0 .' %
  3135                 proc = Popen4(
  2818                               _bytespath(self.options.changed), None, 0)
  3136                     b'hg st --rev "%s" -man0 .'
       
  3137                     % _bytespath(self.options.changed),
       
  3138                     None,
       
  3139                     0,
       
  3140                 )
  2819                 stdout, stderr = proc.communicate()
  3141                 stdout, stderr = proc.communicate()
  2820                 args = stdout.strip(b'\0').split(b'\0')
  3142                 args = stdout.strip(b'\0').split(b'\0')
  2821             else:
  3143             else:
  2822                 args = os.listdir(b'.')
  3144                 args = os.listdir(b'.')
  2823 
  3145 
  2830             else:
  3152             else:
  2831                 expanded_args.append(arg)
  3153                 expanded_args.append(arg)
  2832         args = expanded_args
  3154         args = expanded_args
  2833 
  3155 
  2834         testcasepattern = re.compile(
  3156         testcasepattern = re.compile(
  2835             br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-\.#]+))')
  3157             br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-\.#]+))'
       
  3158         )
  2836         tests = []
  3159         tests = []
  2837         for t in args:
  3160         for t in args:
  2838             case = []
  3161             case = []
  2839 
  3162 
  2840             if not (os.path.basename(t).startswith(b'test-')
  3163             if not (
  2841                     and (t.endswith(b'.py') or t.endswith(b'.t'))):
  3164                 os.path.basename(t).startswith(b'test-')
       
  3165                 and (t.endswith(b'.py') or t.endswith(b'.t'))
       
  3166             ):
  2842 
  3167 
  2843                 m = testcasepattern.match(os.path.basename(t))
  3168                 m = testcasepattern.match(os.path.basename(t))
  2844                 if m is not None:
  3169                 if m is not None:
  2845                     t_basename, casestr = m.groups()
  3170                     t_basename, casestr = m.groups()
  2846                     t = os.path.join(os.path.dirname(t), t_basename)
  3171                     t = os.path.join(os.path.dirname(t), t_basename)
  2852             if t.endswith(b'.t'):
  3177             if t.endswith(b'.t'):
  2853                 # .t file may contain multiple test cases
  3178                 # .t file may contain multiple test cases
  2854                 casedimensions = parsettestcases(t)
  3179                 casedimensions = parsettestcases(t)
  2855                 if casedimensions:
  3180                 if casedimensions:
  2856                     cases = []
  3181                     cases = []
       
  3182 
  2857                     def addcases(case, casedimensions):
  3183                     def addcases(case, casedimensions):
  2858                         if not casedimensions:
  3184                         if not casedimensions:
  2859                             cases.append(case)
  3185                             cases.append(case)
  2860                         else:
  3186                         else:
  2861                             for c in casedimensions[0]:
  3187                             for c in casedimensions[0]:
  2862                                 addcases(case + [c], casedimensions[1:])
  3188                                 addcases(case + [c], casedimensions[1:])
       
  3189 
  2863                     addcases([], casedimensions)
  3190                     addcases([], casedimensions)
  2864                     if case and case in cases:
  3191                     if case and case in cases:
  2865                         cases = [case]
  3192                         cases = [case]
  2866                     elif case:
  3193                     elif case:
  2867                         # Ignore invalid cases
  3194                         # Ignore invalid cases
  2911             failed = False
  3238             failed = False
  2912             kws = self.options.keywords
  3239             kws = self.options.keywords
  2913             if kws is not None and PYTHON3:
  3240             if kws is not None and PYTHON3:
  2914                 kws = kws.encode('utf-8')
  3241                 kws = kws.encode('utf-8')
  2915 
  3242 
  2916             suite = TestSuite(self._testdir,
  3243             suite = TestSuite(
  2917                               jobs=jobs,
  3244                 self._testdir,
  2918                               whitelist=self.options.whitelisted,
  3245                 jobs=jobs,
  2919                               blacklist=self.options.blacklist,
  3246                 whitelist=self.options.whitelisted,
  2920                               retest=self.options.retest,
  3247                 blacklist=self.options.blacklist,
  2921                               keywords=kws,
  3248                 retest=self.options.retest,
  2922                               loop=self.options.loop,
  3249                 keywords=kws,
  2923                               runs_per_test=self.options.runs_per_test,
  3250                 loop=self.options.loop,
  2924                               showchannels=self.options.showchannels,
  3251                 runs_per_test=self.options.runs_per_test,
  2925                               tests=tests, loadtest=_reloadtest)
  3252                 showchannels=self.options.showchannels,
       
  3253                 tests=tests,
       
  3254                 loadtest=_reloadtest,
       
  3255             )
  2926             verbosity = 1
  3256             verbosity = 1
  2927             if self.options.list_tests:
  3257             if self.options.list_tests:
  2928                 verbosity = 0
  3258                 verbosity = 0
  2929             elif self.options.verbose:
  3259             elif self.options.verbose:
  2930                 verbosity = 2
  3260                 verbosity = 2
  2940                     self._usecorrectpython()
  3270                     self._usecorrectpython()
  2941                 if self.options.chg:
  3271                 if self.options.chg:
  2942                     assert self._installdir
  3272                     assert self._installdir
  2943                     self._installchg()
  3273                     self._installchg()
  2944 
  3274 
  2945                 log('running %d tests using %d parallel processes' % (
  3275                 log(
  2946                     num_tests, jobs))
  3276                     'running %d tests using %d parallel processes'
       
  3277                     % (num_tests, jobs)
       
  3278                 )
  2947 
  3279 
  2948                 result = runner.run(suite)
  3280                 result = runner.run(suite)
  2949 
  3281 
  2950             if result.failures or result.errors:
  3282             if result.failures or result.errors:
  2951                 failed = True
  3283                 failed = True
  2960 
  3292 
  2961         if failed:
  3293         if failed:
  2962             return 1
  3294             return 1
  2963 
  3295 
  2964     def _getport(self, count):
  3296     def _getport(self, count):
  2965         port = self._ports.get(count) # do we have a cached entry?
  3297         port = self._ports.get(count)  # do we have a cached entry?
  2966         if port is None:
  3298         if port is None:
  2967             portneeded = 3
  3299             portneeded = 3
  2968             # above 100 tries we just give up and let test reports failure
  3300             # above 100 tries we just give up and let test reports failure
  2969             for tries in xrange(100):
  3301             for tries in xrange(100):
  2970                 allfree = True
  3302                 allfree = True
  2998         tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
  3330         tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
  2999 
  3331 
  3000         # extra keyword parameters. 'case' is used by .t tests
  3332         # extra keyword parameters. 'case' is used by .t tests
  3001         kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
  3333         kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
  3002 
  3334 
  3003         t = testcls(refpath, self._outputdir, tmpdir,
  3335         t = testcls(
  3004                     keeptmpdir=self.options.keep_tmpdir,
  3336             refpath,
  3005                     debug=self.options.debug,
  3337             self._outputdir,
  3006                     first=self.options.first,
  3338             tmpdir,
  3007                     timeout=self.options.timeout,
  3339             keeptmpdir=self.options.keep_tmpdir,
  3008                     startport=self._getport(count),
  3340             debug=self.options.debug,
  3009                     extraconfigopts=self.options.extra_config_opt,
  3341             first=self.options.first,
  3010                     py3warnings=self.options.py3_warnings,
  3342             timeout=self.options.timeout,
  3011                     shell=self.options.shell,
  3343             startport=self._getport(count),
  3012                     hgcommand=self._hgcommand,
  3344             extraconfigopts=self.options.extra_config_opt,
  3013                     usechg=bool(self.options.with_chg or self.options.chg),
  3345             py3warnings=self.options.py3_warnings,
  3014                     useipv6=useipv6, **kwds)
  3346             shell=self.options.shell,
       
  3347             hgcommand=self._hgcommand,
       
  3348             usechg=bool(self.options.with_chg or self.options.chg),
       
  3349             useipv6=useipv6,
       
  3350             **kwds
       
  3351         )
  3015         t.should_reload = True
  3352         t.should_reload = True
  3016         return t
  3353         return t
  3017 
  3354 
  3018     def _cleanup(self):
  3355     def _cleanup(self):
  3019         """Clean up state from this test invocation."""
  3356         """Clean up state from this test invocation."""
  3034         pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
  3371         pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
  3035 
  3372 
  3036         # os.symlink() is a thing with py3 on Windows, but it requires
  3373         # os.symlink() is a thing with py3 on Windows, but it requires
  3037         # Administrator rights.
  3374         # Administrator rights.
  3038         if getattr(os, 'symlink', None) and os.name != 'nt':
  3375         if getattr(os, 'symlink', None) and os.name != 'nt':
  3039             vlog("# Making python executable in test path a symlink to '%s'" %
  3376             vlog(
  3040                  sysexecutable)
  3377                 "# Making python executable in test path a symlink to '%s'"
       
  3378                 % sysexecutable
       
  3379             )
  3041             mypython = os.path.join(self._tmpbindir, pyexename)
  3380             mypython = os.path.join(self._tmpbindir, pyexename)
  3042             try:
  3381             try:
  3043                 if os.readlink(mypython) == sysexecutable:
  3382                 if os.readlink(mypython) == sysexecutable:
  3044                     return
  3383                     return
  3045                 os.unlink(mypython)
  3384                 os.unlink(mypython)
  3054                     # child processes may race, which is harmless
  3393                     # child processes may race, which is harmless
  3055                     if err.errno != errno.EEXIST:
  3394                     if err.errno != errno.EEXIST:
  3056                         raise
  3395                         raise
  3057         else:
  3396         else:
  3058             exedir, exename = os.path.split(sysexecutable)
  3397             exedir, exename = os.path.split(sysexecutable)
  3059             vlog("# Modifying search path to find %s as %s in '%s'" %
  3398             vlog(
  3060                  (exename, pyexename, exedir))
  3399                 "# Modifying search path to find %s as %s in '%s'"
       
  3400                 % (exename, pyexename, exedir)
       
  3401             )
  3061             path = os.environ['PATH'].split(os.pathsep)
  3402             path = os.environ['PATH'].split(os.pathsep)
  3062             while exedir in path:
  3403             while exedir in path:
  3063                 path.remove(exedir)
  3404                 path.remove(exedir)
  3064             os.environ['PATH'] = os.pathsep.join([exedir] + path)
  3405             os.environ['PATH'] = os.pathsep.join([exedir] + path)
  3065             if not self._findprogram(pyexename):
  3406             if not self._findprogram(pyexename):
  3095             # The --home="" trick works only on OS where os.sep == '/'
  3436             # The --home="" trick works only on OS where os.sep == '/'
  3096             # because of a distutils convert_path() fast-path. Avoid it at
  3437             # because of a distutils convert_path() fast-path. Avoid it at
  3097             # least on Windows for now, deal with .pydistutils.cfg bugs
  3438             # least on Windows for now, deal with .pydistutils.cfg bugs
  3098             # when they happen.
  3439             # when they happen.
  3099             nohome = b''
  3440             nohome = b''
  3100         cmd = (b'"%(exe)s" setup.py %(pure)s clean --all'
  3441         cmd = (
  3101                b' build %(compiler)s --build-base="%(base)s"'
  3442             b'"%(exe)s" setup.py %(pure)s clean --all'
  3102                b' install --force --prefix="%(prefix)s"'
  3443             b' build %(compiler)s --build-base="%(base)s"'
  3103                b' --install-lib="%(libdir)s"'
  3444             b' install --force --prefix="%(prefix)s"'
  3104                b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
  3445             b' --install-lib="%(libdir)s"'
  3105                % {b'exe': exe, b'pure': pure,
  3446             b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
  3106                   b'compiler': compiler,
  3447             % {
  3107                   b'base': os.path.join(self._hgtmp, b"build"),
  3448                 b'exe': exe,
  3108                   b'prefix': self._installdir, b'libdir': self._pythondir,
  3449                 b'pure': pure,
  3109                   b'bindir': self._bindir,
  3450                 b'compiler': compiler,
  3110                   b'nohome': nohome, b'logfile': installerrs})
  3451                 b'base': os.path.join(self._hgtmp, b"build"),
       
  3452                 b'prefix': self._installdir,
       
  3453                 b'libdir': self._pythondir,
       
  3454                 b'bindir': self._bindir,
       
  3455                 b'nohome': nohome,
       
  3456                 b'logfile': installerrs,
       
  3457             }
       
  3458         )
  3111 
  3459 
  3112         # setuptools requires install directories to exist.
  3460         # setuptools requires install directories to exist.
  3113         def makedirs(p):
  3461         def makedirs(p):
  3114             try:
  3462             try:
  3115                 os.makedirs(p)
  3463                 os.makedirs(p)
  3116             except OSError as e:
  3464             except OSError as e:
  3117                 if e.errno != errno.EEXIST:
  3465                 if e.errno != errno.EEXIST:
  3118                     raise
  3466                     raise
       
  3467 
  3119         makedirs(self._pythondir)
  3468         makedirs(self._pythondir)
  3120         makedirs(self._bindir)
  3469         makedirs(self._bindir)
  3121 
  3470 
  3122         vlog("# Running", cmd)
  3471         vlog("# Running", cmd)
  3123         if subprocess.call(_strpath(cmd), shell=True) == 0:
  3472         if subprocess.call(_strpath(cmd), shell=True) == 0:
  3153             # hg.bat expects to be put in bin/scripts while run-tests.py
  3502             # hg.bat expects to be put in bin/scripts while run-tests.py
  3154             # installation layout put it in bin/ directly. Fix it
  3503             # installation layout put it in bin/ directly. Fix it
  3155             with open(hgbat, 'rb') as f:
  3504             with open(hgbat, 'rb') as f:
  3156                 data = f.read()
  3505                 data = f.read()
  3157             if br'"%~dp0..\python" "%~dp0hg" %*' in data:
  3506             if br'"%~dp0..\python" "%~dp0hg" %*' in data:
  3158                 data = data.replace(br'"%~dp0..\python" "%~dp0hg" %*',
  3507                 data = data.replace(
  3159                                     b'"%~dp0python" "%~dp0hg" %*')
  3508                     br'"%~dp0..\python" "%~dp0hg" %*',
       
  3509                     b'"%~dp0python" "%~dp0hg" %*',
       
  3510                 )
  3160                 with open(hgbat, 'wb') as f:
  3511                 with open(hgbat, 'wb') as f:
  3161                     f.write(data)
  3512                     f.write(data)
  3162             else:
  3513             else:
  3163                 print('WARNING: cannot fix hg.bat reference to python.exe')
  3514                 print('WARNING: cannot fix hg.bat reference to python.exe')
  3164 
  3515 
  3180             os.environ['COVERAGE_DIR'] = covdir
  3531             os.environ['COVERAGE_DIR'] = covdir
  3181 
  3532 
  3182     def _checkhglib(self, verb):
  3533     def _checkhglib(self, verb):
  3183         """Ensure that the 'mercurial' package imported by python is
  3534         """Ensure that the 'mercurial' package imported by python is
  3184         the one we expect it to be.  If not, print a warning to stderr."""
  3535         the one we expect it to be.  If not, print a warning to stderr."""
  3185         if ((self._bindir == self._pythondir) and
  3536         if (self._bindir == self._pythondir) and (
  3186             (self._bindir != self._tmpbindir)):
  3537             self._bindir != self._tmpbindir
       
  3538         ):
  3187             # The pythondir has been inferred from --with-hg flag.
  3539             # The pythondir has been inferred from --with-hg flag.
  3188             # We cannot expect anything sensible here.
  3540             # We cannot expect anything sensible here.
  3189             return
  3541             return
  3190         expecthg = os.path.join(self._pythondir, b'mercurial')
  3542         expecthg = os.path.join(self._pythondir, b'mercurial')
  3191         actualhg = self._gethgpath()
  3543         actualhg = self._gethgpath()
  3192         if os.path.abspath(actualhg) != os.path.abspath(expecthg):
  3544         if os.path.abspath(actualhg) != os.path.abspath(expecthg):
  3193             sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
  3545             sys.stderr.write(
  3194                              '         (expected %s)\n'
  3546                 'warning: %s with unexpected mercurial lib: %s\n'
  3195                              % (verb, actualhg, expecthg))
  3547                 '         (expected %s)\n' % (verb, actualhg, expecthg)
       
  3548             )
       
  3549 
  3196     def _gethgpath(self):
  3550     def _gethgpath(self):
  3197         """Return the path to the mercurial package that is actually found by
  3551         """Return the path to the mercurial package that is actually found by
  3198         the current Python interpreter."""
  3552         the current Python interpreter."""
  3199         if self._hgpath is not None:
  3553         if self._hgpath is not None:
  3200             return self._hgpath
  3554             return self._hgpath
  3214     def _installchg(self):
  3568     def _installchg(self):
  3215         """Install chg into the test environment"""
  3569         """Install chg into the test environment"""
  3216         vlog('# Performing temporary installation of CHG')
  3570         vlog('# Performing temporary installation of CHG')
  3217         assert os.path.dirname(self._bindir) == self._installdir
  3571         assert os.path.dirname(self._bindir) == self._installdir
  3218         assert self._hgroot, 'must be called after _installhg()'
  3572         assert self._hgroot, 'must be called after _installhg()'
  3219         cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
  3573         cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
  3220                % {b'make': b'make',  # TODO: switch by option or environment?
  3574             b'make': b'make',  # TODO: switch by option or environment?
  3221                   b'prefix': self._installdir})
  3575             b'prefix': self._installdir,
       
  3576         }
  3222         cwd = os.path.join(self._hgroot, b'contrib', b'chg')
  3577         cwd = os.path.join(self._hgroot, b'contrib', b'chg')
  3223         vlog("# Running", cmd)
  3578         vlog("# Running", cmd)
  3224         proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
  3579         proc = subprocess.Popen(
  3225                                 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
  3580             cmd,
  3226                                 stderr=subprocess.STDOUT)
  3581             shell=True,
       
  3582             cwd=cwd,
       
  3583             stdin=subprocess.PIPE,
       
  3584             stdout=subprocess.PIPE,
       
  3585             stderr=subprocess.STDOUT,
       
  3586         )
  3227         out, _err = proc.communicate()
  3587         out, _err = proc.communicate()
  3228         if proc.returncode != 0:
  3588         if proc.returncode != 0:
  3229             if PYTHON3:
  3589             if PYTHON3:
  3230                 sys.stdout.buffer.write(out)
  3590                 sys.stdout.buffer.write(out)
  3231             else:
  3591             else:
  3233             sys.exit(1)
  3593             sys.exit(1)
  3234 
  3594 
  3235     def _outputcoverage(self):
  3595     def _outputcoverage(self):
  3236         """Produce code coverage output."""
  3596         """Produce code coverage output."""
  3237         import coverage
  3597         import coverage
       
  3598 
  3238         coverage = coverage.coverage
  3599         coverage = coverage.coverage
  3239 
  3600 
  3240         vlog('# Producing coverage report')
  3601         vlog('# Producing coverage report')
  3241         # chdir is the easiest way to get short, relative paths in the
  3602         # chdir is the easiest way to get short, relative paths in the
  3242         # output.
  3603         # output.
  3278                 p += b'.exe'
  3639                 p += b'.exe'
  3279             found = self._findprogram(p)
  3640             found = self._findprogram(p)
  3280             if found:
  3641             if found:
  3281                 vlog("# Found prerequisite", p, "at", found)
  3642                 vlog("# Found prerequisite", p, "at", found)
  3282             else:
  3643             else:
  3283                 print("WARNING: Did not find prerequisite tool: %s " %
  3644                 print(
  3284                       p.decode("utf-8"))
  3645                     "WARNING: Did not find prerequisite tool: %s "
       
  3646                     % p.decode("utf-8")
       
  3647                 )
       
  3648 
  3285 
  3649 
  3286 def aggregateexceptions(path):
  3650 def aggregateexceptions(path):
  3287     exceptioncounts = collections.Counter()
  3651     exceptioncounts = collections.Counter()
  3288     testsbyfailure = collections.defaultdict(set)
  3652     testsbyfailure = collections.defaultdict(set)
  3289     failuresbytest = collections.defaultdict(set)
  3653     failuresbytest = collections.defaultdict(set)
  3320 
  3684 
  3321     # Create a combined counter so we can sort by total occurrences and
  3685     # Create a combined counter so we can sort by total occurrences and
  3322     # impacted tests.
  3686     # impacted tests.
  3323     combined = {}
  3687     combined = {}
  3324     for key in exceptioncounts:
  3688     for key in exceptioncounts:
  3325         combined[key] = (exceptioncounts[key],
  3689         combined[key] = (
  3326                          len(testsbyfailure[key]),
  3690             exceptioncounts[key],
  3327                          leastfailing[key][0],
  3691             len(testsbyfailure[key]),
  3328                          leastfailing[key][1])
  3692             leastfailing[key][0],
       
  3693             leastfailing[key][1],
       
  3694         )
  3329 
  3695 
  3330     return {
  3696     return {
  3331         'exceptioncounts': exceptioncounts,
  3697         'exceptioncounts': exceptioncounts,
  3332         'total': sum(exceptioncounts.values()),
  3698         'total': sum(exceptioncounts.values()),
  3333         'combined': combined,
  3699         'combined': combined,
  3334         'leastfailing': leastfailing,
  3700         'leastfailing': leastfailing,
  3335         'byfailure': testsbyfailure,
  3701         'byfailure': testsbyfailure,
  3336         'bytest': failuresbytest,
  3702         'bytest': failuresbytest,
  3337     }
  3703     }
  3338 
  3704 
       
  3705 
  3339 if __name__ == '__main__':
  3706 if __name__ == '__main__':
  3340     runner = TestRunner()
  3707     runner = TestRunner()
  3341 
  3708 
  3342     try:
  3709     try:
  3343         import msvcrt
  3710         import msvcrt
       
  3711 
  3344         msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
  3712         msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
  3345         msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
  3713         msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
  3346         msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
  3714         msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
  3347     except ImportError:
  3715     except ImportError:
  3348         pass
  3716         pass