tests/run-tests.py
changeset 21520 fa6ba4372678
parent 21518 8e8049b9bda4
child 21521 855f092c96e5
equal deleted inserted replaced
21519:25d5a9ecbb85 21520:fa6ba4372678
   336     """
   336     """
   337 
   337 
   338     # Status code reserved for skipped tests (used by hghave).
   338     # Status code reserved for skipped tests (used by hghave).
   339     SKIPPED_STATUS = 80
   339     SKIPPED_STATUS = 80
   340 
   340 
   341     def __init__(self, path, tmpdir, abort, keeptmpdir=False,
   341     def __init__(self, path, tmpdir, keeptmpdir=False,
   342                  debug=False, nodiff=False, diffviewer=None,
   342                  debug=False, nodiff=False, diffviewer=None,
   343                  interactive=False, timeout=defaults['timeout'],
   343                  interactive=False, timeout=defaults['timeout'],
   344                  startport=defaults['port'], extraconfigopts=None,
   344                  startport=defaults['port'], extraconfigopts=None,
   345                  py3kwarnings=False, shell=None):
   345                  py3kwarnings=False, shell=None):
   346         """Create a test from parameters.
   346         """Create a test from parameters.
   347 
   347 
   348         path is the full path to the file defining the test.
   348         path is the full path to the file defining the test.
   349 
   349 
   350         tmpdir is the main temporary directory to use for this test.
   350         tmpdir is the main temporary directory to use for this test.
   351 
   351 
   352         abort is a flag that turns to True if test execution should be aborted.
       
   353         It is consulted periodically during the execution of tests.
       
   354 
       
   355         keeptmpdir determines whether to keep the test's temporary directory
   352         keeptmpdir determines whether to keep the test's temporary directory
   356         after execution. It defaults to removal (False).
   353         after execution. It defaults to removal (False).
   357 
   354 
   358         debug mode will make the test execute verbosely, with unfiltered
   355         debug mode will make the test execute verbosely, with unfiltered
   359         output.
   356         output.
   386         self.name = os.path.basename(path)
   383         self.name = os.path.basename(path)
   387         self._testdir = os.path.dirname(path)
   384         self._testdir = os.path.dirname(path)
   388         self.errpath = os.path.join(self._testdir, '%s.err' % self.name)
   385         self.errpath = os.path.join(self._testdir, '%s.err' % self.name)
   389 
   386 
   390         self._threadtmp = tmpdir
   387         self._threadtmp = tmpdir
   391         self._abort = abort
       
   392         self._keeptmpdir = keeptmpdir
   388         self._keeptmpdir = keeptmpdir
   393         self._debug = debug
   389         self._debug = debug
   394         self._nodiff = nodiff
   390         self._nodiff = nodiff
   395         self._diffviewer = diffviewer
   391         self._diffviewer = diffviewer
   396         self._interactive = interactive
   392         self._interactive = interactive
   397         self._timeout = timeout
   393         self._timeout = timeout
   398         self._startport = startport
   394         self._startport = startport
   399         self._extraconfigopts = extraconfigopts or []
   395         self._extraconfigopts = extraconfigopts or []
   400         self._py3kwarnings = py3kwarnings
   396         self._py3kwarnings = py3kwarnings
   401         self._shell = shell
   397         self._shell = shell
       
   398 
       
   399         self._aborted = False
   402         self._daemonpids = []
   400         self._daemonpids = []
   403 
       
   404         self._finished = None
   401         self._finished = None
   405         self._ret = None
   402         self._ret = None
   406         self._out = None
   403         self._out = None
   407         self._skipped = None
   404         self._skipped = None
   408         self._testtmp = None
   405         self._testtmp = None
   445         if os.path.exists(self.errpath):
   442         if os.path.exists(self.errpath):
   446             os.remove(self.errpath)
   443             os.remove(self.errpath)
   447 
   444 
   448     def run(self, result):
   445     def run(self, result):
   449         result.startTest(self)
   446         result.startTest(self)
   450         interrupted = False
       
   451         try:
   447         try:
   452             try:
   448             try:
   453                 self.setUp()
   449                 self.setUp()
   454             except (KeyboardInterrupt, SystemExit):
   450             except (KeyboardInterrupt, SystemExit):
   455                 interrupted = True
   451                 self._aborted = True
   456                 raise
   452                 raise
   457             except Exception:
   453             except Exception:
   458                 result.addError(self, sys.exc_info())
   454                 result.addError(self, sys.exc_info())
   459                 return
   455                 return
   460 
   456 
   461             success = False
   457             success = False
   462             try:
   458             try:
   463                 self.runTest()
   459                 self.runTest()
   464             except KeyboardInterrupt:
   460             except KeyboardInterrupt:
   465                 interrupted = True
   461                 self._aborted = True
   466                 raise
   462                 raise
   467             except SkipTest, e:
   463             except SkipTest, e:
   468                 result.addSkip(self, str(e))
   464                 result.addSkip(self, str(e))
   469             except IgnoreTest, e:
   465             except IgnoreTest, e:
   470                 result.addIgnore(self, str(e))
   466                 result.addIgnore(self, str(e))
   482                 success = True
   478                 success = True
   483 
   479 
   484             try:
   480             try:
   485                 self.tearDown()
   481                 self.tearDown()
   486             except (KeyboardInterrupt, SystemExit):
   482             except (KeyboardInterrupt, SystemExit):
   487                 interrupted = True
   483                 self._aborted = True
   488                 raise
   484                 raise
   489             except Exception:
   485             except Exception:
   490                 result.addError(self, sys.exc_info())
   486                 result.addError(self, sys.exc_info())
   491                 success = False
   487                 success = False
   492 
   488 
   493             if success:
   489             if success:
   494                 result.addSuccess(self)
   490                 result.addSuccess(self)
   495         finally:
   491         finally:
   496             result.stopTest(self, interrupted=interrupted)
   492             result.stopTest(self, interrupted=self._aborted)
   497 
   493 
   498     def runTest(self):
   494     def runTest(self):
   499         """Run this test instance.
   495         """Run this test instance.
   500 
   496 
   501         This will return a tuple describing the result of the test.
   497         This will return a tuple describing the result of the test.
   587 
   583 
   588     def _run(self, replacements, env):
   584     def _run(self, replacements, env):
   589         # This should be implemented in child classes to run tests.
   585         # This should be implemented in child classes to run tests.
   590         raise SkipTest('unknown test type')
   586         raise SkipTest('unknown test type')
   591 
   587 
       
   588     def abort(self):
       
   589         """Terminate execution of this test."""
       
   590         self._aborted = True
       
   591 
   592     def _getreplacements(self):
   592     def _getreplacements(self):
   593         r = [
   593         r = [
   594             (r':%s\b' % self._startport, ':$HGPORT'),
   594             (r':%s\b' % self._startport, ':$HGPORT'),
   595             (r':%s\b' % (self._startport + 1), ':$HGPORT1'),
   595             (r':%s\b' % (self._startport + 1), ':$HGPORT1'),
   596             (r':%s\b' % (self._startport + 2), ':$HGPORT2'),
   596             (r':%s\b' % (self._startport + 2), ':$HGPORT2'),
   696         py3kswitch = self._py3kwarnings and ' -3' or ''
   696         py3kswitch = self._py3kwarnings and ' -3' or ''
   697         cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path)
   697         cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path)
   698         vlog("# Running", cmd)
   698         vlog("# Running", cmd)
   699         if os.name == 'nt':
   699         if os.name == 'nt':
   700             replacements.append((r'\r\n', '\n'))
   700             replacements.append((r'\r\n', '\n'))
   701         return run(cmd, self._testtmp, replacements, env, self._abort,
   701         result = run(cmd, self._testtmp, replacements, env,
   702                    debug=self._debug, timeout=self._timeout)
   702                    debug=self._debug, timeout=self._timeout)
       
   703         if self._aborted:
       
   704             raise KeyboardInterrupt()
       
   705 
       
   706         return result
   703 
   707 
   704 class TTest(Test):
   708 class TTest(Test):
   705     """A "t test" is a test backed by a .t file."""
   709     """A "t test" is a test backed by a .t file."""
   706 
   710 
   707     SKIPPED_PREFIX = 'skipped: '
   711     SKIPPED_PREFIX = 'skipped: '
   732 
   736 
   733         cmd = '%s "%s"' % (self._shell, fname)
   737         cmd = '%s "%s"' % (self._shell, fname)
   734         vlog("# Running", cmd)
   738         vlog("# Running", cmd)
   735 
   739 
   736         exitcode, output = run(cmd, self._testtmp, replacements, env,
   740         exitcode, output = run(cmd, self._testtmp, replacements, env,
   737                                self._abort, debug=self._debug,
   741                                debug=self._debug, timeout=self._timeout)
   738                                timeout=self._timeout)
   742 
       
   743         if self._aborted:
       
   744             raise KeyboardInterrupt()
       
   745 
   739         # Do not merge output if skipped. Return hghave message instead.
   746         # Do not merge output if skipped. Return hghave message instead.
   740         # Similarly, with --debug, output is None.
   747         # Similarly, with --debug, output is None.
   741         if exitcode == self.SKIPPED_STATUS or output is None:
   748         if exitcode == self.SKIPPED_STATUS or output is None:
   742             return exitcode, output
   749             return exitcode, output
   743 
   750 
  1010     def _stringescape(s):
  1017     def _stringescape(s):
  1011         return TTest.ESCAPESUB(TTest._escapef, s)
  1018         return TTest.ESCAPESUB(TTest._escapef, s)
  1012 
  1019 
  1013 
  1020 
  1014 wifexited = getattr(os, "WIFEXITED", lambda x: False)
  1021 wifexited = getattr(os, "WIFEXITED", lambda x: False)
  1015 def run(cmd, wd, replacements, env, abort, debug=False, timeout=None):
  1022 def run(cmd, wd, replacements, env, debug=False, timeout=None):
  1016     """Run command in a sub-process, capturing the output (stdout and stderr).
  1023     """Run command in a sub-process, capturing the output (stdout and stderr).
  1017     Return a tuple (exitcode, output).  output is None in debug mode."""
  1024     Return a tuple (exitcode, output).  output is None in debug mode."""
  1018     if debug:
  1025     if debug:
  1019         proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
  1026         proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
  1020         ret = proc.wait()
  1027         ret = proc.wait()
  1046     if proc.timeout:
  1053     if proc.timeout:
  1047         ret = 'timeout'
  1054         ret = 'timeout'
  1048 
  1055 
  1049     if ret:
  1056     if ret:
  1050         killdaemons(env['DAEMON_PIDS'])
  1057         killdaemons(env['DAEMON_PIDS'])
  1051 
       
  1052     if abort[0]:
       
  1053         raise KeyboardInterrupt()
       
  1054 
  1058 
  1055     for s, r in replacements:
  1059     for s, r in replacements:
  1056         output = re.sub(s, r, output)
  1060         output = re.sub(s, r, output)
  1057     return ret, output.splitlines(True)
  1061     return ret, output.splitlines(True)
  1058 
  1062 
  1194                     if ignored:
  1198                     if ignored:
  1195                         continue
  1199                         continue
  1196 
  1200 
  1197             tests.append(test)
  1201             tests.append(test)
  1198 
  1202 
       
  1203         runtests = list(tests)
  1199         jobs = self._runner.options.jobs
  1204         jobs = self._runner.options.jobs
  1200         done = queue.Queue()
  1205         done = queue.Queue()
  1201         running = 0
  1206         running = 0
  1202 
  1207 
  1203         def job(test, result):
  1208         def job(test, result):
  1227                     t = threading.Thread(target=job, name=test.name,
  1232                     t = threading.Thread(target=job, name=test.name,
  1228                                          args=(test, result))
  1233                                          args=(test, result))
  1229                     t.start()
  1234                     t.start()
  1230                     running += 1
  1235                     running += 1
  1231         except KeyboardInterrupt:
  1236         except KeyboardInterrupt:
  1232             self._runner.abort[0] = True
  1237             for test in runtests:
       
  1238                 test.abort()
  1233 
  1239 
  1234         return result
  1240         return result
  1235 
  1241 
  1236 class TextTestRunner(unittest.TextTestRunner):
  1242 class TextTestRunner(unittest.TextTestRunner):
  1237     """Custom unittest test runner that uses appropriate settings."""
  1243     """Custom unittest test runner that uses appropriate settings."""
  1313         self.inst = None
  1319         self.inst = None
  1314         self.bindir = None
  1320         self.bindir = None
  1315         self.tmpbinddir = None
  1321         self.tmpbinddir = None
  1316         self.pythondir = None
  1322         self.pythondir = None
  1317         self.coveragefile = None
  1323         self.coveragefile = None
  1318         self.abort = [False]
       
  1319         self._createdfiles = []
  1324         self._createdfiles = []
  1320         self._hgpath = None
  1325         self._hgpath = None
  1321 
  1326 
  1322     def run(self, args, parser=None):
  1327     def run(self, args, parser=None):
  1323         """Run the test suite."""
  1328         """Run the test suite."""
  1516                 break
  1521                 break
  1517 
  1522 
  1518         refpath = os.path.join(self.testdir, test)
  1523         refpath = os.path.join(self.testdir, test)
  1519         tmpdir = os.path.join(self.hgtmp, 'child%d' % count)
  1524         tmpdir = os.path.join(self.hgtmp, 'child%d' % count)
  1520 
  1525 
  1521         return testcls(refpath, tmpdir, self.abort,
  1526         return testcls(refpath, tmpdir,
  1522                        keeptmpdir=self.options.keep_tmpdir,
  1527                        keeptmpdir=self.options.keep_tmpdir,
  1523                        debug=self.options.debug,
  1528                        debug=self.options.debug,
  1524                        nodiff = self.options.nodiff,
  1529                        nodiff = self.options.nodiff,
  1525                        diffviewer=self.options.view,
  1530                        diffviewer=self.options.view,
  1526                        interactive=self.options.interactive,
  1531                        interactive=self.options.interactive,