layout/tools/reftest/runreftest.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/layout/tools/reftest/runreftest.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,539 @@
     1.4 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.7 +
     1.8 +"""
     1.9 +Runs the reftest test harness.
    1.10 +"""
    1.11 +
    1.12 +from optparse import OptionParser
    1.13 +import collections
    1.14 +import json
    1.15 +import multiprocessing
    1.16 +import os
    1.17 +import re
    1.18 +import shutil
    1.19 +import subprocess
    1.20 +import sys
    1.21 +import threading
    1.22 +
    1.23 +SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
    1.24 +sys.path.insert(0, SCRIPT_DIRECTORY)
    1.25 +
    1.26 +from automation import Automation
    1.27 +from automationutils import (
    1.28 +        addCommonOptions,
    1.29 +        getDebuggerInfo,
    1.30 +        isURL,
    1.31 +        processLeakLog
    1.32 +)
    1.33 +import mozprofile
    1.34 +
    1.35 +def categoriesToRegex(categoryList):
    1.36 +  return "\\(" + ', '.join(["(?P<%s>\\d+) %s" % c for c in categoryList]) + "\\)"
    1.37 +summaryLines = [('Successful', [('pass', 'pass'), ('loadOnly', 'load only')]),
    1.38 +                ('Unexpected', [('fail', 'unexpected fail'),
    1.39 +                                ('pass', 'unexpected pass'),
    1.40 +                                ('asserts', 'unexpected asserts'),
    1.41 +                                ('fixedAsserts', 'unexpected fixed asserts'),
    1.42 +                                ('failedLoad', 'failed load'),
    1.43 +                                ('exception', 'exception')]),
    1.44 +                ('Known problems', [('knownFail', 'known fail'),
    1.45 +                                    ('knownAsserts', 'known asserts'),
    1.46 +                                    ('random', 'random'),
    1.47 +                                    ('skipped', 'skipped'),
    1.48 +                                    ('slow', 'slow')])]
    1.49 +
    1.50 +# Python's print is not threadsafe.
    1.51 +printLock = threading.Lock()
    1.52 +
    1.53 +class ReftestThread(threading.Thread):
    1.54 +  def __init__(self, cmdlineArgs):
    1.55 +    threading.Thread.__init__(self)
    1.56 +    self.cmdlineArgs = cmdlineArgs
    1.57 +    self.summaryMatches = {}
    1.58 +    self.retcode = -1
    1.59 +    for text, _ in summaryLines:
    1.60 +      self.summaryMatches[text] = None
    1.61 +
    1.62 +  def run(self):
    1.63 +    with printLock:
    1.64 +      print "Starting thread with", self.cmdlineArgs
    1.65 +      sys.stdout.flush()
    1.66 +    process = subprocess.Popen(self.cmdlineArgs, stdout=subprocess.PIPE)
    1.67 +    for chunk in self.chunkForMergedOutput(process.stdout):
    1.68 +      with printLock:
    1.69 +        print chunk,
    1.70 +        sys.stdout.flush()
    1.71 +    self.retcode = process.wait()
    1.72 +
    1.73 +  def chunkForMergedOutput(self, logsource):
    1.74 +    """Gather lines together that should be printed as one atomic unit.
    1.75 +    Individual test results--anything between 'REFTEST TEST-START' and
    1.76 +    'REFTEST TEST-END' lines--are an atomic unit.  Lines with data from
    1.77 +    summaries are parsed and the data stored for later aggregation.
    1.78 +    Other lines are considered their own atomic units and are permitted
    1.79 +    to intermix freely."""
    1.80 +    testStartRegex = re.compile("^REFTEST TEST-START")
    1.81 +    testEndRegex = re.compile("^REFTEST TEST-END")
    1.82 +    summaryHeadRegex = re.compile("^REFTEST INFO \\| Result summary:")
    1.83 +    summaryRegexFormatString = "^REFTEST INFO \\| (?P<message>{text}): (?P<total>\\d+) {regex}"
    1.84 +    summaryRegexStrings = [summaryRegexFormatString.format(text=text,
    1.85 +                                                           regex=categoriesToRegex(categories))
    1.86 +                           for (text, categories) in summaryLines]
    1.87 +    summaryRegexes = [re.compile(regex) for regex in summaryRegexStrings]
    1.88 +
    1.89 +    for line in logsource:
    1.90 +      if testStartRegex.search(line) is not None:
    1.91 +        chunkedLines = [line]
    1.92 +        for lineToBeChunked in logsource:
    1.93 +          chunkedLines.append(lineToBeChunked)
    1.94 +          if testEndRegex.search(lineToBeChunked) is not None:
    1.95 +            break
    1.96 +        yield ''.join(chunkedLines)
    1.97 +        continue
    1.98 +
    1.99 +      haveSuppressedSummaryLine = False
   1.100 +      for regex in summaryRegexes:
   1.101 +        match = regex.search(line)
   1.102 +        if match is not None:
   1.103 +          self.summaryMatches[match.group('message')] = match
   1.104 +          haveSuppressedSummaryLine = True
   1.105 +          break
   1.106 +      if haveSuppressedSummaryLine:
   1.107 +        continue
   1.108 +
   1.109 +      if summaryHeadRegex.search(line) is None:
   1.110 +        yield line
   1.111 +
   1.112 +class RefTest(object):
   1.113 +
   1.114 +  oldcwd = os.getcwd()
   1.115 +
   1.116 +  def __init__(self, automation=None):
   1.117 +    self.automation = automation or Automation()
   1.118 +
   1.119 +  def getFullPath(self, path):
   1.120 +    "Get an absolute path relative to self.oldcwd."
   1.121 +    return os.path.normpath(os.path.join(self.oldcwd, os.path.expanduser(path)))
   1.122 +
   1.123 +  def getManifestPath(self, path):
   1.124 +    "Get the path of the manifest, and for remote testing this function is subclassed to point to remote manifest"
   1.125 +    path = self.getFullPath(path)
   1.126 +    if os.path.isdir(path):
   1.127 +      defaultManifestPath = os.path.join(path, 'reftest.list')
   1.128 +      if os.path.exists(defaultManifestPath):
   1.129 +        path = defaultManifestPath
   1.130 +      else:
   1.131 +        defaultManifestPath = os.path.join(path, 'crashtests.list')
   1.132 +        if os.path.exists(defaultManifestPath):
   1.133 +          path = defaultManifestPath
   1.134 +    return path
   1.135 +
   1.136 +  def makeJSString(self, s):
   1.137 +    return '"%s"' % re.sub(r'([\\"])', r'\\\1', s)
   1.138 +
   1.139 +  def createReftestProfile(self, options, manifest, server='localhost',
   1.140 +                           special_powers=True, profile_to_clone=None):
   1.141 +    """
   1.142 +      Sets up a profile for reftest.
   1.143 +      'manifest' is the path to the reftest.list file we want to test with.  This is used in
   1.144 +      the remote subclass in remotereftest.py so we can write it to a preference for the
   1.145 +      bootstrap extension.
   1.146 +    """
   1.147 +
   1.148 +    locations = mozprofile.permissions.ServerLocations()
   1.149 +    locations.add_host(server, port=0)
   1.150 +    locations.add_host('<file>', port=0)
   1.151 +
   1.152 +    # Set preferences for communication between our command line arguments
   1.153 +    # and the reftest harness.  Preferences that are required for reftest
   1.154 +    # to work should instead be set in reftest-cmdline.js .
   1.155 +    prefs = {}
   1.156 +    prefs['reftest.timeout'] = options.timeout * 1000
   1.157 +    if options.totalChunks:
   1.158 +      prefs['reftest.totalChunks'] = options.totalChunks
   1.159 +    if options.thisChunk:
   1.160 +      prefs['reftest.thisChunk'] = options.thisChunk
   1.161 +    if options.logFile:
   1.162 +      prefs['reftest.logFile'] = options.logFile
   1.163 +    if options.ignoreWindowSize:
   1.164 +      prefs['reftest.ignoreWindowSize'] = True
   1.165 +    if options.filter:
   1.166 +      prefs['reftest.filter'] = options.filter
   1.167 +    if options.shuffle:
   1.168 +      prefs['reftest.shuffle'] = True
   1.169 +    prefs['reftest.focusFilterMode'] = options.focusFilterMode
   1.170 +
   1.171 +    # Ensure that telemetry is disabled, so we don't connect to the telemetry
   1.172 +    # server in the middle of the tests.
   1.173 +    prefs['toolkit.telemetry.enabled'] = False
   1.174 +    # Likewise for safebrowsing.
   1.175 +    prefs['browser.safebrowsing.enabled'] = False
   1.176 +    prefs['browser.safebrowsing.malware.enabled'] = False
   1.177 +    # And for snippets.
   1.178 +    prefs['browser.snippets.enabled'] = False
   1.179 +    prefs['browser.snippets.syncPromo.enabled'] = False
   1.180 +    # And for useragent updates.
   1.181 +    prefs['general.useragent.updates.enabled'] = False
   1.182 +    # And for webapp updates.  Yes, it is supposed to be an integer.
   1.183 +    prefs['browser.webapps.checkForUpdates'] = 0
   1.184 +
   1.185 +    if options.e10s:
   1.186 +      prefs['browser.tabs.remote.autostart'] = True
   1.187 +
   1.188 +    for v in options.extraPrefs:
   1.189 +      thispref = v.split('=')
   1.190 +      if len(thispref) < 2:
   1.191 +        print "Error: syntax error in --setpref=" + v
   1.192 +        sys.exit(1)
   1.193 +      prefs[thispref[0]] = mozprofile.Preferences.cast(thispref[1].strip())
   1.194 +
   1.195 +    # install the reftest extension bits into the profile
   1.196 +    addons = []
   1.197 +    addons.append(os.path.join(SCRIPT_DIRECTORY, "reftest"))
   1.198 +
   1.199 +    # I would prefer to use "--install-extension reftest/specialpowers", but that requires tight coordination with
   1.200 +    # release engineering and landing on multiple branches at once.
   1.201 +    if special_powers and (manifest.endswith('crashtests.list') or manifest.endswith('jstests.list')):
   1.202 +      addons.append(os.path.join(SCRIPT_DIRECTORY, 'specialpowers'))
   1.203 +
   1.204 +    # Install distributed extensions, if application has any.
   1.205 +    distExtDir = os.path.join(options.app[ : options.app.rfind(os.sep)], "distribution", "extensions")
   1.206 +    if os.path.isdir(distExtDir):
   1.207 +      for f in os.listdir(distExtDir):
   1.208 +        addons.append(os.path.join(distExtDir, f))
   1.209 +
   1.210 +    # Install custom extensions.
   1.211 +    for f in options.extensionsToInstall:
   1.212 +      addons.append(self.getFullPath(f))
   1.213 +
   1.214 +    kwargs = { 'addons': addons,
   1.215 +               'preferences': prefs,
   1.216 +               'locations': locations }
   1.217 +    if profile_to_clone:
   1.218 +        profile = mozprofile.Profile.clone(profile_to_clone, **kwargs)
   1.219 +    else:
   1.220 +        profile = mozprofile.Profile(**kwargs)
   1.221 +
   1.222 +    self.copyExtraFilesToProfile(options, profile)
   1.223 +    return profile
   1.224 +
   1.225 +  def buildBrowserEnv(self, options, profileDir):
   1.226 +    browserEnv = self.automation.environment(xrePath = options.xrePath, debugger=options.debugger)
   1.227 +    browserEnv["XPCOM_DEBUG_BREAK"] = "stack"
   1.228 +
   1.229 +    for v in options.environment:
   1.230 +      ix = v.find("=")
   1.231 +      if ix <= 0:
   1.232 +        print "Error: syntax error in --setenv=" + v
   1.233 +        return None
   1.234 +      browserEnv[v[:ix]] = v[ix + 1:]
   1.235 +
   1.236 +    # Enable leaks detection to its own log file.
   1.237 +    self.leakLogFile = os.path.join(profileDir, "runreftest_leaks.log")
   1.238 +    browserEnv["XPCOM_MEM_BLOAT_LOG"] = self.leakLogFile
   1.239 +    return browserEnv
   1.240 +
   1.241 +  def cleanup(self, profileDir):
   1.242 +    if profileDir:
   1.243 +      shutil.rmtree(profileDir, True)
   1.244 +
   1.245 +  def runTests(self, testPath, options, cmdlineArgs = None):
   1.246 +    if not options.runTestsInParallel:
   1.247 +      return self.runSerialTests(testPath, options, cmdlineArgs)
   1.248 +
   1.249 +    cpuCount = multiprocessing.cpu_count()
   1.250 +
   1.251 +    # We have the directive, technology, and machine to run multiple test instances.
   1.252 +    # Experimentation says that reftests are not overly CPU-intensive, so we can run
   1.253 +    # multiple jobs per CPU core.
   1.254 +    #
   1.255 +    # Our Windows machines in automation seem to get upset when we run a lot of
   1.256 +    # simultaneous tests on them, so tone things down there.
   1.257 +    if sys.platform == 'win32':
   1.258 +      jobsWithoutFocus = cpuCount
   1.259 +    else:
   1.260 +      jobsWithoutFocus = 2 * cpuCount
   1.261 +      
   1.262 +    totalJobs = jobsWithoutFocus + 1
   1.263 +    perProcessArgs = [sys.argv[:] for i in range(0, totalJobs)]
   1.264 +
   1.265 +    # First job is only needs-focus tests.  Remaining jobs are non-needs-focus and chunked.
   1.266 +    perProcessArgs[0].insert(-1, "--focus-filter-mode=needs-focus")
   1.267 +    for (chunkNumber, jobArgs) in enumerate(perProcessArgs[1:], start=1):
   1.268 +      jobArgs[-1:-1] = ["--focus-filter-mode=non-needs-focus",
   1.269 +                        "--total-chunks=%d" % jobsWithoutFocus,
   1.270 +                        "--this-chunk=%d" % chunkNumber]
   1.271 +
   1.272 +    for jobArgs in perProcessArgs:
   1.273 +      try:
   1.274 +        jobArgs.remove("--run-tests-in-parallel")
   1.275 +      except:
   1.276 +        pass
   1.277 +      jobArgs.insert(-1, "--no-run-tests-in-parallel")
   1.278 +      jobArgs[0:0] = [sys.executable, "-u"]
   1.279 +
   1.280 +    threads = [ReftestThread(args) for args in perProcessArgs[1:]]
   1.281 +    for t in threads:
   1.282 +      t.start()
   1.283 +
   1.284 +    while True:
   1.285 +      # The test harness in each individual thread will be doing timeout
   1.286 +      # handling on its own, so we shouldn't need to worry about any of
   1.287 +      # the threads hanging for arbitrarily long.
   1.288 +      for t in threads:
   1.289 +        t.join(10)
   1.290 +      if not any(t.is_alive() for t in threads):
   1.291 +        break
   1.292 +
   1.293 +    # Run the needs-focus tests serially after the other ones, so we don't
   1.294 +    # have to worry about races between the needs-focus tests *actually*
   1.295 +    # needing focus and the dummy windows in the non-needs-focus tests
   1.296 +    # trying to focus themselves.
   1.297 +    focusThread = ReftestThread(perProcessArgs[0])
   1.298 +    focusThread.start()
   1.299 +    focusThread.join()
   1.300 +
   1.301 +    # Output the summaries that the ReftestThread filters suppressed.
   1.302 +    summaryObjects = [collections.defaultdict(int) for s in summaryLines]
   1.303 +    for t in threads:
   1.304 +      for (summaryObj, (text, categories)) in zip(summaryObjects, summaryLines):
   1.305 +        threadMatches = t.summaryMatches[text]
   1.306 +        for (attribute, description) in categories:
   1.307 +          amount = int(threadMatches.group(attribute) if threadMatches else 0)
   1.308 +          summaryObj[attribute] += amount
   1.309 +        amount = int(threadMatches.group('total') if threadMatches else 0)
   1.310 +        summaryObj['total'] += amount
   1.311 +
   1.312 +    print 'REFTEST INFO | Result summary:'
   1.313 +    for (summaryObj, (text, categories)) in zip(summaryObjects, summaryLines):
   1.314 +      details = ', '.join(["%d %s" % (summaryObj[attribute], description) for (attribute, description) in categories])
   1.315 +      print 'REFTEST INFO | ' + text + ': ' + str(summaryObj['total']) + ' (' +  details + ')'
   1.316 +
   1.317 +    return int(any(t.retcode != 0 for t in threads))
   1.318 +
   1.319 +  def runSerialTests(self, testPath, options, cmdlineArgs = None):
   1.320 +    debuggerInfo = getDebuggerInfo(self.oldcwd, options.debugger, options.debuggerArgs,
   1.321 +        options.debuggerInteractive);
   1.322 +
   1.323 +    profileDir = None
   1.324 +    try:
   1.325 +      reftestlist = self.getManifestPath(testPath)
   1.326 +      if cmdlineArgs == None:
   1.327 +        cmdlineArgs = ['-reftest', reftestlist]
   1.328 +      profile = self.createReftestProfile(options, reftestlist)
   1.329 +      profileDir = profile.profile # name makes more sense
   1.330 +
   1.331 +      # browser environment
   1.332 +      browserEnv = self.buildBrowserEnv(options, profileDir)
   1.333 +
   1.334 +      self.automation.log.info("REFTEST INFO | runreftest.py | Running tests: start.\n")
   1.335 +      status = self.automation.runApp(None, browserEnv, options.app, profileDir,
   1.336 +                                 cmdlineArgs,
   1.337 +                                 utilityPath = options.utilityPath,
   1.338 +                                 xrePath=options.xrePath,
   1.339 +                                 debuggerInfo=debuggerInfo,
   1.340 +                                 symbolsPath=options.symbolsPath,
   1.341 +                                 # give the JS harness 30 seconds to deal
   1.342 +                                 # with its own timeouts
   1.343 +                                 timeout=options.timeout + 30.0)
   1.344 +      processLeakLog(self.leakLogFile, options.leakThreshold)
   1.345 +      self.automation.log.info("\nREFTEST INFO | runreftest.py | Running tests: end.")
   1.346 +    finally:
   1.347 +      self.cleanup(profileDir)
   1.348 +    return status
   1.349 +
   1.350 +  def copyExtraFilesToProfile(self, options, profile):
   1.351 +    "Copy extra files or dirs specified on the command line to the testing profile."
   1.352 +    profileDir = profile.profile
   1.353 +    for f in options.extraProfileFiles:
   1.354 +      abspath = self.getFullPath(f)
   1.355 +      if os.path.isfile(abspath):
   1.356 +        if os.path.basename(abspath) == 'user.js':
   1.357 +          extra_prefs = mozprofile.Preferences.read_prefs(abspath)
   1.358 +          profile.set_preferences(extra_prefs)
   1.359 +        else:
   1.360 +          shutil.copy2(abspath, profileDir)
   1.361 +      elif os.path.isdir(abspath):
   1.362 +        dest = os.path.join(profileDir, os.path.basename(abspath))
   1.363 +        shutil.copytree(abspath, dest)
   1.364 +      else:
   1.365 +        self.automation.log.warning("WARNING | runreftest.py | Failed to copy %s to profile", abspath)
   1.366 +        continue
   1.367 +
   1.368 +
   1.369 +class ReftestOptions(OptionParser):
   1.370 +
   1.371 +  def __init__(self, automation=None):
   1.372 +    self.automation = automation or Automation()
   1.373 +    OptionParser.__init__(self)
   1.374 +    defaults = {}
   1.375 +
   1.376 +    # we want to pass down everything from automation.__all__
   1.377 +    addCommonOptions(self,
   1.378 +                     defaults=dict(zip(self.automation.__all__,
   1.379 +                            [getattr(self.automation, x) for x in self.automation.__all__])))
   1.380 +    self.automation.addCommonOptions(self)
   1.381 +    self.add_option("--appname",
   1.382 +                    action = "store", type = "string", dest = "app",
   1.383 +                    default = os.path.join(SCRIPT_DIRECTORY, automation.DEFAULT_APP),
   1.384 +                    help = "absolute path to application, overriding default")
   1.385 +    self.add_option("--extra-profile-file",
   1.386 +                    action = "append", dest = "extraProfileFiles",
   1.387 +                    default = [],
   1.388 +                    help = "copy specified files/dirs to testing profile")
   1.389 +    self.add_option("--timeout",
   1.390 +                    action = "store", dest = "timeout", type = "int",
   1.391 +                    default = 5 * 60, # 5 minutes per bug 479518
   1.392 +                    help = "reftest will timeout in specified number of seconds. [default %default s].")
   1.393 +    self.add_option("--leak-threshold",
   1.394 +                    action = "store", type = "int", dest = "leakThreshold",
   1.395 +                    default = 0,
   1.396 +                    help = "fail if the number of bytes leaked through "
   1.397 +                           "refcounted objects (or bytes in classes with "
   1.398 +                           "MOZ_COUNT_CTOR and MOZ_COUNT_DTOR) is greater "
   1.399 +                           "than the given number")
   1.400 +    self.add_option("--utility-path",
   1.401 +                    action = "store", type = "string", dest = "utilityPath",
   1.402 +                    default = self.automation.DIST_BIN,
   1.403 +                    help = "absolute path to directory containing utility "
   1.404 +                           "programs (xpcshell, ssltunnel, certutil)")
   1.405 +    defaults["utilityPath"] = self.automation.DIST_BIN
   1.406 +
   1.407 +    self.add_option("--total-chunks",
   1.408 +                    type = "int", dest = "totalChunks",
   1.409 +                    help = "how many chunks to split the tests up into")
   1.410 +    defaults["totalChunks"] = None
   1.411 +
   1.412 +    self.add_option("--this-chunk",
   1.413 +                    type = "int", dest = "thisChunk",
   1.414 +                    help = "which chunk to run between 1 and --total-chunks")
   1.415 +    defaults["thisChunk"] = None
   1.416 +
   1.417 +    self.add_option("--log-file",
   1.418 +                    action = "store", type = "string", dest = "logFile",
   1.419 +                    default = None,
   1.420 +                    help = "file to log output to in addition to stdout")
   1.421 +    defaults["logFile"] = None
   1.422 +
   1.423 +    self.add_option("--skip-slow-tests",
   1.424 +                    dest = "skipSlowTests", action = "store_true",
   1.425 +                    help = "skip tests marked as slow when running")
   1.426 +    defaults["skipSlowTests"] = False
   1.427 +
   1.428 +    self.add_option("--ignore-window-size",
   1.429 +                    dest = "ignoreWindowSize", action = "store_true",
   1.430 +                    help = "ignore the window size, which may cause spurious failures and passes")
   1.431 +    defaults["ignoreWindowSize"] = False
   1.432 +
   1.433 +    self.add_option("--install-extension",
   1.434 +                    action = "append", dest = "extensionsToInstall",
   1.435 +                    help = "install the specified extension in the testing profile. "
   1.436 +                           "The extension file's name should be <id>.xpi where <id> is "
   1.437 +                           "the extension's id as indicated in its install.rdf. "
   1.438 +                           "An optional path can be specified too.")
   1.439 +    defaults["extensionsToInstall"] = []
   1.440 +
   1.441 +    self.add_option("--run-tests-in-parallel",
   1.442 +                    action = "store_true", dest = "runTestsInParallel",
   1.443 +                    help = "run tests in parallel if possible")
   1.444 +    self.add_option("--no-run-tests-in-parallel",
   1.445 +                    action = "store_false", dest = "runTestsInParallel",
   1.446 +                    help = "do not run tests in parallel")
   1.447 +    defaults["runTestsInParallel"] = False
   1.448 +
   1.449 +    self.add_option("--setenv",
   1.450 +                    action = "append", type = "string",
   1.451 +                    dest = "environment", metavar = "NAME=VALUE",
   1.452 +                    help = "sets the given variable in the application's "
   1.453 +                           "environment")
   1.454 +    defaults["environment"] = []
   1.455 +
   1.456 +    self.add_option("--filter",
   1.457 +                    action = "store", type="string", dest = "filter",
   1.458 +                    help = "specifies a regular expression (as could be passed to the JS "
   1.459 +                           "RegExp constructor) to test against URLs in the reftest manifest; "
   1.460 +                           "only test items that have a matching test URL will be run.")
   1.461 +    defaults["filter"] = None
   1.462 +
   1.463 +    self.add_option("--shuffle",
   1.464 +                    action = "store_true", dest = "shuffle",
   1.465 +                    help = "run reftests in random order")
   1.466 +    defaults["shuffle"] = False
   1.467 +
   1.468 +    self.add_option("--focus-filter-mode",
   1.469 +                    action = "store", type = "string", dest = "focusFilterMode",
   1.470 +                    help = "filters tests to run by whether they require focus. "
   1.471 +                           "Valid values are `all', `needs-focus', or `non-needs-focus'. "
   1.472 +                           "Defaults to `all'.")
   1.473 +    defaults["focusFilterMode"] = "all"
   1.474 +
   1.475 +    self.add_option("--e10s",
   1.476 +                    action = "store_true",
   1.477 +                    dest = "e10s",
   1.478 +                    help = "enables content processes")
   1.479 +    defaults["e10s"] = False
   1.480 +
   1.481 +    self.set_defaults(**defaults)
   1.482 +
   1.483 +  def verifyCommonOptions(self, options, reftest):
   1.484 +    if options.totalChunks is not None and options.thisChunk is None:
   1.485 +      self.error("thisChunk must be specified when totalChunks is specified")
   1.486 +
   1.487 +    if options.totalChunks:
   1.488 +      if not 1 <= options.thisChunk <= options.totalChunks:
   1.489 +        self.error("thisChunk must be between 1 and totalChunks")
   1.490 +
   1.491 +    if options.logFile:
   1.492 +      options.logFile = reftest.getFullPath(options.logFile)
   1.493 +
   1.494 +    if options.xrePath is not None:
   1.495 +      if not os.access(options.xrePath, os.F_OK):
   1.496 +        self.error("--xre-path '%s' not found" % options.xrePath)
   1.497 +      if not os.path.isdir(options.xrePath):
   1.498 +        self.error("--xre-path '%s' is not a directory" % options.xrePath)
   1.499 +      options.xrePath = reftest.getFullPath(options.xrePath)
   1.500 +
   1.501 +    if options.runTestsInParallel:
   1.502 +      if options.logFile is not None:
   1.503 +        self.error("cannot specify logfile with parallel tests")
   1.504 +      if options.totalChunks is not None and options.thisChunk is None:
   1.505 +        self.error("cannot specify thisChunk or totalChunks with parallel tests")
   1.506 +      if options.focusFilterMode != "all":
   1.507 +        self.error("cannot specify focusFilterMode with parallel tests")
   1.508 +      if options.debugger is not None:
   1.509 +        self.error("cannot specify a debugger with parallel tests")
   1.510 +
   1.511 +    return options
   1.512 +
   1.513 +def main():
   1.514 +  automation = Automation()
   1.515 +  parser = ReftestOptions(automation)
   1.516 +  reftest = RefTest(automation)
   1.517 +
   1.518 +  options, args = parser.parse_args()
   1.519 +  if len(args) != 1:
   1.520 +    print >>sys.stderr, "No reftest.list specified."
   1.521 +    sys.exit(1)
   1.522 +
   1.523 +  options = parser.verifyCommonOptions(options, reftest)
   1.524 +
   1.525 +  options.app = reftest.getFullPath(options.app)
   1.526 +  if not os.path.exists(options.app):
   1.527 +    print """Error: Path %(app)s doesn't exist.
   1.528 +Are you executing $objdir/_tests/reftest/runreftest.py?""" \
   1.529 +            % {"app": options.app}
   1.530 +    sys.exit(1)
   1.531 +
   1.532 +  if options.xrePath is None:
   1.533 +    options.xrePath = os.path.dirname(options.app)
   1.534 +
   1.535 +  if options.symbolsPath and not isURL(options.symbolsPath):
   1.536 +    options.symbolsPath = reftest.getFullPath(options.symbolsPath)
   1.537 +  options.utilityPath = reftest.getFullPath(options.utilityPath)
   1.538 +
   1.539 +  sys.exit(reftest.runTests(args[0], options))
   1.540 +
   1.541 +if __name__ == "__main__":
   1.542 +  main()

mercurial