tests/run-tests.py
changeset 723 1c69e8924c9c
parent 722 85da4926cf39
child 724 211fa4b9803d
equal deleted inserted replaced
715:0614ba0295f6 723:1c69e8924c9c
    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 distutils import version
    44 import difflib
    45 import difflib
    45 import errno
    46 import errno
    46 import optparse
    47 import optparse
    47 import os
    48 import os
    48 import signal
    49 import signal
    67 # reserved exit code to skip test (used by hghave)
    68 # reserved exit code to skip test (used by hghave)
    68 SKIPPED_STATUS = 80
    69 SKIPPED_STATUS = 80
    69 SKIPPED_PREFIX = 'skipped: '
    70 SKIPPED_PREFIX = 'skipped: '
    70 FAILED_PREFIX  = 'hghave check failed: '
    71 FAILED_PREFIX  = 'hghave check failed: '
    71 PYTHON = sys.executable
    72 PYTHON = sys.executable
       
    73 IMPL_PATH = 'PYTHONPATH'
       
    74 if 'java' in sys.platform:
       
    75     IMPL_PATH = 'JYTHONPATH'
    72 
    76 
    73 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
    77 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
    74 
    78 
    75 defaults = {
    79 defaults = {
    76     'jobs': ('HGTEST_JOBS', 1),
    80     'jobs': ('HGTEST_JOBS', 1),
   108     parser.add_option("-p", "--port", type="int",
   112     parser.add_option("-p", "--port", type="int",
   109         help="port on which servers should listen"
   113         help="port on which servers should listen"
   110              " (default: $%s or %d)" % defaults['port'])
   114              " (default: $%s or %d)" % defaults['port'])
   111     parser.add_option("-r", "--retest", action="store_true",
   115     parser.add_option("-r", "--retest", action="store_true",
   112         help="retest failed tests")
   116         help="retest failed tests")
   113     parser.add_option("-s", "--cover_stdlib", action="store_true",
       
   114         help="print a test coverage report inc. standard libraries")
       
   115     parser.add_option("-S", "--noskips", action="store_true",
   117     parser.add_option("-S", "--noskips", action="store_true",
   116         help="don't report skip tests verbosely")
   118         help="don't report skip tests verbosely")
   117     parser.add_option("-t", "--timeout", type="int",
   119     parser.add_option("-t", "--timeout", type="int",
   118         help="kill errant tests after TIMEOUT seconds"
   120         help="kill errant tests after TIMEOUT seconds"
   119              " (default: $%s or %d)" % defaults['timeout'])
   121              " (default: $%s or %d)" % defaults['timeout'])
   139     for option, default in defaults.items():
   141     for option, default in defaults.items():
   140         defaults[option] = int(os.environ.get(*default))
   142         defaults[option] = int(os.environ.get(*default))
   141     parser.set_defaults(**defaults)
   143     parser.set_defaults(**defaults)
   142     (options, args) = parser.parse_args()
   144     (options, args) = parser.parse_args()
   143 
   145 
       
   146     # jython is always pure
       
   147     if 'java' in sys.platform or '__pypy__' in sys.modules:
       
   148         options.pure = True
       
   149 
   144     if options.with_hg:
   150     if options.with_hg:
   145         if not (os.path.isfile(options.with_hg) and
   151         if not (os.path.isfile(options.with_hg) and
   146                 os.access(options.with_hg, os.X_OK)):
   152                 os.access(options.with_hg, os.X_OK)):
   147             parser.error('--with-hg must specify an executable hg script')
   153             parser.error('--with-hg must specify an executable hg script')
   148         if not os.path.basename(options.with_hg) == 'hg':
   154         if not os.path.basename(options.with_hg) == 'hg':
   153         if not os.access(hgbin, os.X_OK):
   159         if not os.access(hgbin, os.X_OK):
   154             parser.error('--local specified, but %r not found or not executable'
   160             parser.error('--local specified, but %r not found or not executable'
   155                          % hgbin)
   161                          % hgbin)
   156         options.with_hg = hgbin
   162         options.with_hg = hgbin
   157 
   163 
   158     options.anycoverage = (options.cover or
   164     options.anycoverage = options.cover or options.annotate
   159                            options.cover_stdlib or
   165     if options.anycoverage:
   160                            options.annotate)
   166         try:
   161 
   167             import coverage
   162     if options.anycoverage and options.with_hg:
   168             covver = version.StrictVersion(coverage.__version__).version
   163         # I'm not sure if this is a fundamental limitation or just a
   169             if covver < (3, 3):
   164         # bug.  But I don't want to waste people's time and energy doing
   170                 parser.error('coverage options require coverage 3.3 or later')
   165         # test runs that don't give the results they want.
   171         except ImportError:
   166         parser.error("sorry, coverage options do not work when --with-hg "
   172             parser.error('coverage options now require the coverage package')
   167                      "or --local specified")
   173 
       
   174     if options.anycoverage and options.local:
       
   175         # this needs some path mangling somewhere, I guess
       
   176         parser.error("sorry, coverage options do not work when --local "
       
   177                      "is specified")
   168 
   178 
   169     global vlog
   179     global vlog
   170     if options.verbose:
   180     if options.verbose:
   171         if options.jobs > 1 or options.child is not None:
   181         if options.jobs > 1 or options.child is not None:
   172             pid = "[%d]" % os.getpid()
   182             pid = "[%d]" % os.getpid()
   388         for line in lines:
   398         for line in lines:
   389             f.write(line + '\n')
   399             f.write(line + '\n')
   390         f.close()
   400         f.close()
   391 
   401 
   392     if options.anycoverage:
   402     if options.anycoverage:
   393         vlog("# Installing coverage wrapper")
   403         custom = os.path.join(TESTDIR, 'sitecustomize.py')
   394         os.environ['COVERAGE_FILE'] = COVERAGE_FILE
   404         target = os.path.join(PYTHONDIR, 'sitecustomize.py')
   395         if os.path.exists(COVERAGE_FILE):
   405         vlog('# Installing coverage trigger to %s' % target)
   396             os.unlink(COVERAGE_FILE)
   406         shutil.copyfile(custom, target)
   397         # Create a wrapper script to invoke hg via coverage.py
   407         rc = os.path.join(TESTDIR, '.coveragerc')
   398         os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
   408         vlog('# Installing coverage rc to %s' % rc)
   399         f = open(os.path.join(BINDIR, 'hg'), 'w')
   409         os.environ['COVERAGE_PROCESS_START'] = rc
   400         f.write('#!' + sys.executable + '\n')
   410         fn = os.path.join(INST, '..', '.coverage')
   401         f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
   411         os.environ['COVERAGE_FILE'] = fn
   402                 '"%s", "-x", "-p", "%s"] + sys.argv[1:])\n' %
       
   403                 (os.path.join(TESTDIR, 'coverage.py'),
       
   404                  os.path.join(BINDIR, '_hg.py')))
       
   405         f.close()
       
   406         os.chmod(os.path.join(BINDIR, 'hg'), 0700)
       
   407 
   412 
   408 def outputcoverage(options):
   413 def outputcoverage(options):
   409 
   414 
   410     vlog('# Producing coverage report')
   415     vlog('# Producing coverage report')
   411     os.chdir(PYTHONDIR)
   416     os.chdir(PYTHONDIR)
   412 
   417 
   413     def covrun(*args):
   418     def covrun(*args):
   414         start = sys.executable, os.path.join(TESTDIR, 'coverage.py')
   419         cmd = 'coverage %s' % ' '.join(args)
   415         cmd = '"%s" "%s" %s' % (start[0], start[1], ' '.join(args))
       
   416         vlog('# Running: %s' % cmd)
   420         vlog('# Running: %s' % cmd)
   417         os.system(cmd)
   421         os.system(cmd)
   418 
   422 
   419     omit = [BINDIR, TESTDIR, PYTHONDIR]
   423     if options.child:
   420     if not options.cover_stdlib:
   424         return
   421         # Exclude as system paths (ignoring empty strings seen on win)
   425 
   422         omit += [x for x in sys.path if x != '']
   426     covrun('-c')
   423     omit = ','.join(omit)
   427     omit = ','.join([BINDIR, TESTDIR])
   424 
       
   425     covrun('-c') # combine from parallel processes
       
   426     for fn in os.listdir(TESTDIR):
       
   427         if fn.startswith('.coverage.'):
       
   428             os.unlink(os.path.join(TESTDIR, fn))
       
   429 
       
   430     covrun('-i', '-r', '"--omit=%s"' % omit) # report
   428     covrun('-i', '-r', '"--omit=%s"' % omit) # report
   431     if options.annotate:
   429     if options.annotate:
   432         adir = os.path.join(TESTDIR, 'annotated')
   430         adir = os.path.join(TESTDIR, 'annotated')
   433         if not os.path.isdir(adir):
   431         if not os.path.isdir(adir):
   434             os.mkdir(adir)
   432             os.mkdir(adir)
   666 
   664 
   667     optcopy = dict(options.__dict__)
   665     optcopy = dict(options.__dict__)
   668     optcopy['jobs'] = 1
   666     optcopy['jobs'] = 1
   669     if optcopy['with_hg'] is None:
   667     if optcopy['with_hg'] is None:
   670         optcopy['with_hg'] = os.path.join(BINDIR, "hg")
   668         optcopy['with_hg'] = os.path.join(BINDIR, "hg")
       
   669     optcopy.pop('anycoverage', None)
       
   670 
   671     opts = []
   671     opts = []
   672     for opt, value in optcopy.iteritems():
   672     for opt, value in optcopy.iteritems():
   673         name = '--' + opt.replace('_', '-')
   673         name = '--' + opt.replace('_', '-')
   674         if value is True:
   674         if value is True:
   675             opts.append(name)
   675             opts.append(name)
   727         print "Failed %s: %s" % (s[0], s[1])
   727         print "Failed %s: %s" % (s[0], s[1])
   728 
   728 
   729     _checkhglib("Tested")
   729     _checkhglib("Tested")
   730     print "# Ran %d tests, %d skipped, %d failed." % (
   730     print "# Ran %d tests, %d skipped, %d failed." % (
   731         tested, skipped, failed)
   731         tested, skipped, failed)
       
   732 
       
   733     if options.anycoverage:
       
   734         outputcoverage(options)
   732     sys.exit(failures != 0)
   735     sys.exit(failures != 0)
   733 
   736 
   734 def runtests(options, tests):
   737 def runtests(options, tests):
   735     global DAEMON_PIDS, HGRCPATH
   738     global DAEMON_PIDS, HGRCPATH
   736     DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
   739     DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
   844     os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
   847     os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
   845     os.environ['TZ'] = 'GMT'
   848     os.environ['TZ'] = 'GMT'
   846     os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
   849     os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
   847     os.environ['CDPATH'] = ''
   850     os.environ['CDPATH'] = ''
   848     os.environ['COLUMNS'] = '80'
   851     os.environ['COLUMNS'] = '80'
       
   852     os.environ['GREP_OPTIONS'] = ''
   849     os.environ['http_proxy'] = ''
   853     os.environ['http_proxy'] = ''
   850 
   854 
   851     global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
   855     global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
   852     TESTDIR = os.environ["TESTDIR"] = os.getcwd()
   856     TESTDIR = os.environ["TESTDIR"] = os.getcwd()
   853     if options.tmpdir:
   857     if options.tmpdir:
   908         pypath = [PYTHONDIR, TESTDIR]
   912         pypath = [PYTHONDIR, TESTDIR]
   909         # We have to augment PYTHONPATH, rather than simply replacing
   913         # We have to augment PYTHONPATH, rather than simply replacing
   910         # it, in case external libraries are only available via current
   914         # it, in case external libraries are only available via current
   911         # PYTHONPATH.  (In particular, the Subversion bindings on OS X
   915         # PYTHONPATH.  (In particular, the Subversion bindings on OS X
   912         # are in /opt/subversion.)
   916         # are in /opt/subversion.)
   913         oldpypath = os.environ.get('PYTHONPATH')
   917         oldpypath = os.environ.get(IMPL_PATH)
   914         if oldpypath:
   918         if oldpypath:
   915             pypath.append(oldpypath)
   919             pypath.append(oldpypath)
   916         os.environ['PYTHONPATH'] = os.pathsep.join(pypath)
   920         os.environ[IMPL_PATH] = os.pathsep.join(pypath)
   917 
   921 
   918     COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
   922     COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
   919 
   923 
   920     if len(args) == 0:
   924     if len(args) == 0:
   921         args = os.listdir(".")
   925         args = os.listdir(".")
   932         return
   936         return
   933 
   937 
   934     vlog("# Using TESTDIR", TESTDIR)
   938     vlog("# Using TESTDIR", TESTDIR)
   935     vlog("# Using HGTMP", HGTMP)
   939     vlog("# Using HGTMP", HGTMP)
   936     vlog("# Using PATH", os.environ["PATH"])
   940     vlog("# Using PATH", os.environ["PATH"])
   937     vlog("# Using PYTHONPATH", os.environ["PYTHONPATH"])
   941     vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
   938 
   942 
   939     try:
   943     try:
   940         if len(tests) > 1 and options.jobs > 1:
   944         if len(tests) > 1 and options.jobs > 1:
   941             runchildren(options, tests)
   945             runchildren(options, tests)
   942         else:
   946         else: