Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
michael@0 | 1 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 4 | |
michael@0 | 5 | """ |
michael@0 | 6 | Runs the reftest test harness. |
michael@0 | 7 | """ |
michael@0 | 8 | |
michael@0 | 9 | from optparse import OptionParser |
michael@0 | 10 | import collections |
michael@0 | 11 | import json |
michael@0 | 12 | import multiprocessing |
michael@0 | 13 | import os |
michael@0 | 14 | import re |
michael@0 | 15 | import shutil |
michael@0 | 16 | import subprocess |
michael@0 | 17 | import sys |
michael@0 | 18 | import threading |
michael@0 | 19 | |
michael@0 | 20 | SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0]))) |
michael@0 | 21 | sys.path.insert(0, SCRIPT_DIRECTORY) |
michael@0 | 22 | |
michael@0 | 23 | from automation import Automation |
michael@0 | 24 | from automationutils import ( |
michael@0 | 25 | addCommonOptions, |
michael@0 | 26 | getDebuggerInfo, |
michael@0 | 27 | isURL, |
michael@0 | 28 | processLeakLog |
michael@0 | 29 | ) |
michael@0 | 30 | import mozprofile |
michael@0 | 31 | |
michael@0 | 32 | def categoriesToRegex(categoryList): |
michael@0 | 33 | return "\\(" + ', '.join(["(?P<%s>\\d+) %s" % c for c in categoryList]) + "\\)" |
michael@0 | 34 | summaryLines = [('Successful', [('pass', 'pass'), ('loadOnly', 'load only')]), |
michael@0 | 35 | ('Unexpected', [('fail', 'unexpected fail'), |
michael@0 | 36 | ('pass', 'unexpected pass'), |
michael@0 | 37 | ('asserts', 'unexpected asserts'), |
michael@0 | 38 | ('fixedAsserts', 'unexpected fixed asserts'), |
michael@0 | 39 | ('failedLoad', 'failed load'), |
michael@0 | 40 | ('exception', 'exception')]), |
michael@0 | 41 | ('Known problems', [('knownFail', 'known fail'), |
michael@0 | 42 | ('knownAsserts', 'known asserts'), |
michael@0 | 43 | ('random', 'random'), |
michael@0 | 44 | ('skipped', 'skipped'), |
michael@0 | 45 | ('slow', 'slow')])] |
michael@0 | 46 | |
michael@0 | 47 | # Python's print is not threadsafe. |
michael@0 | 48 | printLock = threading.Lock() |
michael@0 | 49 | |
michael@0 | 50 | class ReftestThread(threading.Thread): |
michael@0 | 51 | def __init__(self, cmdlineArgs): |
michael@0 | 52 | threading.Thread.__init__(self) |
michael@0 | 53 | self.cmdlineArgs = cmdlineArgs |
michael@0 | 54 | self.summaryMatches = {} |
michael@0 | 55 | self.retcode = -1 |
michael@0 | 56 | for text, _ in summaryLines: |
michael@0 | 57 | self.summaryMatches[text] = None |
michael@0 | 58 | |
michael@0 | 59 | def run(self): |
michael@0 | 60 | with printLock: |
michael@0 | 61 | print "Starting thread with", self.cmdlineArgs |
michael@0 | 62 | sys.stdout.flush() |
michael@0 | 63 | process = subprocess.Popen(self.cmdlineArgs, stdout=subprocess.PIPE) |
michael@0 | 64 | for chunk in self.chunkForMergedOutput(process.stdout): |
michael@0 | 65 | with printLock: |
michael@0 | 66 | print chunk, |
michael@0 | 67 | sys.stdout.flush() |
michael@0 | 68 | self.retcode = process.wait() |
michael@0 | 69 | |
michael@0 | 70 | def chunkForMergedOutput(self, logsource): |
michael@0 | 71 | """Gather lines together that should be printed as one atomic unit. |
michael@0 | 72 | Individual test results--anything between 'REFTEST TEST-START' and |
michael@0 | 73 | 'REFTEST TEST-END' lines--are an atomic unit. Lines with data from |
michael@0 | 74 | summaries are parsed and the data stored for later aggregation. |
michael@0 | 75 | Other lines are considered their own atomic units and are permitted |
michael@0 | 76 | to intermix freely.""" |
michael@0 | 77 | testStartRegex = re.compile("^REFTEST TEST-START") |
michael@0 | 78 | testEndRegex = re.compile("^REFTEST TEST-END") |
michael@0 | 79 | summaryHeadRegex = re.compile("^REFTEST INFO \\| Result summary:") |
michael@0 | 80 | summaryRegexFormatString = "^REFTEST INFO \\| (?P<message>{text}): (?P<total>\\d+) {regex}" |
michael@0 | 81 | summaryRegexStrings = [summaryRegexFormatString.format(text=text, |
michael@0 | 82 | regex=categoriesToRegex(categories)) |
michael@0 | 83 | for (text, categories) in summaryLines] |
michael@0 | 84 | summaryRegexes = [re.compile(regex) for regex in summaryRegexStrings] |
michael@0 | 85 | |
michael@0 | 86 | for line in logsource: |
michael@0 | 87 | if testStartRegex.search(line) is not None: |
michael@0 | 88 | chunkedLines = [line] |
michael@0 | 89 | for lineToBeChunked in logsource: |
michael@0 | 90 | chunkedLines.append(lineToBeChunked) |
michael@0 | 91 | if testEndRegex.search(lineToBeChunked) is not None: |
michael@0 | 92 | break |
michael@0 | 93 | yield ''.join(chunkedLines) |
michael@0 | 94 | continue |
michael@0 | 95 | |
michael@0 | 96 | haveSuppressedSummaryLine = False |
michael@0 | 97 | for regex in summaryRegexes: |
michael@0 | 98 | match = regex.search(line) |
michael@0 | 99 | if match is not None: |
michael@0 | 100 | self.summaryMatches[match.group('message')] = match |
michael@0 | 101 | haveSuppressedSummaryLine = True |
michael@0 | 102 | break |
michael@0 | 103 | if haveSuppressedSummaryLine: |
michael@0 | 104 | continue |
michael@0 | 105 | |
michael@0 | 106 | if summaryHeadRegex.search(line) is None: |
michael@0 | 107 | yield line |
michael@0 | 108 | |
michael@0 | 109 | class RefTest(object): |
michael@0 | 110 | |
michael@0 | 111 | oldcwd = os.getcwd() |
michael@0 | 112 | |
michael@0 | 113 | def __init__(self, automation=None): |
michael@0 | 114 | self.automation = automation or Automation() |
michael@0 | 115 | |
michael@0 | 116 | def getFullPath(self, path): |
michael@0 | 117 | "Get an absolute path relative to self.oldcwd." |
michael@0 | 118 | return os.path.normpath(os.path.join(self.oldcwd, os.path.expanduser(path))) |
michael@0 | 119 | |
michael@0 | 120 | def getManifestPath(self, path): |
michael@0 | 121 | "Get the path of the manifest, and for remote testing this function is subclassed to point to remote manifest" |
michael@0 | 122 | path = self.getFullPath(path) |
michael@0 | 123 | if os.path.isdir(path): |
michael@0 | 124 | defaultManifestPath = os.path.join(path, 'reftest.list') |
michael@0 | 125 | if os.path.exists(defaultManifestPath): |
michael@0 | 126 | path = defaultManifestPath |
michael@0 | 127 | else: |
michael@0 | 128 | defaultManifestPath = os.path.join(path, 'crashtests.list') |
michael@0 | 129 | if os.path.exists(defaultManifestPath): |
michael@0 | 130 | path = defaultManifestPath |
michael@0 | 131 | return path |
michael@0 | 132 | |
michael@0 | 133 | def makeJSString(self, s): |
michael@0 | 134 | return '"%s"' % re.sub(r'([\\"])', r'\\\1', s) |
michael@0 | 135 | |
michael@0 | 136 | def createReftestProfile(self, options, manifest, server='localhost', |
michael@0 | 137 | special_powers=True, profile_to_clone=None): |
michael@0 | 138 | """ |
michael@0 | 139 | Sets up a profile for reftest. |
michael@0 | 140 | 'manifest' is the path to the reftest.list file we want to test with. This is used in |
michael@0 | 141 | the remote subclass in remotereftest.py so we can write it to a preference for the |
michael@0 | 142 | bootstrap extension. |
michael@0 | 143 | """ |
michael@0 | 144 | |
michael@0 | 145 | locations = mozprofile.permissions.ServerLocations() |
michael@0 | 146 | locations.add_host(server, port=0) |
michael@0 | 147 | locations.add_host('<file>', port=0) |
michael@0 | 148 | |
michael@0 | 149 | # Set preferences for communication between our command line arguments |
michael@0 | 150 | # and the reftest harness. Preferences that are required for reftest |
michael@0 | 151 | # to work should instead be set in reftest-cmdline.js . |
michael@0 | 152 | prefs = {} |
michael@0 | 153 | prefs['reftest.timeout'] = options.timeout * 1000 |
michael@0 | 154 | if options.totalChunks: |
michael@0 | 155 | prefs['reftest.totalChunks'] = options.totalChunks |
michael@0 | 156 | if options.thisChunk: |
michael@0 | 157 | prefs['reftest.thisChunk'] = options.thisChunk |
michael@0 | 158 | if options.logFile: |
michael@0 | 159 | prefs['reftest.logFile'] = options.logFile |
michael@0 | 160 | if options.ignoreWindowSize: |
michael@0 | 161 | prefs['reftest.ignoreWindowSize'] = True |
michael@0 | 162 | if options.filter: |
michael@0 | 163 | prefs['reftest.filter'] = options.filter |
michael@0 | 164 | if options.shuffle: |
michael@0 | 165 | prefs['reftest.shuffle'] = True |
michael@0 | 166 | prefs['reftest.focusFilterMode'] = options.focusFilterMode |
michael@0 | 167 | |
michael@0 | 168 | # Ensure that telemetry is disabled, so we don't connect to the telemetry |
michael@0 | 169 | # server in the middle of the tests. |
michael@0 | 170 | prefs['toolkit.telemetry.enabled'] = False |
michael@0 | 171 | # Likewise for safebrowsing. |
michael@0 | 172 | prefs['browser.safebrowsing.enabled'] = False |
michael@0 | 173 | prefs['browser.safebrowsing.malware.enabled'] = False |
michael@0 | 174 | # And for snippets. |
michael@0 | 175 | prefs['browser.snippets.enabled'] = False |
michael@0 | 176 | prefs['browser.snippets.syncPromo.enabled'] = False |
michael@0 | 177 | # And for useragent updates. |
michael@0 | 178 | prefs['general.useragent.updates.enabled'] = False |
michael@0 | 179 | # And for webapp updates. Yes, it is supposed to be an integer. |
michael@0 | 180 | prefs['browser.webapps.checkForUpdates'] = 0 |
michael@0 | 181 | |
michael@0 | 182 | if options.e10s: |
michael@0 | 183 | prefs['browser.tabs.remote.autostart'] = True |
michael@0 | 184 | |
michael@0 | 185 | for v in options.extraPrefs: |
michael@0 | 186 | thispref = v.split('=') |
michael@0 | 187 | if len(thispref) < 2: |
michael@0 | 188 | print "Error: syntax error in --setpref=" + v |
michael@0 | 189 | sys.exit(1) |
michael@0 | 190 | prefs[thispref[0]] = mozprofile.Preferences.cast(thispref[1].strip()) |
michael@0 | 191 | |
michael@0 | 192 | # install the reftest extension bits into the profile |
michael@0 | 193 | addons = [] |
michael@0 | 194 | addons.append(os.path.join(SCRIPT_DIRECTORY, "reftest")) |
michael@0 | 195 | |
michael@0 | 196 | # I would prefer to use "--install-extension reftest/specialpowers", but that requires tight coordination with |
michael@0 | 197 | # release engineering and landing on multiple branches at once. |
michael@0 | 198 | if special_powers and (manifest.endswith('crashtests.list') or manifest.endswith('jstests.list')): |
michael@0 | 199 | addons.append(os.path.join(SCRIPT_DIRECTORY, 'specialpowers')) |
michael@0 | 200 | |
michael@0 | 201 | # Install distributed extensions, if application has any. |
michael@0 | 202 | distExtDir = os.path.join(options.app[ : options.app.rfind(os.sep)], "distribution", "extensions") |
michael@0 | 203 | if os.path.isdir(distExtDir): |
michael@0 | 204 | for f in os.listdir(distExtDir): |
michael@0 | 205 | addons.append(os.path.join(distExtDir, f)) |
michael@0 | 206 | |
michael@0 | 207 | # Install custom extensions. |
michael@0 | 208 | for f in options.extensionsToInstall: |
michael@0 | 209 | addons.append(self.getFullPath(f)) |
michael@0 | 210 | |
michael@0 | 211 | kwargs = { 'addons': addons, |
michael@0 | 212 | 'preferences': prefs, |
michael@0 | 213 | 'locations': locations } |
michael@0 | 214 | if profile_to_clone: |
michael@0 | 215 | profile = mozprofile.Profile.clone(profile_to_clone, **kwargs) |
michael@0 | 216 | else: |
michael@0 | 217 | profile = mozprofile.Profile(**kwargs) |
michael@0 | 218 | |
michael@0 | 219 | self.copyExtraFilesToProfile(options, profile) |
michael@0 | 220 | return profile |
michael@0 | 221 | |
michael@0 | 222 | def buildBrowserEnv(self, options, profileDir): |
michael@0 | 223 | browserEnv = self.automation.environment(xrePath = options.xrePath, debugger=options.debugger) |
michael@0 | 224 | browserEnv["XPCOM_DEBUG_BREAK"] = "stack" |
michael@0 | 225 | |
michael@0 | 226 | for v in options.environment: |
michael@0 | 227 | ix = v.find("=") |
michael@0 | 228 | if ix <= 0: |
michael@0 | 229 | print "Error: syntax error in --setenv=" + v |
michael@0 | 230 | return None |
michael@0 | 231 | browserEnv[v[:ix]] = v[ix + 1:] |
michael@0 | 232 | |
michael@0 | 233 | # Enable leaks detection to its own log file. |
michael@0 | 234 | self.leakLogFile = os.path.join(profileDir, "runreftest_leaks.log") |
michael@0 | 235 | browserEnv["XPCOM_MEM_BLOAT_LOG"] = self.leakLogFile |
michael@0 | 236 | return browserEnv |
michael@0 | 237 | |
michael@0 | 238 | def cleanup(self, profileDir): |
michael@0 | 239 | if profileDir: |
michael@0 | 240 | shutil.rmtree(profileDir, True) |
michael@0 | 241 | |
michael@0 | 242 | def runTests(self, testPath, options, cmdlineArgs = None): |
michael@0 | 243 | if not options.runTestsInParallel: |
michael@0 | 244 | return self.runSerialTests(testPath, options, cmdlineArgs) |
michael@0 | 245 | |
michael@0 | 246 | cpuCount = multiprocessing.cpu_count() |
michael@0 | 247 | |
michael@0 | 248 | # We have the directive, technology, and machine to run multiple test instances. |
michael@0 | 249 | # Experimentation says that reftests are not overly CPU-intensive, so we can run |
michael@0 | 250 | # multiple jobs per CPU core. |
michael@0 | 251 | # |
michael@0 | 252 | # Our Windows machines in automation seem to get upset when we run a lot of |
michael@0 | 253 | # simultaneous tests on them, so tone things down there. |
michael@0 | 254 | if sys.platform == 'win32': |
michael@0 | 255 | jobsWithoutFocus = cpuCount |
michael@0 | 256 | else: |
michael@0 | 257 | jobsWithoutFocus = 2 * cpuCount |
michael@0 | 258 | |
michael@0 | 259 | totalJobs = jobsWithoutFocus + 1 |
michael@0 | 260 | perProcessArgs = [sys.argv[:] for i in range(0, totalJobs)] |
michael@0 | 261 | |
michael@0 | 262 | # First job is only needs-focus tests. Remaining jobs are non-needs-focus and chunked. |
michael@0 | 263 | perProcessArgs[0].insert(-1, "--focus-filter-mode=needs-focus") |
michael@0 | 264 | for (chunkNumber, jobArgs) in enumerate(perProcessArgs[1:], start=1): |
michael@0 | 265 | jobArgs[-1:-1] = ["--focus-filter-mode=non-needs-focus", |
michael@0 | 266 | "--total-chunks=%d" % jobsWithoutFocus, |
michael@0 | 267 | "--this-chunk=%d" % chunkNumber] |
michael@0 | 268 | |
michael@0 | 269 | for jobArgs in perProcessArgs: |
michael@0 | 270 | try: |
michael@0 | 271 | jobArgs.remove("--run-tests-in-parallel") |
michael@0 | 272 | except: |
michael@0 | 273 | pass |
michael@0 | 274 | jobArgs.insert(-1, "--no-run-tests-in-parallel") |
michael@0 | 275 | jobArgs[0:0] = [sys.executable, "-u"] |
michael@0 | 276 | |
michael@0 | 277 | threads = [ReftestThread(args) for args in perProcessArgs[1:]] |
michael@0 | 278 | for t in threads: |
michael@0 | 279 | t.start() |
michael@0 | 280 | |
michael@0 | 281 | while True: |
michael@0 | 282 | # The test harness in each individual thread will be doing timeout |
michael@0 | 283 | # handling on its own, so we shouldn't need to worry about any of |
michael@0 | 284 | # the threads hanging for arbitrarily long. |
michael@0 | 285 | for t in threads: |
michael@0 | 286 | t.join(10) |
michael@0 | 287 | if not any(t.is_alive() for t in threads): |
michael@0 | 288 | break |
michael@0 | 289 | |
michael@0 | 290 | # Run the needs-focus tests serially after the other ones, so we don't |
michael@0 | 291 | # have to worry about races between the needs-focus tests *actually* |
michael@0 | 292 | # needing focus and the dummy windows in the non-needs-focus tests |
michael@0 | 293 | # trying to focus themselves. |
michael@0 | 294 | focusThread = ReftestThread(perProcessArgs[0]) |
michael@0 | 295 | focusThread.start() |
michael@0 | 296 | focusThread.join() |
michael@0 | 297 | |
michael@0 | 298 | # Output the summaries that the ReftestThread filters suppressed. |
michael@0 | 299 | summaryObjects = [collections.defaultdict(int) for s in summaryLines] |
michael@0 | 300 | for t in threads: |
michael@0 | 301 | for (summaryObj, (text, categories)) in zip(summaryObjects, summaryLines): |
michael@0 | 302 | threadMatches = t.summaryMatches[text] |
michael@0 | 303 | for (attribute, description) in categories: |
michael@0 | 304 | amount = int(threadMatches.group(attribute) if threadMatches else 0) |
michael@0 | 305 | summaryObj[attribute] += amount |
michael@0 | 306 | amount = int(threadMatches.group('total') if threadMatches else 0) |
michael@0 | 307 | summaryObj['total'] += amount |
michael@0 | 308 | |
michael@0 | 309 | print 'REFTEST INFO | Result summary:' |
michael@0 | 310 | for (summaryObj, (text, categories)) in zip(summaryObjects, summaryLines): |
michael@0 | 311 | details = ', '.join(["%d %s" % (summaryObj[attribute], description) for (attribute, description) in categories]) |
michael@0 | 312 | print 'REFTEST INFO | ' + text + ': ' + str(summaryObj['total']) + ' (' + details + ')' |
michael@0 | 313 | |
michael@0 | 314 | return int(any(t.retcode != 0 for t in threads)) |
michael@0 | 315 | |
michael@0 | 316 | def runSerialTests(self, testPath, options, cmdlineArgs = None): |
michael@0 | 317 | debuggerInfo = getDebuggerInfo(self.oldcwd, options.debugger, options.debuggerArgs, |
michael@0 | 318 | options.debuggerInteractive); |
michael@0 | 319 | |
michael@0 | 320 | profileDir = None |
michael@0 | 321 | try: |
michael@0 | 322 | reftestlist = self.getManifestPath(testPath) |
michael@0 | 323 | if cmdlineArgs == None: |
michael@0 | 324 | cmdlineArgs = ['-reftest', reftestlist] |
michael@0 | 325 | profile = self.createReftestProfile(options, reftestlist) |
michael@0 | 326 | profileDir = profile.profile # name makes more sense |
michael@0 | 327 | |
michael@0 | 328 | # browser environment |
michael@0 | 329 | browserEnv = self.buildBrowserEnv(options, profileDir) |
michael@0 | 330 | |
michael@0 | 331 | self.automation.log.info("REFTEST INFO | runreftest.py | Running tests: start.\n") |
michael@0 | 332 | status = self.automation.runApp(None, browserEnv, options.app, profileDir, |
michael@0 | 333 | cmdlineArgs, |
michael@0 | 334 | utilityPath = options.utilityPath, |
michael@0 | 335 | xrePath=options.xrePath, |
michael@0 | 336 | debuggerInfo=debuggerInfo, |
michael@0 | 337 | symbolsPath=options.symbolsPath, |
michael@0 | 338 | # give the JS harness 30 seconds to deal |
michael@0 | 339 | # with its own timeouts |
michael@0 | 340 | timeout=options.timeout + 30.0) |
michael@0 | 341 | processLeakLog(self.leakLogFile, options.leakThreshold) |
michael@0 | 342 | self.automation.log.info("\nREFTEST INFO | runreftest.py | Running tests: end.") |
michael@0 | 343 | finally: |
michael@0 | 344 | self.cleanup(profileDir) |
michael@0 | 345 | return status |
michael@0 | 346 | |
michael@0 | 347 | def copyExtraFilesToProfile(self, options, profile): |
michael@0 | 348 | "Copy extra files or dirs specified on the command line to the testing profile." |
michael@0 | 349 | profileDir = profile.profile |
michael@0 | 350 | for f in options.extraProfileFiles: |
michael@0 | 351 | abspath = self.getFullPath(f) |
michael@0 | 352 | if os.path.isfile(abspath): |
michael@0 | 353 | if os.path.basename(abspath) == 'user.js': |
michael@0 | 354 | extra_prefs = mozprofile.Preferences.read_prefs(abspath) |
michael@0 | 355 | profile.set_preferences(extra_prefs) |
michael@0 | 356 | else: |
michael@0 | 357 | shutil.copy2(abspath, profileDir) |
michael@0 | 358 | elif os.path.isdir(abspath): |
michael@0 | 359 | dest = os.path.join(profileDir, os.path.basename(abspath)) |
michael@0 | 360 | shutil.copytree(abspath, dest) |
michael@0 | 361 | else: |
michael@0 | 362 | self.automation.log.warning("WARNING | runreftest.py | Failed to copy %s to profile", abspath) |
michael@0 | 363 | continue |
michael@0 | 364 | |
michael@0 | 365 | |
michael@0 | 366 | class ReftestOptions(OptionParser): |
michael@0 | 367 | |
michael@0 | 368 | def __init__(self, automation=None): |
michael@0 | 369 | self.automation = automation or Automation() |
michael@0 | 370 | OptionParser.__init__(self) |
michael@0 | 371 | defaults = {} |
michael@0 | 372 | |
michael@0 | 373 | # we want to pass down everything from automation.__all__ |
michael@0 | 374 | addCommonOptions(self, |
michael@0 | 375 | defaults=dict(zip(self.automation.__all__, |
michael@0 | 376 | [getattr(self.automation, x) for x in self.automation.__all__]))) |
michael@0 | 377 | self.automation.addCommonOptions(self) |
michael@0 | 378 | self.add_option("--appname", |
michael@0 | 379 | action = "store", type = "string", dest = "app", |
michael@0 | 380 | default = os.path.join(SCRIPT_DIRECTORY, automation.DEFAULT_APP), |
michael@0 | 381 | help = "absolute path to application, overriding default") |
michael@0 | 382 | self.add_option("--extra-profile-file", |
michael@0 | 383 | action = "append", dest = "extraProfileFiles", |
michael@0 | 384 | default = [], |
michael@0 | 385 | help = "copy specified files/dirs to testing profile") |
michael@0 | 386 | self.add_option("--timeout", |
michael@0 | 387 | action = "store", dest = "timeout", type = "int", |
michael@0 | 388 | default = 5 * 60, # 5 minutes per bug 479518 |
michael@0 | 389 | help = "reftest will timeout in specified number of seconds. [default %default s].") |
michael@0 | 390 | self.add_option("--leak-threshold", |
michael@0 | 391 | action = "store", type = "int", dest = "leakThreshold", |
michael@0 | 392 | default = 0, |
michael@0 | 393 | help = "fail if the number of bytes leaked through " |
michael@0 | 394 | "refcounted objects (or bytes in classes with " |
michael@0 | 395 | "MOZ_COUNT_CTOR and MOZ_COUNT_DTOR) is greater " |
michael@0 | 396 | "than the given number") |
michael@0 | 397 | self.add_option("--utility-path", |
michael@0 | 398 | action = "store", type = "string", dest = "utilityPath", |
michael@0 | 399 | default = self.automation.DIST_BIN, |
michael@0 | 400 | help = "absolute path to directory containing utility " |
michael@0 | 401 | "programs (xpcshell, ssltunnel, certutil)") |
michael@0 | 402 | defaults["utilityPath"] = self.automation.DIST_BIN |
michael@0 | 403 | |
michael@0 | 404 | self.add_option("--total-chunks", |
michael@0 | 405 | type = "int", dest = "totalChunks", |
michael@0 | 406 | help = "how many chunks to split the tests up into") |
michael@0 | 407 | defaults["totalChunks"] = None |
michael@0 | 408 | |
michael@0 | 409 | self.add_option("--this-chunk", |
michael@0 | 410 | type = "int", dest = "thisChunk", |
michael@0 | 411 | help = "which chunk to run between 1 and --total-chunks") |
michael@0 | 412 | defaults["thisChunk"] = None |
michael@0 | 413 | |
michael@0 | 414 | self.add_option("--log-file", |
michael@0 | 415 | action = "store", type = "string", dest = "logFile", |
michael@0 | 416 | default = None, |
michael@0 | 417 | help = "file to log output to in addition to stdout") |
michael@0 | 418 | defaults["logFile"] = None |
michael@0 | 419 | |
michael@0 | 420 | self.add_option("--skip-slow-tests", |
michael@0 | 421 | dest = "skipSlowTests", action = "store_true", |
michael@0 | 422 | help = "skip tests marked as slow when running") |
michael@0 | 423 | defaults["skipSlowTests"] = False |
michael@0 | 424 | |
michael@0 | 425 | self.add_option("--ignore-window-size", |
michael@0 | 426 | dest = "ignoreWindowSize", action = "store_true", |
michael@0 | 427 | help = "ignore the window size, which may cause spurious failures and passes") |
michael@0 | 428 | defaults["ignoreWindowSize"] = False |
michael@0 | 429 | |
michael@0 | 430 | self.add_option("--install-extension", |
michael@0 | 431 | action = "append", dest = "extensionsToInstall", |
michael@0 | 432 | help = "install the specified extension in the testing profile. " |
michael@0 | 433 | "The extension file's name should be <id>.xpi where <id> is " |
michael@0 | 434 | "the extension's id as indicated in its install.rdf. " |
michael@0 | 435 | "An optional path can be specified too.") |
michael@0 | 436 | defaults["extensionsToInstall"] = [] |
michael@0 | 437 | |
michael@0 | 438 | self.add_option("--run-tests-in-parallel", |
michael@0 | 439 | action = "store_true", dest = "runTestsInParallel", |
michael@0 | 440 | help = "run tests in parallel if possible") |
michael@0 | 441 | self.add_option("--no-run-tests-in-parallel", |
michael@0 | 442 | action = "store_false", dest = "runTestsInParallel", |
michael@0 | 443 | help = "do not run tests in parallel") |
michael@0 | 444 | defaults["runTestsInParallel"] = False |
michael@0 | 445 | |
michael@0 | 446 | self.add_option("--setenv", |
michael@0 | 447 | action = "append", type = "string", |
michael@0 | 448 | dest = "environment", metavar = "NAME=VALUE", |
michael@0 | 449 | help = "sets the given variable in the application's " |
michael@0 | 450 | "environment") |
michael@0 | 451 | defaults["environment"] = [] |
michael@0 | 452 | |
michael@0 | 453 | self.add_option("--filter", |
michael@0 | 454 | action = "store", type="string", dest = "filter", |
michael@0 | 455 | help = "specifies a regular expression (as could be passed to the JS " |
michael@0 | 456 | "RegExp constructor) to test against URLs in the reftest manifest; " |
michael@0 | 457 | "only test items that have a matching test URL will be run.") |
michael@0 | 458 | defaults["filter"] = None |
michael@0 | 459 | |
michael@0 | 460 | self.add_option("--shuffle", |
michael@0 | 461 | action = "store_true", dest = "shuffle", |
michael@0 | 462 | help = "run reftests in random order") |
michael@0 | 463 | defaults["shuffle"] = False |
michael@0 | 464 | |
michael@0 | 465 | self.add_option("--focus-filter-mode", |
michael@0 | 466 | action = "store", type = "string", dest = "focusFilterMode", |
michael@0 | 467 | help = "filters tests to run by whether they require focus. " |
michael@0 | 468 | "Valid values are `all', `needs-focus', or `non-needs-focus'. " |
michael@0 | 469 | "Defaults to `all'.") |
michael@0 | 470 | defaults["focusFilterMode"] = "all" |
michael@0 | 471 | |
michael@0 | 472 | self.add_option("--e10s", |
michael@0 | 473 | action = "store_true", |
michael@0 | 474 | dest = "e10s", |
michael@0 | 475 | help = "enables content processes") |
michael@0 | 476 | defaults["e10s"] = False |
michael@0 | 477 | |
michael@0 | 478 | self.set_defaults(**defaults) |
michael@0 | 479 | |
michael@0 | 480 | def verifyCommonOptions(self, options, reftest): |
michael@0 | 481 | if options.totalChunks is not None and options.thisChunk is None: |
michael@0 | 482 | self.error("thisChunk must be specified when totalChunks is specified") |
michael@0 | 483 | |
michael@0 | 484 | if options.totalChunks: |
michael@0 | 485 | if not 1 <= options.thisChunk <= options.totalChunks: |
michael@0 | 486 | self.error("thisChunk must be between 1 and totalChunks") |
michael@0 | 487 | |
michael@0 | 488 | if options.logFile: |
michael@0 | 489 | options.logFile = reftest.getFullPath(options.logFile) |
michael@0 | 490 | |
michael@0 | 491 | if options.xrePath is not None: |
michael@0 | 492 | if not os.access(options.xrePath, os.F_OK): |
michael@0 | 493 | self.error("--xre-path '%s' not found" % options.xrePath) |
michael@0 | 494 | if not os.path.isdir(options.xrePath): |
michael@0 | 495 | self.error("--xre-path '%s' is not a directory" % options.xrePath) |
michael@0 | 496 | options.xrePath = reftest.getFullPath(options.xrePath) |
michael@0 | 497 | |
michael@0 | 498 | if options.runTestsInParallel: |
michael@0 | 499 | if options.logFile is not None: |
michael@0 | 500 | self.error("cannot specify logfile with parallel tests") |
michael@0 | 501 | if options.totalChunks is not None and options.thisChunk is None: |
michael@0 | 502 | self.error("cannot specify thisChunk or totalChunks with parallel tests") |
michael@0 | 503 | if options.focusFilterMode != "all": |
michael@0 | 504 | self.error("cannot specify focusFilterMode with parallel tests") |
michael@0 | 505 | if options.debugger is not None: |
michael@0 | 506 | self.error("cannot specify a debugger with parallel tests") |
michael@0 | 507 | |
michael@0 | 508 | return options |
michael@0 | 509 | |
michael@0 | 510 | def main(): |
michael@0 | 511 | automation = Automation() |
michael@0 | 512 | parser = ReftestOptions(automation) |
michael@0 | 513 | reftest = RefTest(automation) |
michael@0 | 514 | |
michael@0 | 515 | options, args = parser.parse_args() |
michael@0 | 516 | if len(args) != 1: |
michael@0 | 517 | print >>sys.stderr, "No reftest.list specified." |
michael@0 | 518 | sys.exit(1) |
michael@0 | 519 | |
michael@0 | 520 | options = parser.verifyCommonOptions(options, reftest) |
michael@0 | 521 | |
michael@0 | 522 | options.app = reftest.getFullPath(options.app) |
michael@0 | 523 | if not os.path.exists(options.app): |
michael@0 | 524 | print """Error: Path %(app)s doesn't exist. |
michael@0 | 525 | Are you executing $objdir/_tests/reftest/runreftest.py?""" \ |
michael@0 | 526 | % {"app": options.app} |
michael@0 | 527 | sys.exit(1) |
michael@0 | 528 | |
michael@0 | 529 | if options.xrePath is None: |
michael@0 | 530 | options.xrePath = os.path.dirname(options.app) |
michael@0 | 531 | |
michael@0 | 532 | if options.symbolsPath and not isURL(options.symbolsPath): |
michael@0 | 533 | options.symbolsPath = reftest.getFullPath(options.symbolsPath) |
michael@0 | 534 | options.utilityPath = reftest.getFullPath(options.utilityPath) |
michael@0 | 535 | |
michael@0 | 536 | sys.exit(reftest.runTests(args[0], options)) |
michael@0 | 537 | |
michael@0 | 538 | if __name__ == "__main__": |
michael@0 | 539 | main() |