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()