tests/run-tests.py
changeset 684 4da6285efe4c
parent 683 26ca426a04e1
child 692 5ac40b193130
equal deleted inserted replaced
677:9bdbae6007cb 684:4da6285efe4c
    39 # (You could use any subset of the tests: test-s* happens to match
    39 # (You could use any subset of the tests: test-s* happens to match
    40 # enough that it's worth doing parallel runs, few enough that it
    40 # enough that it's worth doing parallel runs, few enough that it
    41 # completes fairly quickly, includes both shell and Python scripts, and
    41 # completes fairly quickly, includes both shell and Python scripts, and
    42 # includes some scripts that run daemon processes.)
    42 # includes some scripts that run daemon processes.)
    43 
    43 
    44 from ConfigParser import ConfigParser
       
    45 import difflib
    44 import difflib
    46 import errno
    45 import errno
    47 import optparse
    46 import optparse
    48 import os
    47 import os
       
    48 import signal
    49 import subprocess
    49 import subprocess
    50 import shutil
    50 import shutil
    51 import signal
    51 import signal
    52 import sys
    52 import sys
    53 import tempfile
    53 import tempfile
   132     parser.add_option("-3", "--py3k-warnings", action="store_true",
   132     parser.add_option("-3", "--py3k-warnings", action="store_true",
   133         help="enable Py3k warnings on Python 2.6+")
   133         help="enable Py3k warnings on Python 2.6+")
   134     parser.add_option("--inotify", action="store_true",
   134     parser.add_option("--inotify", action="store_true",
   135         help="enable inotify extension when running tests")
   135         help="enable inotify extension when running tests")
   136     parser.add_option("--blacklist", action="append",
   136     parser.add_option("--blacklist", action="append",
   137         help="skip tests listed in the specified section of "
   137         help="skip tests listed in the specified blacklist file")
   138              "the blacklist file")
       
   139 
   138 
   140     for option, default in defaults.items():
   139     for option, default in defaults.items():
   141         defaults[option] = int(os.environ.get(*default))
   140         defaults[option] = int(os.environ.get(*default))
   142     parser.set_defaults(**defaults)
   141     parser.set_defaults(**defaults)
   143     (options, args) = parser.parse_args()
   142     (options, args) = parser.parse_args()
   200         options.timeout = 0
   199         options.timeout = 0
   201     if options.py3k_warnings:
   200     if options.py3k_warnings:
   202         if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
   201         if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
   203             parser.error('--py3k-warnings can only be used on Python 2.6+')
   202             parser.error('--py3k-warnings can only be used on Python 2.6+')
   204     if options.blacklist:
   203     if options.blacklist:
   205         configparser = ConfigParser()
       
   206         configparser.read("blacklist")
       
   207         blacklist = dict()
   204         blacklist = dict()
   208         for section in options.blacklist:
   205         for filename in options.blacklist:
   209             for (item, value) in configparser.items(section):
   206             try:
   210                 blacklist["test-" + item] = section
   207                 path = os.path.expanduser(os.path.expandvars(filename))
       
   208                 f = open(path, "r")
       
   209             except IOError, err:
       
   210                 if err.errno != errno.ENOENT:
       
   211                     raise
       
   212                 print "warning: no such blacklist file: %s" % filename
       
   213                 continue
       
   214 
       
   215             for line in f.readlines():
       
   216                 line = line.strip()
       
   217                 if line and not line.startswith('#'):
       
   218                     blacklist[line] = filename
       
   219 
   211         options.blacklist = blacklist
   220         options.blacklist = blacklist
   212 
   221 
   213     return (options, args)
   222     return (options, args)
   214 
   223 
   215 def rename(src, dst):
   224 def rename(src, dst):
   229         if n == -1:
   238         if n == -1:
   230             last = text[i:]
   239             last = text[i:]
   231             if last:
   240             if last:
   232                 lines.append(last)
   241                 lines.append(last)
   233             return lines
   242             return lines
   234         lines.append(text[i:n+1])
   243         lines.append(text[i:n + 1])
   235         i = n + 1
   244         i = n + 1
   236 
   245 
   237 def parsehghaveoutput(lines):
   246 def parsehghaveoutput(lines):
   238     '''Parse hghave log lines.
   247     '''Parse hghave log lines.
   239     Return tuple of lists (missing, failed):
   248     Return tuple of lists (missing, failed):
   272         found = findprogram(p)
   281         found = findprogram(p)
   273         if found:
   282         if found:
   274             vlog("# Found prerequisite", p, "at", found)
   283             vlog("# Found prerequisite", p, "at", found)
   275         else:
   284         else:
   276             print "WARNING: Did not find prerequisite tool: "+p
   285             print "WARNING: Did not find prerequisite tool: "+p
       
   286 
       
   287 def killdaemons():
       
   288     # Kill off any leftover daemon processes
       
   289     try:
       
   290         fp = open(DAEMON_PIDS)
       
   291         for line in fp:
       
   292             try:
       
   293                 pid = int(line)
       
   294             except ValueError:
       
   295                 continue
       
   296             try:
       
   297                 os.kill(pid, 0)
       
   298                 vlog('# Killing daemon process %d' % pid)
       
   299                 os.kill(pid, signal.SIGTERM)
       
   300                 time.sleep(0.25)
       
   301                 os.kill(pid, 0)
       
   302                 vlog('# Daemon process %d is stuck - really killing it' % pid)
       
   303                 os.kill(pid, signal.SIGKILL)
       
   304             except OSError, err:
       
   305                 if err.errno != errno.ESRCH:
       
   306                     raise
       
   307         fp.close()
       
   308         os.unlink(DAEMON_PIDS)
       
   309     except IOError:
       
   310         pass
   277 
   311 
   278 def cleanup(options):
   312 def cleanup(options):
   279     if not options.keep_tmpdir:
   313     if not options.keep_tmpdir:
   280         vlog("# Cleaning up HGTMP", HGTMP)
   314         vlog("# Cleaning up HGTMP", HGTMP)
   281         shutil.rmtree(HGTMP, True)
   315         shutil.rmtree(HGTMP, True)
   422         ret = fromchild.close()
   456         ret = fromchild.close()
   423         if ret == None:
   457         if ret == None:
   424             ret = 0
   458             ret = 0
   425     else:
   459     else:
   426         proc = Popen4(cmd)
   460         proc = Popen4(cmd)
       
   461         def cleanup():
       
   462             os.kill(proc.pid, signal.SIGTERM)
       
   463             ret = proc.wait()
       
   464             if ret == 0:
       
   465                 ret = signal.SIGTERM << 8
       
   466             killdaemons()
       
   467             return ret
       
   468 
   427         try:
   469         try:
   428             output = ''
   470             output = ''
   429             proc.tochild.close()
   471             proc.tochild.close()
   430             output = proc.fromchild.read()
   472             output = proc.fromchild.read()
   431             ret = proc.wait()
   473             ret = proc.wait()
   432             if os.WIFEXITED(ret):
   474             if os.WIFEXITED(ret):
   433                 ret = os.WEXITSTATUS(ret)
   475                 ret = os.WEXITSTATUS(ret)
   434         except Timeout:
   476         except Timeout:
   435             vlog('# Process %d timed out - killing it' % proc.pid)
   477             vlog('# Process %d timed out - killing it' % proc.pid)
   436             os.kill(proc.pid, signal.SIGTERM)
   478             ret = cleanup()
   437             ret = proc.wait()
       
   438             if ret == 0:
       
   439                 ret = signal.SIGTERM << 8
       
   440             output += ("\n### Abort: timeout after %d seconds.\n"
   479             output += ("\n### Abort: timeout after %d seconds.\n"
   441                        % options.timeout)
   480                        % options.timeout)
       
   481         except KeyboardInterrupt:
       
   482             vlog('# Handling keyboard interrupt')
       
   483             cleanup()
       
   484             raise
       
   485 
   442     return ret, splitnewlines(output)
   486     return ret, splitnewlines(output)
   443 
   487 
   444 def runone(options, test, skips, fails):
   488 def runone(options, test, skips, fails):
   445     '''tristate output:
   489     '''tristate output:
   446     None -> skipped
   490     None -> skipped
   579         f = open(err, "wb")
   623         f = open(err, "wb")
   580         for line in out:
   624         for line in out:
   581             f.write(line)
   625             f.write(line)
   582         f.close()
   626         f.close()
   583 
   627 
   584     # Kill off any leftover daemon processes
   628     killdaemons()
   585     try:
       
   586         fp = open(DAEMON_PIDS)
       
   587         for line in fp:
       
   588             try:
       
   589                 pid = int(line)
       
   590             except ValueError:
       
   591                 continue
       
   592             try:
       
   593                 os.kill(pid, 0)
       
   594                 vlog('# Killing daemon process %d' % pid)
       
   595                 os.kill(pid, signal.SIGTERM)
       
   596                 time.sleep(0.25)
       
   597                 os.kill(pid, 0)
       
   598                 vlog('# Daemon process %d is stuck - really killing it' % pid)
       
   599                 os.kill(pid, signal.SIGKILL)
       
   600             except OSError, err:
       
   601                 if err.errno != errno.ESRCH:
       
   602                     raise
       
   603         fp.close()
       
   604         os.unlink(DAEMON_PIDS)
       
   605     except IOError:
       
   606         pass
       
   607 
   629 
   608     os.chdir(TESTDIR)
   630     os.chdir(TESTDIR)
   609     if not options.keep_tmpdir:
   631     if not options.keep_tmpdir:
   610         shutil.rmtree(tmpd, True)
   632         shutil.rmtree(tmpd, True)
   611     if skipped:
   633     if skipped:
   658 
   680 
   659     tests.reverse()
   681     tests.reverse()
   660     jobs = [[] for j in xrange(options.jobs)]
   682     jobs = [[] for j in xrange(options.jobs)]
   661     while tests:
   683     while tests:
   662         for job in jobs:
   684         for job in jobs:
   663             if not tests: break
   685             if not tests:
       
   686                 break
   664             job.append(tests.pop())
   687             job.append(tests.pop())
   665     fps = {}
   688     fps = {}
       
   689 
   666     for j, job in enumerate(jobs):
   690     for j, job in enumerate(jobs):
   667         if not job:
   691         if not job:
   668             continue
   692             continue
   669         rfd, wfd = os.pipe()
   693         rfd, wfd = os.pipe()
   670         childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
   694         childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
   672         childopts += ['--tmpdir', childtmp]
   696         childopts += ['--tmpdir', childtmp]
   673         cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
   697         cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
   674         vlog(' '.join(cmdline))
   698         vlog(' '.join(cmdline))
   675         fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
   699         fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
   676         os.close(wfd)
   700         os.close(wfd)
       
   701     signal.signal(signal.SIGINT, signal.SIG_IGN)
   677     failures = 0
   702     failures = 0
   678     tested, skipped, failed = 0, 0, 0
   703     tested, skipped, failed = 0, 0, 0
   679     skips = []
   704     skips = []
   680     fails = []
   705     fails = []
   681     while fps:
   706     while fps:
   682         pid, status = os.wait()
   707         pid, status = os.wait()
   683         fp = fps.pop(pid)
   708         fp = fps.pop(pid)
   684         l = fp.read().splitlines()
   709         l = fp.read().splitlines()
   685         test, skip, fail = map(int, l[:3])
   710         try:
       
   711             test, skip, fail = map(int, l[:3])
       
   712         except ValueError:
       
   713             test, skip, fail = 0, 0, 0
   686         split = -fail or len(l)
   714         split = -fail or len(l)
   687         for s in l[3:split]:
   715         for s in l[3:split]:
   688             skips.append(s.split(" ", 1))
   716             skips.append(s.split(" ", 1))
   689         for s in l[split:]:
   717         for s in l[split:]:
   690             fails.append(s.split(" ", 1))
   718             fails.append(s.split(" ", 1))
   741         skips = []
   769         skips = []
   742         fails = []
   770         fails = []
   743 
   771 
   744         for test in tests:
   772         for test in tests:
   745             if options.blacklist:
   773             if options.blacklist:
   746                 section = options.blacklist.get(test)
   774                 filename = options.blacklist.get(test)
   747                 if section is not None:
   775                 if filename is not None:
   748                     skips.append((test, "blacklisted (%s section)" % section))
   776                     skips.append((test, "blacklisted (%s)" % filename))
   749                     skipped += 1
   777                     skipped += 1
   750                     continue
   778                     continue
   751 
   779 
   752             if options.retest and not os.path.exists(test + ".err"):
   780             if options.retest and not os.path.exists(test + ".err"):
   753                 skipped += 1
   781                 skipped += 1
   757                 t = open(test).read().lower() + test.lower()
   785                 t = open(test).read().lower() + test.lower()
   758                 for k in options.keywords.lower().split():
   786                 for k in options.keywords.lower().split():
   759                     if k in t:
   787                     if k in t:
   760                         break
   788                         break
   761                 else:
   789                 else:
   762                     skipped +=1
   790                     skipped += 1
   763                     continue
   791                     continue
   764 
   792 
   765             ret = runone(options, test, skips, fails)
   793             ret = runone(options, test, skips, fails)
   766             if ret is None:
   794             if ret is None:
   767                 skipped += 1
   795                 skipped += 1
   913         if len(tests) > 1 and options.jobs > 1:
   941         if len(tests) > 1 and options.jobs > 1:
   914             runchildren(options, tests)
   942             runchildren(options, tests)
   915         else:
   943         else:
   916             runtests(options, tests)
   944             runtests(options, tests)
   917     finally:
   945     finally:
       
   946         time.sleep(1)
   918         cleanup(options)
   947         cleanup(options)
   919 
   948 
   920 main()
   949 main()