зеркало из https://github.com/mozilla/pjs.git
610 строки
23 KiB
Python
610 строки
23 KiB
Python
#
|
|
# ***** BEGIN LICENSE BLOCK *****
|
|
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
#
|
|
# The contents of this file are subject to the Mozilla Public License Version
|
|
# 1.1 (the "License"); you may not use this file except in compliance with
|
|
# the License. You may obtain a copy of the License at
|
|
# http://www.mozilla.org/MPL/
|
|
#
|
|
# Software distributed under the License is distributed on an "AS IS" basis,
|
|
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
# for the specific language governing rights and limitations under the
|
|
# License.
|
|
#
|
|
# The Original Code is mozilla.org code.
|
|
#
|
|
# The Initial Developer of the Original Code is
|
|
# Mozilla Foundation.
|
|
# Portions created by the Initial Developer are Copyright (C) 1998
|
|
# the Initial Developer. All Rights Reserved.
|
|
#
|
|
# Contributor(s):
|
|
# Robert Sayre <sayrer@gmail.com>
|
|
# Jeff Walden <jwalden+bmo@mit.edu>
|
|
# Serge Gautherie <sgautherie.bz@free.fr>
|
|
#
|
|
# Alternatively, the contents of this file may be used under the terms of
|
|
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
# in which case the provisions of the GPL or the LGPL are applicable instead
|
|
# of those above. If you wish to allow use of your version of this file only
|
|
# under the terms of either the GPL or the LGPL, and not to allow others to
|
|
# use your version of this file under the terms of the MPL, indicate your
|
|
# decision by deleting the provisions above and replace them with the notice
|
|
# and other provisions required by the GPL or the LGPL. If you do not delete
|
|
# the provisions above, a recipient may use your version of this file under
|
|
# the terms of any one of the MPL, the GPL or the LGPL.
|
|
#
|
|
# ***** END LICENSE BLOCK *****
|
|
|
|
"""
|
|
Runs the Mochitest test harness.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
import optparse
|
|
import os
|
|
import os.path
|
|
import sys
|
|
import time
|
|
import shutil
|
|
from urllib import quote_plus as encodeURIComponent
|
|
import urllib2
|
|
import commands
|
|
from automation import Automation
|
|
from automationutils import *
|
|
import tempfile
|
|
|
|
#######################
|
|
# COMMANDLINE OPTIONS #
|
|
#######################
|
|
|
|
class MochitestOptions(optparse.OptionParser):
|
|
"""Parses Mochitest commandline options."""
|
|
def __init__(self, automation, scriptdir, **kwargs):
|
|
self._automation = automation
|
|
optparse.OptionParser.__init__(self, **kwargs)
|
|
defaults = {}
|
|
|
|
# we want to pass down everything from self._automation.__all__
|
|
addCommonOptions(self, defaults=dict(zip(self._automation.__all__,
|
|
[getattr(self._automation, x) for x in self._automation.__all__])))
|
|
self._automation.addCommonOptions(self)
|
|
|
|
self.add_option("--close-when-done",
|
|
action = "store_true", dest = "closeWhenDone",
|
|
help = "close the application when tests are done running")
|
|
defaults["closeWhenDone"] = False
|
|
|
|
self.add_option("--appname",
|
|
action = "store", type = "string", dest = "app",
|
|
help = "absolute path to application, overriding default")
|
|
defaults["app"] = os.path.join(scriptdir, self._automation.DEFAULT_APP)
|
|
|
|
self.add_option("--utility-path",
|
|
action = "store", type = "string", dest = "utilityPath",
|
|
help = "absolute path to directory containing utility programs (xpcshell, ssltunnel, certutil)")
|
|
defaults["utilityPath"] = self._automation.DIST_BIN
|
|
|
|
self.add_option("--certificate-path",
|
|
action = "store", type = "string", dest = "certPath",
|
|
help = "absolute path to directory containing certificate store to use testing profile")
|
|
defaults["certPath"] = self._automation.CERTS_SRC_DIR
|
|
|
|
self.add_option("--log-file",
|
|
action = "store", type = "string",
|
|
dest = "logFile", metavar = "FILE",
|
|
help = "file to which logging occurs")
|
|
defaults["logFile"] = ""
|
|
|
|
self.add_option("--autorun",
|
|
action = "store_true", dest = "autorun",
|
|
help = "start running tests when the application starts")
|
|
defaults["autorun"] = False
|
|
|
|
self.add_option("--timeout",
|
|
type = "int", dest = "timeout",
|
|
help = "per-test timeout in seconds")
|
|
defaults["timeout"] = None
|
|
|
|
self.add_option("--total-chunks",
|
|
type = "int", dest = "totalChunks",
|
|
help = "how many chunks to split the tests up into")
|
|
defaults["totalChunks"] = None
|
|
|
|
self.add_option("--this-chunk",
|
|
type = "int", dest = "thisChunk",
|
|
help = "which chunk to run")
|
|
defaults["thisChunk"] = None
|
|
|
|
self.add_option("--chunk-by-dir",
|
|
type = "int", dest = "chunkByDir",
|
|
help = "group tests together in the same chunk that are in the same top chunkByDir directories")
|
|
defaults["chunkByDir"] = 0
|
|
|
|
self.add_option("--shuffle",
|
|
dest = "shuffle",
|
|
action = "store_true",
|
|
help = "randomize test order")
|
|
defaults["shuffle"] = False
|
|
|
|
LOG_LEVELS = ("DEBUG", "INFO", "WARNING", "ERROR", "FATAL")
|
|
LEVEL_STRING = ", ".join(LOG_LEVELS)
|
|
|
|
self.add_option("--console-level",
|
|
action = "store", type = "choice", dest = "consoleLevel",
|
|
choices = LOG_LEVELS, metavar = "LEVEL",
|
|
help = "one of %s to determine the level of console "
|
|
"logging" % LEVEL_STRING)
|
|
defaults["consoleLevel"] = None
|
|
|
|
self.add_option("--file-level",
|
|
action = "store", type = "choice", dest = "fileLevel",
|
|
choices = LOG_LEVELS, metavar = "LEVEL",
|
|
help = "one of %s to determine the level of file "
|
|
"logging if a file has been specified, defaulting "
|
|
"to INFO" % LEVEL_STRING)
|
|
defaults["fileLevel"] = "INFO"
|
|
|
|
self.add_option("--chrome",
|
|
action = "store_true", dest = "chrome",
|
|
help = "run chrome Mochitests")
|
|
defaults["chrome"] = False
|
|
|
|
self.add_option("--test-path",
|
|
action = "store", type = "string", dest = "testPath",
|
|
help = "start in the given directory's tests")
|
|
defaults["testPath"] = ""
|
|
|
|
self.add_option("--browser-chrome",
|
|
action = "store_true", dest = "browserChrome",
|
|
help = "run browser chrome Mochitests")
|
|
defaults["browserChrome"] = False
|
|
|
|
self.add_option("--a11y",
|
|
action = "store_true", dest = "a11y",
|
|
help = "run accessibility Mochitests");
|
|
|
|
self.add_option("--setenv",
|
|
action = "append", type = "string",
|
|
dest = "environment", metavar = "NAME=VALUE",
|
|
help = "sets the given variable in the application's "
|
|
"environment")
|
|
defaults["environment"] = []
|
|
|
|
self.add_option("--browser-arg",
|
|
action = "append", type = "string",
|
|
dest = "browserArgs", metavar = "ARG",
|
|
help = "provides an argument to the test application")
|
|
defaults["browserArgs"] = []
|
|
|
|
self.add_option("--leak-threshold",
|
|
action = "store", type = "int",
|
|
dest = "leakThreshold", metavar = "THRESHOLD",
|
|
help = "fail if the number of bytes leaked through "
|
|
"refcounted objects (or bytes in classes with "
|
|
"MOZ_COUNT_CTOR and MOZ_COUNT_DTOR) is greater "
|
|
"than the given number")
|
|
defaults["leakThreshold"] = 0
|
|
|
|
self.add_option("--fatal-assertions",
|
|
action = "store_true", dest = "fatalAssertions",
|
|
help = "abort testing whenever an assertion is hit "
|
|
"(requires a debug build to be effective)")
|
|
defaults["fatalAssertions"] = False
|
|
|
|
self.add_option("--extra-profile-file",
|
|
action = "append", dest = "extraProfileFiles",
|
|
help = "copy specified files/dirs to testing profile")
|
|
defaults["extraProfileFiles"] = []
|
|
|
|
# -h, --help are automatically handled by OptionParser
|
|
|
|
self.set_defaults(**defaults)
|
|
|
|
usage = """\
|
|
Usage instructions for runtests.py.
|
|
All arguments are optional.
|
|
If --chrome is specified, chrome tests will be run instead of web content tests.
|
|
If --browser-chrome is specified, browser-chrome tests will be run instead of web content tests.
|
|
See <http://mochikit.com/doc/html/MochiKit/Logging.html> for details on the logging levels."""
|
|
self.set_usage(usage)
|
|
|
|
|
|
#######################
|
|
# HTTP SERVER SUPPORT #
|
|
#######################
|
|
|
|
class MochitestServer:
|
|
"Web server used to serve Mochitests, for closer fidelity to the real web."
|
|
|
|
def __init__(self, automation, options, profileDir, shutdownURL):
|
|
self._automation = automation
|
|
self._closeWhenDone = options.closeWhenDone
|
|
self._utilityPath = options.utilityPath
|
|
self._xrePath = options.xrePath
|
|
self._profileDir = profileDir
|
|
self.shutdownURL = shutdownURL
|
|
|
|
def start(self):
|
|
"Run the Mochitest server, returning the process ID of the server."
|
|
|
|
env = self._automation.environment(xrePath = self._xrePath)
|
|
env["XPCOM_DEBUG_BREAK"] = "warn"
|
|
if self._automation.IS_WIN32:
|
|
env["PATH"] = env["PATH"] + ";" + self._xrePath
|
|
|
|
args = ["-g", self._xrePath,
|
|
"-v", "170",
|
|
"-f", "./" + "httpd.js",
|
|
"-f", "./" + "server.js"]
|
|
|
|
xpcshell = os.path.join(self._utilityPath,
|
|
"xpcshell" + self._automation.BIN_SUFFIX)
|
|
self._process = self._automation.Process([xpcshell] + args, env = env)
|
|
pid = self._process.pid
|
|
if pid < 0:
|
|
print "Error starting server."
|
|
sys.exit(2)
|
|
self._automation.log.info("INFO | runtests.py | Server pid: %d", pid)
|
|
|
|
def ensureReady(self, timeout):
|
|
assert timeout >= 0
|
|
|
|
aliveFile = os.path.join(self._profileDir, "server_alive.txt")
|
|
i = 0
|
|
while i < timeout:
|
|
if os.path.exists(aliveFile):
|
|
break
|
|
time.sleep(1)
|
|
i += 1
|
|
else:
|
|
print "Timed out while waiting for server startup."
|
|
self.stop()
|
|
sys.exit(1)
|
|
|
|
def stop(self):
|
|
try:
|
|
c = urllib2.urlopen(self.shutdownURL)
|
|
c.read()
|
|
c.close()
|
|
self._process.wait()
|
|
except:
|
|
self._process.kill()
|
|
|
|
|
|
class Mochitest(object):
|
|
# Path to the test script on the server
|
|
TEST_SERVER_HOST = "localhost:8888"
|
|
TEST_PATH = "/tests/"
|
|
CHROME_PATH = "/redirect.html";
|
|
A11Y_PATH = "/redirect-a11y.html"
|
|
urlOpts = []
|
|
runSSLTunnel = True
|
|
|
|
oldcwd = os.getcwd()
|
|
|
|
def __init__(self, automation):
|
|
self.automation = automation
|
|
|
|
# Max time in seconds to wait for server startup before tests will fail -- if
|
|
# this seems big, it's mostly for debug machines where cold startup
|
|
# (particularly after a build) takes forever.
|
|
if self.automation.IS_DEBUG_BUILD:
|
|
self.SERVER_STARTUP_TIMEOUT = 180
|
|
else:
|
|
self.SERVER_STARTUP_TIMEOUT = 90
|
|
|
|
self.SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
|
|
os.chdir(self.SCRIPT_DIRECTORY)
|
|
|
|
self.PROFILE_DIRECTORY = os.path.abspath("./mochitesttestingprofile")
|
|
|
|
self.LEAK_REPORT_FILE = os.path.join(self.PROFILE_DIRECTORY, "runtests_leaks.log")
|
|
|
|
def getFullPath(self, path):
|
|
"Get an absolute path relative to self.oldcwd."
|
|
return os.path.normpath(os.path.join(self.oldcwd, os.path.expanduser(path)))
|
|
|
|
def buildTestPath(self, options):
|
|
""" build the url path to the specific test harness and test file or directory """
|
|
testHost = "http://" + self.TEST_SERVER_HOST
|
|
testURL = testHost + self.TEST_PATH + options.testPath
|
|
if options.chrome:
|
|
testURL = testHost + self.CHROME_PATH
|
|
if options.testPath:
|
|
self.urlOpts.append("testPath=" + encodeURIComponent(options.testPath))
|
|
elif options.a11y:
|
|
testURL = testHost + self.A11Y_PATH
|
|
if options.testPath:
|
|
self.urlOpts.append("testPath=" + encodeURIComponent(options.testPath))
|
|
elif options.browserChrome:
|
|
testURL = "about:blank"
|
|
return testURL
|
|
|
|
def startWebServer(self, options):
|
|
""" create the webserver and start it up """
|
|
shutdownURL = "http://" + self.TEST_SERVER_HOST + "/server/shutdown"
|
|
self.server = MochitestServer(self.automation, options, self.PROFILE_DIRECTORY, shutdownURL)
|
|
self.server.start()
|
|
|
|
# If we're lucky, the server has fully started by now, and all paths are
|
|
# ready, etc. However, xpcshell cold start times suck, at least for debug
|
|
# builds. We'll try to connect to the server for awhile, and if we fail,
|
|
# we'll try to kill the server and exit with an error.
|
|
self.server.ensureReady(self.SERVER_STARTUP_TIMEOUT)
|
|
|
|
def stopWebServer(self):
|
|
""" Server's no longer needed, and perhaps more importantly, anything it might
|
|
spew to console shouldn't disrupt the leak information table we print next.
|
|
"""
|
|
self.server.stop()
|
|
|
|
def getLogFilePath(self, logFile):
|
|
""" return the log file path relative to the device we are testing on, in most cases
|
|
it will be the full path on the local system
|
|
"""
|
|
return self.getFullPath(logFile)
|
|
|
|
def buildProfile(self, options):
|
|
""" create the profile and add optional chrome bits and files if requested """
|
|
self.automation.initializeProfile(self.PROFILE_DIRECTORY, options.extraPrefs)
|
|
manifest = self.addChromeToProfile(options)
|
|
self.copyExtraFilesToProfile(options)
|
|
return manifest
|
|
|
|
def buildBrowserEnv(self, options):
|
|
""" build the environment variables for the specific test and operating system """
|
|
browserEnv = self.automation.environment(xrePath = options.xrePath)
|
|
|
|
# These variables are necessary for correct application startup; change
|
|
# via the commandline at your own risk.
|
|
browserEnv["XPCOM_DEBUG_BREAK"] = "stack"
|
|
|
|
for v in options.environment:
|
|
ix = v.find("=")
|
|
if ix <= 0:
|
|
print "Error: syntax error in --setenv=" + v
|
|
return None
|
|
browserEnv[v[:ix]] = v[ix + 1:]
|
|
|
|
browserEnv["XPCOM_MEM_BLOAT_LOG"] = self.LEAK_REPORT_FILE
|
|
|
|
if options.fatalAssertions:
|
|
browserEnv["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
|
|
|
|
return browserEnv
|
|
|
|
def runExtensionRegistration(self, options, browserEnv):
|
|
""" run once with -silent to let the extension manager do its thing
|
|
and then exit the app
|
|
"""
|
|
self.automation.log.info("INFO | runtests.py | Performing extension manager registration: start.\n")
|
|
# Don't care about this |status|: |runApp()| reporting it should be enough.
|
|
status = self.automation.runApp(None, browserEnv, options.app,
|
|
self.PROFILE_DIRECTORY, ["-silent"],
|
|
utilityPath = options.utilityPath,
|
|
xrePath = options.xrePath,
|
|
symbolsPath=options.symbolsPath)
|
|
# We don't care to call |processLeakLog()| for this step.
|
|
self.automation.log.info("\nINFO | runtests.py | Performing extension manager registration: end.")
|
|
|
|
def buildURLOptions(self, options):
|
|
""" Add test control options from the command line to the url
|
|
|
|
URL parameters to test URL:
|
|
|
|
autorun -- kick off tests automatically
|
|
closeWhenDone -- runs quit.js after tests
|
|
logFile -- logs test run to an absolute path
|
|
totalChunks -- how many chunks to split tests into
|
|
thisChunk -- which chunk to run
|
|
timeout -- per-test timeout in seconds
|
|
"""
|
|
|
|
# allow relative paths for logFile
|
|
if options.logFile:
|
|
options.logFile = self.getFullPath(options.logFile)
|
|
if options.browserChrome:
|
|
self.makeTestConfig(options)
|
|
else:
|
|
if options.autorun:
|
|
self.urlOpts.append("autorun=1")
|
|
if options.timeout:
|
|
self.urlOpts.append("timeout=%d" % options.timeout)
|
|
if options.closeWhenDone:
|
|
self.urlOpts.append("closeWhenDone=1")
|
|
if options.logFile:
|
|
self.urlOpts.append("logFile=" + encodeURIComponent(options.logFile))
|
|
self.urlOpts.append("fileLevel=" + encodeURIComponent(options.fileLevel))
|
|
if options.consoleLevel:
|
|
self.urlOpts.append("consoleLevel=" + encodeURIComponent(options.consoleLevel))
|
|
if options.totalChunks:
|
|
self.urlOpts.append("totalChunks=%d" % options.totalChunks)
|
|
self.urlOpts.append("thisChunk=%d" % options.thisChunk)
|
|
if options.chunkByDir:
|
|
self.urlOpts.append("chunkByDir=%d" % options.chunkByDir)
|
|
if options.shuffle:
|
|
self.urlOpts.append("shuffle=1")
|
|
|
|
def cleanup(self, manifest):
|
|
""" remove temporary files and profile """
|
|
os.remove(manifest)
|
|
shutil.rmtree(self.PROFILE_DIRECTORY)
|
|
|
|
def runTests(self, options):
|
|
""" Prepare, configure, run tests and cleanup """
|
|
debuggerInfo = getDebuggerInfo(self.oldcwd, options.debugger, options.debuggerArgs,
|
|
options.debuggerInteractive);
|
|
|
|
browserEnv = self.buildBrowserEnv(options)
|
|
if (browserEnv == None):
|
|
return 1
|
|
|
|
manifest = self.buildProfile(options)
|
|
self.startWebServer(options)
|
|
|
|
|
|
testURL = self.buildTestPath(options)
|
|
self.buildURLOptions(options)
|
|
if (len(self.urlOpts) > 0):
|
|
testURL += "?" + "&".join(self.urlOpts)
|
|
|
|
self.runExtensionRegistration(options, browserEnv)
|
|
|
|
# Remove the leak detection file so it can't "leak" to the tests run.
|
|
# The file is not there if leak logging was not enabled in the application build.
|
|
if os.path.exists(self.LEAK_REPORT_FILE):
|
|
os.remove(self.LEAK_REPORT_FILE)
|
|
|
|
# then again to actually run mochitest
|
|
if options.timeout:
|
|
timeout = options.timeout + 30
|
|
elif not options.autorun:
|
|
timeout = None
|
|
else:
|
|
timeout = 330.0 # default JS harness timeout is 300 seconds
|
|
self.automation.log.info("INFO | runtests.py | Running tests: start.\n")
|
|
status = self.automation.runApp(testURL, browserEnv, options.app,
|
|
self.PROFILE_DIRECTORY, options.browserArgs,
|
|
runSSLTunnel = self.runSSLTunnel,
|
|
utilityPath = options.utilityPath,
|
|
xrePath = options.xrePath,
|
|
certPath=options.certPath,
|
|
debuggerInfo=debuggerInfo,
|
|
symbolsPath=options.symbolsPath,
|
|
timeout = timeout)
|
|
|
|
self.stopWebServer()
|
|
processLeakLog(self.LEAK_REPORT_FILE, options.leakThreshold)
|
|
self.automation.log.info("\nINFO | runtests.py | Running tests: end.")
|
|
|
|
self.cleanup(manifest)
|
|
return status
|
|
|
|
def makeTestConfig(self, options):
|
|
"Creates a test configuration file for customizing test execution."
|
|
def boolString(b):
|
|
if b:
|
|
return "true"
|
|
return "false"
|
|
|
|
logFile = options.logFile.replace("\\", "\\\\")
|
|
testPath = options.testPath.replace("\\", "\\\\")
|
|
content = """\
|
|
({
|
|
autoRun: %(autorun)s,
|
|
closeWhenDone: %(closeWhenDone)s,
|
|
logPath: "%(logPath)s",
|
|
testPath: "%(testPath)s"
|
|
})""" % {"autorun": boolString(options.autorun),
|
|
"closeWhenDone": boolString(options.closeWhenDone),
|
|
"logPath": logFile,
|
|
"testPath": testPath}
|
|
|
|
config = open(os.path.join(self.PROFILE_DIRECTORY, "testConfig.js"), "w")
|
|
config.write(content)
|
|
config.close()
|
|
|
|
|
|
def addChromeToProfile(self, options):
|
|
"Adds MochiKit chrome tests to the profile."
|
|
|
|
chromedir = os.path.join(self.PROFILE_DIRECTORY, "chrome")
|
|
os.mkdir(chromedir)
|
|
|
|
chrome = """
|
|
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */
|
|
toolbar,
|
|
toolbarpalette {
|
|
background-color: rgb(235, 235, 235) !important;
|
|
}
|
|
toolbar#nav-bar {
|
|
background-image: none !important;
|
|
}
|
|
"""
|
|
|
|
# write userChrome.css
|
|
chromeFile = open(os.path.join(self.PROFILE_DIRECTORY, "userChrome.css"), "a")
|
|
chromeFile.write(chrome)
|
|
chromeFile.close()
|
|
|
|
|
|
# register our chrome dir
|
|
chrometestDir = os.path.abspath(".") + "/"
|
|
if self.automation.IS_WIN32:
|
|
chrometestDir = "file:///" + chrometestDir.replace("\\", "/")
|
|
|
|
temp_file = os.path.join(tempfile.mkdtemp(), "mochikit.manifest")
|
|
manifestFile = open(temp_file, "w")
|
|
manifestFile.write("content mochikit " + chrometestDir + " contentaccessible=yes\n")
|
|
|
|
if options.browserChrome:
|
|
manifestFile.write("""overlay chrome://navigator/content/navigator.xul chrome://mochikit/content/browser-test-overlay.xul
|
|
overlay chrome://browser/content/browser.xul chrome://mochikit/content/browser-test-overlay.xul
|
|
""")
|
|
manifestFile.close()
|
|
|
|
return self.installChromeFile(temp_file, options)
|
|
|
|
def installChromeFile(self, filename, options):
|
|
(p, file) = os.path.split(filename)
|
|
(path, leaf) = os.path.split(options.app)
|
|
manifest = os.path.join(path, "chrome", file)
|
|
shutil.copy(filename, manifest)
|
|
return manifest
|
|
|
|
def copyExtraFilesToProfile(self, options):
|
|
"Copy extra files or dirs specified on the command line to the testing profile."
|
|
for f in options.extraProfileFiles:
|
|
abspath = self.getFullPath(f)
|
|
dest = os.path.join(self.PROFILE_DIRECTORY, os.path.basename(abspath))
|
|
if os.path.isdir(abspath):
|
|
shutil.copytree(abspath, dest)
|
|
else:
|
|
shutil.copy(abspath, dest)
|
|
|
|
def main():
|
|
automation = Automation()
|
|
mochitest = Mochitest(automation)
|
|
parser = MochitestOptions(automation, mochitest.SCRIPT_DIRECTORY)
|
|
options, args = parser.parse_args()
|
|
|
|
if options.totalChunks is not None and options.thisChunk is None:
|
|
parser.error("thisChunk must be specified when totalChunks is specified")
|
|
|
|
if options.totalChunks:
|
|
if not 1 <= options.thisChunk <= options.totalChunks:
|
|
parser.error("thisChunk must be between 1 and totalChunks")
|
|
|
|
if options.xrePath is None:
|
|
# default xrePath to the app path if not provided
|
|
# but only if an app path was explicitly provided
|
|
if options.app != parser.defaults['app']:
|
|
options.xrePath = os.path.dirname(options.app)
|
|
else:
|
|
# otherwise default to dist/bin
|
|
options.xrePath = automation.DIST_BIN
|
|
|
|
# allow relative paths
|
|
options.xrePath = mochitest.getFullPath(options.xrePath)
|
|
|
|
options.app = mochitest.getFullPath(options.app)
|
|
if not os.path.exists(options.app):
|
|
msg = """\
|
|
Error: Path %(app)s doesn't exist.
|
|
Are you executing $objdir/_tests/testing/mochitest/runtests.py?"""
|
|
print msg % {"app": options.app}
|
|
sys.exit(1)
|
|
|
|
options.utilityPath = mochitest.getFullPath(options.utilityPath)
|
|
options.certPath = mochitest.getFullPath(options.certPath)
|
|
if options.symbolsPath:
|
|
options.symbolsPath = mochitest.getFullPath(options.symbolsPath)
|
|
|
|
sys.exit(mochitest.runTests(options))
|
|
|
|
if __name__ == "__main__":
|
|
main()
|