mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 13:51:41 +00:00
Bug 530475 - convert all python test harnesses to classes for easier reuse, patch=jmaher, r=ted
This commit is contained in:
parent
b20bff31e2
commit
3d0c15f608
File diff suppressed because it is too large
Load Diff
@ -46,18 +46,19 @@ import os
|
||||
import sys
|
||||
import logging
|
||||
from getopt import getopt
|
||||
import automation
|
||||
from automation import Automation
|
||||
|
||||
PORT = 8888
|
||||
SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
|
||||
PROFILE_DIRECTORY = os.path.abspath(os.path.join(SCRIPT_DIR, "./leakprofile"))
|
||||
DIST_BIN = os.path.join(SCRIPT_DIR, automation.DIST_BIN)
|
||||
os.chdir(SCRIPT_DIR)
|
||||
|
||||
class EasyServer(SocketServer.TCPServer):
|
||||
allow_reuse_address = True
|
||||
|
||||
if __name__ == '__main__':
|
||||
automation = Automation()
|
||||
DIST_BIN = os.path.join(SCRIPT_DIR, automation.DIST_BIN)
|
||||
opts, extraArgs = getopt(sys.argv[1:], 'l:')
|
||||
if len(opts) > 0:
|
||||
try:
|
||||
|
@ -36,7 +36,7 @@
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
import automation
|
||||
from automation import Automation
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
@ -47,6 +47,8 @@ import sys
|
||||
#expand PROFILE_DIR = __PROFILE_DIR__
|
||||
#expand CERTS_SRC_DIR = __CERTS_SRC_DIR__
|
||||
|
||||
automation = Automation()
|
||||
|
||||
dbFiles = [
|
||||
re.compile("^cert[0-9]+\.db$"),
|
||||
re.compile("^key[0-9]+\.db$"),
|
||||
|
@ -46,7 +46,7 @@ import os
|
||||
import sys
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
import automation
|
||||
from automation import Automation
|
||||
|
||||
PORT = 8888
|
||||
SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
|
||||
@ -57,6 +57,7 @@ class EasyServer(SocketServer.TCPServer):
|
||||
allow_reuse_address = True
|
||||
|
||||
if __name__ == '__main__':
|
||||
automation = Automation()
|
||||
httpd = EasyServer(("", PORT), SimpleHTTPServer.SimpleHTTPRequestHandler)
|
||||
t = threading.Thread(target=httpd.serve_forever)
|
||||
t.setDaemon(True) # don't hang on exit
|
||||
|
@ -44,54 +44,128 @@ Runs the reftest test harness.
|
||||
import sys, shutil, os, os.path
|
||||
SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
|
||||
sys.path.append(SCRIPT_DIRECTORY)
|
||||
import automation
|
||||
from automation import Automation
|
||||
from automationutils import *
|
||||
from optparse import OptionParser
|
||||
from tempfile import mkdtemp
|
||||
|
||||
oldcwd = os.getcwd()
|
||||
os.chdir(SCRIPT_DIRECTORY)
|
||||
class RefTest(object):
|
||||
|
||||
def getFullPath(path):
|
||||
"Get an absolute path relative to oldcwd."
|
||||
return os.path.normpath(os.path.join(oldcwd, os.path.expanduser(path)))
|
||||
oldcwd = os.getcwd()
|
||||
|
||||
def createReftestProfile(options, profileDir):
|
||||
"Sets up a profile for reftest."
|
||||
def __init__(self, automation):
|
||||
self.automation = automation
|
||||
os.chdir(SCRIPT_DIRECTORY)
|
||||
|
||||
# Set preferences.
|
||||
prefsFile = open(os.path.join(profileDir, "user.js"), "w")
|
||||
prefsFile.write("""user_pref("browser.dom.window.dump.enabled", true);
|
||||
""")
|
||||
prefsFile.write('user_pref("reftest.timeout", %d);\n' % (options.timeout * 1000))
|
||||
prefsFile.write('user_pref("ui.caretBlinkTime", -1);\n')
|
||||
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)))
|
||||
|
||||
for v in options.extraPrefs:
|
||||
thispref = v.split("=")
|
||||
if len(thispref) < 2:
|
||||
print "Error: syntax error in --setpref=" + v
|
||||
sys.exit(1)
|
||||
part = 'user_pref("%s", %s);\n' % (thispref[0], thispref[1])
|
||||
prefsFile.write(part)
|
||||
# no slow script dialogs
|
||||
prefsFile.write('user_pref("dom.max_script_run_time", 0);')
|
||||
prefsFile.write('user_pref("dom.max_chrome_script_run_time", 0);')
|
||||
prefsFile.close()
|
||||
def createReftestProfile(self, options, profileDir):
|
||||
"Sets up a profile for reftest."
|
||||
|
||||
# Set preferences.
|
||||
prefsFile = open(os.path.join(profileDir, "user.js"), "w")
|
||||
prefsFile.write("""user_pref("browser.dom.window.dump.enabled", true);
|
||||
""")
|
||||
prefsFile.write('user_pref("reftest.timeout", %d);\n' % (options.timeout * 1000))
|
||||
prefsFile.write('user_pref("ui.caretBlinkTime", -1);\n')
|
||||
|
||||
for v in options.extraPrefs:
|
||||
thispref = v.split("=")
|
||||
if len(thispref) < 2:
|
||||
print "Error: syntax error in --setpref=" + v
|
||||
sys.exit(1)
|
||||
part = 'user_pref("%s", %s);\n' % (thispref[0], thispref[1])
|
||||
prefsFile.write(part)
|
||||
# no slow script dialogs
|
||||
prefsFile.write('user_pref("dom.max_script_run_time", 0);')
|
||||
prefsFile.write('user_pref("dom.max_chrome_script_run_time", 0);')
|
||||
prefsFile.close()
|
||||
|
||||
# install the reftest extension bits into the profile
|
||||
profileExtensionsPath = os.path.join(profileDir, "extensions")
|
||||
os.mkdir(profileExtensionsPath)
|
||||
reftestExtensionPath = os.path.join(SCRIPT_DIRECTORY, "reftest")
|
||||
extFile = open(os.path.join(profileExtensionsPath, "reftest@mozilla.org"), "w")
|
||||
extFile.write(reftestExtensionPath)
|
||||
extFile.close()
|
||||
|
||||
def runTests(self, manifest, options):
|
||||
debuggerInfo = getDebuggerInfo(self.oldcwd, options.debugger, options.debuggerArgs,
|
||||
options.debuggerInteractive);
|
||||
|
||||
profileDir = None
|
||||
try:
|
||||
profileDir = mkdtemp()
|
||||
self.createReftestProfile(options, profileDir)
|
||||
self.copyExtraFilesToProfile(options, profileDir)
|
||||
|
||||
# browser environment
|
||||
browserEnv = self.automation.environment(xrePath = options.xrePath)
|
||||
browserEnv["XPCOM_DEBUG_BREAK"] = "stack"
|
||||
|
||||
# Enable leaks detection to its own log file.
|
||||
leakLogFile = os.path.join(profileDir, "runreftest_leaks.log")
|
||||
browserEnv["XPCOM_MEM_BLOAT_LOG"] = leakLogFile
|
||||
|
||||
# run once with -silent to let the extension manager do its thing
|
||||
# and then exit the app
|
||||
self.automation.log.info("REFTEST INFO | runreftest.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, profileDir,
|
||||
["-silent"],
|
||||
utilityPath = options.utilityPath,
|
||||
xrePath=options.xrePath,
|
||||
symbolsPath=options.symbolsPath)
|
||||
# We don't care to call |processLeakLog()| for this step.
|
||||
self.automation.log.info("\nREFTEST INFO | runreftest.py | Performing extension manager registration: end.")
|
||||
|
||||
# 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(leakLogFile):
|
||||
os.remove(leakLogFile)
|
||||
|
||||
# then again to actually run reftest
|
||||
self.automation.log.info("REFTEST INFO | runreftest.py | Running tests: start.\n")
|
||||
reftestlist = self.getFullPath(manifest)
|
||||
status = self.automation.runApp(None, browserEnv, options.app, profileDir,
|
||||
["-reftest", reftestlist],
|
||||
utilityPath = options.utilityPath,
|
||||
xrePath=options.xrePath,
|
||||
debuggerInfo=debuggerInfo,
|
||||
symbolsPath=options.symbolsPath,
|
||||
# give the JS harness 30 seconds to deal
|
||||
# with its own timeouts
|
||||
timeout=options.timeout + 30.0)
|
||||
processLeakLog(leakLogFile, options.leakThreshold)
|
||||
self.automation.log.info("\nREFTEST INFO | runreftest.py | Running tests: end.")
|
||||
finally:
|
||||
if profileDir:
|
||||
shutil.rmtree(profileDir)
|
||||
return status
|
||||
|
||||
def copyExtraFilesToProfile(self, options, profileDir):
|
||||
"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(profileDir, os.path.basename(abspath))
|
||||
if os.path.isdir(abspath):
|
||||
shutil.copytree(abspath, dest)
|
||||
else:
|
||||
shutil.copy(abspath, dest)
|
||||
|
||||
# install the reftest extension bits into the profile
|
||||
profileExtensionsPath = os.path.join(profileDir, "extensions")
|
||||
os.mkdir(profileExtensionsPath)
|
||||
reftestExtensionPath = os.path.join(SCRIPT_DIRECTORY, "reftest")
|
||||
extFile = open(os.path.join(profileExtensionsPath, "reftest@mozilla.org"), "w")
|
||||
extFile.write(reftestExtensionPath)
|
||||
extFile.close()
|
||||
|
||||
def main():
|
||||
automation = Automation()
|
||||
parser = OptionParser()
|
||||
reftest = RefTest(automation)
|
||||
|
||||
# we want to pass down everything from automation.__all__
|
||||
addCommonOptions(parser, defaults=dict(zip(automation.__all__, [getattr(automation, x) for x in automation.__all__])))
|
||||
automation.addExtraCommonOptions(parser)
|
||||
addCommonOptions(parser,
|
||||
defaults=dict(zip(automation.__all__,
|
||||
[getattr(automation, x) for x in automation.__all__])))
|
||||
automation.addCommonOptions(parser)
|
||||
parser.add_option("--appname",
|
||||
action = "store", type = "string", dest = "app",
|
||||
default = os.path.join(SCRIPT_DIRECTORY, automation.DEFAULT_APP),
|
||||
@ -118,90 +192,28 @@ def main():
|
||||
"programs (xpcshell, ssltunnel, certutil)")
|
||||
|
||||
options, args = parser.parse_args()
|
||||
|
||||
if len(args) != 1:
|
||||
print >>sys.stderr, "No reftest.list specified."
|
||||
sys.exit(1)
|
||||
|
||||
options.app = getFullPath(options.app)
|
||||
options.app = reftest.getFullPath(options.app)
|
||||
if not os.path.exists(options.app):
|
||||
print """Error: Path %(app)s doesn't exist.
|
||||
Are you executing $objdir/_tests/reftest/runreftest.py?""" \
|
||||
% {"app": options.app}
|
||||
% {"app": options.app}
|
||||
sys.exit(1)
|
||||
|
||||
if options.xrePath is None:
|
||||
options.xrePath = os.path.dirname(options.app)
|
||||
else:
|
||||
# allow relative paths
|
||||
options.xrePath = getFullPath(options.xrePath)
|
||||
options.xrePath = reftest.getFullPath(options.xrePath)
|
||||
|
||||
if options.symbolsPath:
|
||||
options.symbolsPath = getFullPath(options.symbolsPath)
|
||||
options.utilityPath = getFullPath(options.utilityPath)
|
||||
|
||||
debuggerInfo = getDebuggerInfo(oldcwd, options.debugger, options.debuggerArgs,
|
||||
options.debuggerInteractive);
|
||||
|
||||
profileDir = None
|
||||
try:
|
||||
profileDir = mkdtemp()
|
||||
createReftestProfile(options, profileDir)
|
||||
copyExtraFilesToProfile(options, profileDir)
|
||||
|
||||
# browser environment
|
||||
browserEnv = automation.environment(xrePath = options.xrePath)
|
||||
browserEnv["XPCOM_DEBUG_BREAK"] = "stack"
|
||||
|
||||
# Enable leaks detection to its own log file.
|
||||
leakLogFile = os.path.join(profileDir, "runreftest_leaks.log")
|
||||
browserEnv["XPCOM_MEM_BLOAT_LOG"] = leakLogFile
|
||||
|
||||
# run once with -silent to let the extension manager do its thing
|
||||
# and then exit the app
|
||||
automation.log.info("REFTEST INFO | runreftest.py | Performing extension manager registration: start.\n")
|
||||
# Don't care about this |status|: |runApp()| reporting it should be enough.
|
||||
status = automation.runApp(None, browserEnv, options.app, profileDir,
|
||||
["-silent"],
|
||||
utilityPath = options.utilityPath,
|
||||
xrePath=options.xrePath,
|
||||
symbolsPath=options.symbolsPath)
|
||||
# We don't care to call |processLeakLog()| for this step.
|
||||
automation.log.info("\nREFTEST INFO | runreftest.py | Performing extension manager registration: end.")
|
||||
|
||||
# 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(leakLogFile):
|
||||
os.remove(leakLogFile)
|
||||
|
||||
# then again to actually run reftest
|
||||
automation.log.info("REFTEST INFO | runreftest.py | Running tests: start.\n")
|
||||
reftestlist = getFullPath(args[0])
|
||||
status = automation.runApp(None, browserEnv, options.app, profileDir,
|
||||
["-reftest", reftestlist],
|
||||
utilityPath = options.utilityPath,
|
||||
xrePath=options.xrePath,
|
||||
debuggerInfo=debuggerInfo,
|
||||
symbolsPath=options.symbolsPath,
|
||||
# give the JS harness 30 seconds to deal
|
||||
# with its own timeouts
|
||||
timeout=options.timeout + 30.0)
|
||||
processLeakLog(leakLogFile, options.leakThreshold)
|
||||
automation.log.info("\nREFTEST INFO | runreftest.py | Running tests: end.")
|
||||
finally:
|
||||
if profileDir:
|
||||
shutil.rmtree(profileDir)
|
||||
sys.exit(status)
|
||||
|
||||
def copyExtraFilesToProfile(options, profileDir):
|
||||
"Copy extra files or dirs specified on the command line to the testing profile."
|
||||
for f in options.extraProfileFiles:
|
||||
abspath = getFullPath(f)
|
||||
dest = os.path.join(profileDir, os.path.basename(abspath))
|
||||
if os.path.isdir(abspath):
|
||||
shutil.copytree(abspath, dest)
|
||||
else:
|
||||
shutil.copy(abspath, dest)
|
||||
options.symbolsPath = reftest.getFullPath(options.symbolsPath)
|
||||
options.utilityPath = reftest.getFullPath(options.utilityPath)
|
||||
|
||||
sys.exit(reftest.runTests(args[0], options))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
@ -52,40 +52,9 @@ import shutil
|
||||
from urllib import quote_plus as encodeURIComponent
|
||||
import urllib2
|
||||
import commands
|
||||
import automation
|
||||
from automation import Automation
|
||||
from automationutils import *
|
||||
|
||||
# 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"
|
||||
TESTS_URL = "http://" + TEST_SERVER_HOST + TEST_PATH
|
||||
CHROMETESTS_URL = "http://" + TEST_SERVER_HOST + CHROME_PATH
|
||||
A11YTESTS_URL = "http://" + TEST_SERVER_HOST + A11Y_PATH
|
||||
SERVER_SHUTDOWN_URL = "http://" + TEST_SERVER_HOST + "/server/shutdown"
|
||||
# main browser chrome URL, same as browser.chromeURL pref
|
||||
#ifdef MOZ_SUITE
|
||||
BROWSER_CHROME_URL = "chrome://navigator/content/navigator.xul"
|
||||
#else
|
||||
BROWSER_CHROME_URL = "chrome://browser/content/browser.xul"
|
||||
#endif
|
||||
|
||||
# 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 automation.IS_DEBUG_BUILD:
|
||||
SERVER_STARTUP_TIMEOUT = 180
|
||||
else:
|
||||
SERVER_STARTUP_TIMEOUT = 90
|
||||
|
||||
oldcwd = os.getcwd()
|
||||
SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
|
||||
os.chdir(SCRIPT_DIRECTORY)
|
||||
|
||||
PROFILE_DIRECTORY = os.path.abspath("./mochitesttestingprofile")
|
||||
|
||||
LEAK_REPORT_FILE = os.path.join(PROFILE_DIRECTORY, "runtests_leaks.log")
|
||||
|
||||
#######################
|
||||
# COMMANDLINE OPTIONS #
|
||||
@ -93,13 +62,15 @@ LEAK_REPORT_FILE = os.path.join(PROFILE_DIRECTORY, "runtests_leaks.log")
|
||||
|
||||
class MochitestOptions(optparse.OptionParser):
|
||||
"""Parses Mochitest commandline options."""
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self, automation, scriptdir, **kwargs):
|
||||
self._automation = automation
|
||||
optparse.OptionParser.__init__(self, **kwargs)
|
||||
defaults = {}
|
||||
|
||||
# we want to pass down everything from automation.__all__
|
||||
addCommonOptions(self, defaults=dict(zip(automation.__all__, [getattr(automation, x) for x in automation.__all__])))
|
||||
automation.addExtraCommonOptions(self)
|
||||
# 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",
|
||||
@ -109,17 +80,17 @@ class MochitestOptions(optparse.OptionParser):
|
||||
self.add_option("--appname",
|
||||
action = "store", type = "string", dest = "app",
|
||||
help = "absolute path to application, overriding default")
|
||||
defaults["app"] = os.path.join(SCRIPT_DIRECTORY, automation.DEFAULT_APP)
|
||||
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"] = automation.DIST_BIN
|
||||
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"] = automation.CERTS_SRC_DIR
|
||||
defaults["certPath"] = self._automation.CERTS_SRC_DIR
|
||||
|
||||
self.add_option("--log-file",
|
||||
action = "store", type = "string",
|
||||
@ -249,17 +220,19 @@ See <http://mochikit.com/doc/html/MochiKit/Logging.html> for details on the logg
|
||||
class MochitestServer:
|
||||
"Web server used to serve Mochitests, for closer fidelity to the real web."
|
||||
|
||||
def __init__(self, options):
|
||||
def __init__(self, automation, options, profileDir):
|
||||
self._automation = automation
|
||||
self._closeWhenDone = options.closeWhenDone
|
||||
self._utilityPath = options.utilityPath
|
||||
self._xrePath = options.xrePath
|
||||
self._profileDir = profileDir
|
||||
|
||||
def start(self):
|
||||
"Run the Mochitest server, returning the process ID of the server."
|
||||
|
||||
env = automation.environment(xrePath = self._xrePath)
|
||||
env = self._automation.environment(xrePath = self._xrePath)
|
||||
env["XPCOM_DEBUG_BREAK"] = "warn"
|
||||
if automation.IS_WIN32:
|
||||
if self._automation.IS_WIN32:
|
||||
env["PATH"] = env["PATH"] + ";" + self._xrePath
|
||||
|
||||
args = ["-g", self._xrePath,
|
||||
@ -268,19 +241,18 @@ class MochitestServer:
|
||||
"-f", "./" + "server.js"]
|
||||
|
||||
xpcshell = os.path.join(self._utilityPath,
|
||||
"xpcshell" + automation.BIN_SUFFIX)
|
||||
self._process = automation.Process([xpcshell] + args, env = env)
|
||||
"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)
|
||||
automation.log.info("INFO | runtests.py | Server pid: %d", pid)
|
||||
|
||||
self._automation.log.info("INFO | runtests.py | Server pid: %d", pid)
|
||||
|
||||
def ensureReady(self, timeout):
|
||||
assert timeout >= 0
|
||||
|
||||
aliveFile = os.path.join(PROFILE_DIRECTORY, "server_alive.txt")
|
||||
aliveFile = os.path.join(self._profileDir, "server_alive.txt")
|
||||
i = 0
|
||||
while i < timeout:
|
||||
if os.path.exists(aliveFile):
|
||||
@ -301,16 +273,261 @@ class MochitestServer:
|
||||
except:
|
||||
self._process.kill()
|
||||
|
||||
def getFullPath(path):
|
||||
"Get an absolute path relative to oldcwd."
|
||||
return os.path.normpath(os.path.join(oldcwd, os.path.expanduser(path)))
|
||||
|
||||
#################
|
||||
# MAIN FUNCTION #
|
||||
#################
|
||||
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"
|
||||
TESTS_URL = "http://" + TEST_SERVER_HOST + TEST_PATH
|
||||
CHROMETESTS_URL = "http://" + TEST_SERVER_HOST + CHROME_PATH
|
||||
A11YTESTS_URL = "http://" + TEST_SERVER_HOST + A11Y_PATH
|
||||
SERVER_SHUTDOWN_URL = "http://" + TEST_SERVER_HOST + "/server/shutdown"
|
||||
|
||||
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 runTests(self, options):
|
||||
debuggerInfo = getDebuggerInfo(self.oldcwd, options.debugger, options.debuggerArgs,
|
||||
options.debuggerInteractive);
|
||||
|
||||
# browser environment
|
||||
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 1
|
||||
browserEnv[v[:ix]] = v[ix + 1:]
|
||||
|
||||
self.automation.initializeProfile(self.PROFILE_DIRECTORY, options.extraPrefs)
|
||||
manifest = self.addChromeToProfile(options)
|
||||
self.copyExtraFilesToProfile(options)
|
||||
server = MochitestServer(self.automation, options, self.PROFILE_DIRECTORY)
|
||||
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.
|
||||
server.ensureReady(self.SERVER_STARTUP_TIMEOUT)
|
||||
|
||||
# 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
|
||||
#
|
||||
|
||||
# consoleLevel, fileLevel: set the logging level of the console and
|
||||
# file logs, if activated.
|
||||
# <http://mochikit.com/doc/html/MochiKit/Logging.html>
|
||||
|
||||
testURL = self.TESTS_URL + options.testPath
|
||||
urlOpts = []
|
||||
if options.chrome:
|
||||
testURL = self.CHROMETESTS_URL
|
||||
if options.testPath:
|
||||
urlOpts.append("testPath=" + encodeURIComponent(options.testPath))
|
||||
elif options.a11y:
|
||||
testURL = self.A11YTESTS_URL
|
||||
if options.testPath:
|
||||
urlOpts.append("testPath=" + encodeURIComponent(options.testPath))
|
||||
elif options.browserChrome:
|
||||
testURL = "about:blank"
|
||||
|
||||
# allow relative paths for logFile
|
||||
if options.logFile:
|
||||
options.logFile = self.getFullPath(options.logFile)
|
||||
if options.browserChrome:
|
||||
self.makeTestConfig(options)
|
||||
else:
|
||||
if options.autorun:
|
||||
urlOpts.append("autorun=1")
|
||||
if options.timeout:
|
||||
urlOpts.append("timeout=%d" % options.timeout)
|
||||
if options.closeWhenDone:
|
||||
urlOpts.append("closeWhenDone=1")
|
||||
if options.logFile:
|
||||
urlOpts.append("logFile=" + encodeURIComponent(options.logFile))
|
||||
urlOpts.append("fileLevel=" + encodeURIComponent(options.fileLevel))
|
||||
if options.consoleLevel:
|
||||
urlOpts.append("consoleLevel=" + encodeURIComponent(options.consoleLevel))
|
||||
if options.totalChunks:
|
||||
urlOpts.append("totalChunks=%d" % options.totalChunks)
|
||||
urlOpts.append("thisChunk=%d" % options.thisChunk)
|
||||
if options.chunkByDir:
|
||||
urlOpts.append("chunkByDir=%d" % options.chunkByDir)
|
||||
if options.shuffle:
|
||||
urlOpts.append("shuffle=1")
|
||||
if len(urlOpts) > 0:
|
||||
testURL += "?" + "&".join(urlOpts)
|
||||
|
||||
browserEnv["XPCOM_MEM_BLOAT_LOG"] = self.LEAK_REPORT_FILE
|
||||
|
||||
if options.fatalAssertions:
|
||||
browserEnv["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
|
||||
|
||||
# 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.")
|
||||
|
||||
# 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 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 = True,
|
||||
utilityPath = options.utilityPath,
|
||||
xrePath = options.xrePath,
|
||||
certPath=options.certPath,
|
||||
debuggerInfo=debuggerInfo,
|
||||
symbolsPath=options.symbolsPath,
|
||||
timeout = timeout)
|
||||
|
||||
# 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.
|
||||
server.stop()
|
||||
|
||||
processLeakLog(self.LEAK_REPORT_FILE, options.leakThreshold)
|
||||
self.automation.log.info("\nINFO | runtests.py | Running tests: end.")
|
||||
|
||||
# delete the profile and manifest
|
||||
os.remove(manifest)
|
||||
|
||||
# hanging due to non-halting threads is no fun; assume we hit the errors we
|
||||
# were going to hit already and exit.
|
||||
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("\\", "/")
|
||||
|
||||
|
||||
(path, leaf) = os.path.split(options.app)
|
||||
manifest = os.path.join(path, "chrome", "mochikit.manifest")
|
||||
manifestFile = open(manifest, "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 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():
|
||||
parser = MochitestOptions()
|
||||
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:
|
||||
@ -318,7 +535,7 @@ def main():
|
||||
|
||||
if options.totalChunks:
|
||||
if not 1 <= options.thisChunk <= options.totalChunks:
|
||||
parser.error("thisChunk must be between 1 and totalChunks")
|
||||
parser.error("thisChunk must be between 1 and totalChunks")
|
||||
|
||||
if options.xrePath is None:
|
||||
# default xrePath to the app path if not provided
|
||||
@ -330,248 +547,22 @@ def main():
|
||||
options.xrePath = automation.DIST_BIN
|
||||
|
||||
# allow relative paths
|
||||
options.xrePath = getFullPath(options.xrePath)
|
||||
options.xrePath = mochitest.getFullPath(options.xrePath)
|
||||
|
||||
options.app = getFullPath(options.app)
|
||||
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?"""
|
||||
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 = getFullPath(options.utilityPath)
|
||||
options.certPath = getFullPath(options.certPath)
|
||||
options.utilityPath = mochitest.getFullPath(options.utilityPath)
|
||||
options.certPath = mochitest.getFullPath(options.certPath)
|
||||
if options.symbolsPath:
|
||||
options.symbolsPath = getFullPath(options.symbolsPath)
|
||||
options.symbolsPath = mochitest.getFullPath(options.symbolsPath)
|
||||
|
||||
debuggerInfo = getDebuggerInfo(oldcwd, options.debugger, options.debuggerArgs,
|
||||
options.debuggerInteractive);
|
||||
|
||||
# browser environment
|
||||
browserEnv = 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
|
||||
sys.exit(1)
|
||||
browserEnv[v[:ix]] = v[ix + 1:]
|
||||
|
||||
automation.initializeProfile(PROFILE_DIRECTORY, options.extraPrefs)
|
||||
manifest = addChromeToProfile(options)
|
||||
copyExtraFilesToProfile(options)
|
||||
server = MochitestServer(options)
|
||||
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.
|
||||
server.ensureReady(SERVER_STARTUP_TIMEOUT)
|
||||
|
||||
# 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
|
||||
#
|
||||
|
||||
# consoleLevel, fileLevel: set the logging level of the console and
|
||||
# file logs, if activated.
|
||||
# <http://mochikit.com/doc/html/MochiKit/Logging.html>
|
||||
|
||||
testURL = TESTS_URL + options.testPath
|
||||
urlOpts = []
|
||||
if options.chrome:
|
||||
testURL = CHROMETESTS_URL
|
||||
if options.testPath:
|
||||
urlOpts.append("testPath=" + encodeURIComponent(options.testPath))
|
||||
elif options.a11y:
|
||||
testURL = A11YTESTS_URL
|
||||
if options.testPath:
|
||||
urlOpts.append("testPath=" + encodeURIComponent(options.testPath))
|
||||
elif options.browserChrome:
|
||||
testURL = "about:blank"
|
||||
|
||||
# allow relative paths for logFile
|
||||
if options.logFile:
|
||||
options.logFile = getFullPath(options.logFile)
|
||||
if options.browserChrome:
|
||||
makeTestConfig(options)
|
||||
else:
|
||||
if options.autorun:
|
||||
urlOpts.append("autorun=1")
|
||||
if options.timeout:
|
||||
urlOpts.append("timeout=%d" % options.timeout)
|
||||
if options.closeWhenDone:
|
||||
urlOpts.append("closeWhenDone=1")
|
||||
if options.logFile:
|
||||
urlOpts.append("logFile=" + encodeURIComponent(options.logFile))
|
||||
urlOpts.append("fileLevel=" + encodeURIComponent(options.fileLevel))
|
||||
if options.consoleLevel:
|
||||
urlOpts.append("consoleLevel=" + encodeURIComponent(options.consoleLevel))
|
||||
if options.totalChunks:
|
||||
urlOpts.append("totalChunks=%d" % options.totalChunks)
|
||||
urlOpts.append("thisChunk=%d" % options.thisChunk)
|
||||
if options.chunkByDir:
|
||||
urlOpts.append("chunkByDir=%d" % options.chunkByDir)
|
||||
if options.shuffle:
|
||||
urlOpts.append("shuffle=1")
|
||||
if len(urlOpts) > 0:
|
||||
testURL += "?" + "&".join(urlOpts)
|
||||
|
||||
browserEnv["XPCOM_MEM_BLOAT_LOG"] = LEAK_REPORT_FILE
|
||||
|
||||
if options.fatalAssertions:
|
||||
browserEnv["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
|
||||
|
||||
# run once with -silent to let the extension manager do its thing
|
||||
# and then exit the app
|
||||
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 = automation.runApp(None, browserEnv, options.app,
|
||||
PROFILE_DIRECTORY, ["-silent"],
|
||||
utilityPath = options.utilityPath,
|
||||
xrePath = options.xrePath,
|
||||
symbolsPath=options.symbolsPath)
|
||||
# We don't care to call |processLeakLog()| for this step.
|
||||
automation.log.info("\nINFO | runtests.py | Performing extension manager registration: end.")
|
||||
|
||||
# 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(LEAK_REPORT_FILE):
|
||||
os.remove(LEAK_REPORT_FILE)
|
||||
|
||||
# then again to actually run mochitest
|
||||
if options.timeout:
|
||||
timeout = options.timeout + 30
|
||||
elif options.autorun:
|
||||
timeout = None
|
||||
else:
|
||||
timeout = 330.0 # default JS harness timeout is 300 seconds
|
||||
automation.log.info("INFO | runtests.py | Running tests: start.\n")
|
||||
status = automation.runApp(testURL, browserEnv, options.app,
|
||||
PROFILE_DIRECTORY, options.browserArgs,
|
||||
runSSLTunnel = True,
|
||||
utilityPath = options.utilityPath,
|
||||
xrePath = options.xrePath,
|
||||
certPath=options.certPath,
|
||||
debuggerInfo=debuggerInfo,
|
||||
symbolsPath=options.symbolsPath,
|
||||
timeout = timeout)
|
||||
|
||||
# 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.
|
||||
server.stop()
|
||||
|
||||
processLeakLog(LEAK_REPORT_FILE, options.leakThreshold)
|
||||
automation.log.info("\nINFO | runtests.py | Running tests: end.")
|
||||
|
||||
# delete the profile and manifest
|
||||
os.remove(manifest)
|
||||
|
||||
# hanging due to non-halting threads is no fun; assume we hit the errors we
|
||||
# were going to hit already and exit.
|
||||
sys.exit(status)
|
||||
|
||||
|
||||
|
||||
#######################
|
||||
# CONFIGURATION SETUP #
|
||||
#######################
|
||||
|
||||
def makeTestConfig(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(PROFILE_DIRECTORY, "testConfig.js"), "w")
|
||||
config.write(content)
|
||||
config.close()
|
||||
|
||||
|
||||
def addChromeToProfile(options):
|
||||
"Adds MochiKit chrome tests to the profile."
|
||||
|
||||
chromedir = os.path.join(PROFILE_DIRECTORY, "chrome")
|
||||
os.mkdir(chromedir)
|
||||
|
||||
chrome = []
|
||||
|
||||
part = """
|
||||
@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;
|
||||
}
|
||||
"""
|
||||
chrome.append(part)
|
||||
|
||||
|
||||
|
||||
# write userChrome.css
|
||||
chromeFile = open(os.path.join(PROFILE_DIRECTORY, "userChrome.css"), "a")
|
||||
chromeFile.write("".join(chrome))
|
||||
chromeFile.close()
|
||||
|
||||
|
||||
# register our chrome dir
|
||||
chrometestDir = os.path.abspath(".") + "/"
|
||||
if automation.IS_WIN32:
|
||||
chrometestDir = "file:///" + chrometestDir.replace("\\", "/")
|
||||
|
||||
|
||||
(path, leaf) = os.path.split(options.app)
|
||||
manifest = os.path.join(path, "chrome", "mochikit.manifest")
|
||||
manifestFile = open(manifest, "w")
|
||||
manifestFile.write("content mochikit " + chrometestDir + " contentaccessible=yes\n")
|
||||
if options.browserChrome:
|
||||
overlayLine = "overlay " + BROWSER_CHROME_URL + " " \
|
||||
"chrome://mochikit/content/browser-test-overlay.xul\n"
|
||||
manifestFile.write(overlayLine)
|
||||
manifestFile.close()
|
||||
|
||||
return manifest
|
||||
|
||||
def copyExtraFilesToProfile(options):
|
||||
"Copy extra files or dirs specified on the command line to the testing profile."
|
||||
for f in options.extraProfileFiles:
|
||||
abspath = getFullPath(f)
|
||||
dest = os.path.join(PROFILE_DIRECTORY, os.path.basename(abspath))
|
||||
if os.path.isdir(abspath):
|
||||
shutil.copytree(abspath, dest)
|
||||
else:
|
||||
shutil.copy(abspath, dest)
|
||||
|
||||
#########
|
||||
# DO IT #
|
||||
#########
|
||||
sys.exit(mochitest.runTests(options))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
@ -22,6 +22,7 @@
|
||||
# Contributor(s):
|
||||
# Serge Gautherie <sgautherie.bz@free.fr>
|
||||
# Ted Mielczarek <ted.mielczarek@gmail.com>
|
||||
# Joel Maher <joel.maher@gmail.com>
|
||||
#
|
||||
# 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
|
||||
@ -45,243 +46,246 @@ from tempfile import mkdtemp
|
||||
|
||||
from automationutils import *
|
||||
|
||||
# Init logging
|
||||
log = logging.getLogger()
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
log.setLevel(logging.INFO)
|
||||
log.addHandler(handler)
|
||||
class XPCShellTests(object):
|
||||
|
||||
oldcwd = os.getcwd()
|
||||
log = logging.getLogger()
|
||||
oldcwd = os.getcwd()
|
||||
|
||||
def readManifest(manifest):
|
||||
"""Given a manifest file containing a list of test directories,
|
||||
return a list of absolute paths to the directories contained within."""
|
||||
manifestdir = os.path.dirname(manifest)
|
||||
testdirs = []
|
||||
try:
|
||||
f = open(manifest, "r")
|
||||
for line in f:
|
||||
dir = line.rstrip()
|
||||
path = os.path.join(manifestdir, dir)
|
||||
if os.path.isdir(path):
|
||||
testdirs.append(path)
|
||||
f.close()
|
||||
except:
|
||||
pass # just eat exceptions
|
||||
return testdirs
|
||||
def __init__(self):
|
||||
# Init logging
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
self.log.setLevel(logging.INFO)
|
||||
self.log.addHandler(handler)
|
||||
|
||||
def runTests(xpcshell, xrePath=None, symbolsPath=None,
|
||||
manifest=None, testdirs=[], testPath=None,
|
||||
interactive=False, logfiles=True,
|
||||
debuggerInfo=None):
|
||||
"""Run xpcshell tests.
|
||||
def readManifest(self, manifest):
|
||||
"""Given a manifest file containing a list of test directories,
|
||||
return a list of absolute paths to the directories contained within."""
|
||||
manifestdir = os.path.dirname(manifest)
|
||||
testdirs = []
|
||||
try:
|
||||
f = open(manifest, "r")
|
||||
for line in f:
|
||||
dir = line.rstrip()
|
||||
path = os.path.join(manifestdir, dir)
|
||||
if os.path.isdir(path):
|
||||
testdirs.append(path)
|
||||
f.close()
|
||||
except:
|
||||
pass # just eat exceptions
|
||||
return testdirs
|
||||
|
||||
|xpcshell|, is the xpcshell executable to use to run the tests.
|
||||
|xrePath|, if provided, is the path to the XRE to use.
|
||||
|symbolsPath|, if provided is the path to a directory containing
|
||||
breakpad symbols for processing crashes in tests.
|
||||
|manifest|, if provided, is a file containing a list of
|
||||
test directories to run.
|
||||
|testdirs|, if provided, is a list of absolute paths of test directories.
|
||||
No-manifest only option.
|
||||
|testPath|, if provided, indicates a single path and/or test to run.
|
||||
|interactive|, if set to True, indicates to provide an xpcshell prompt
|
||||
instead of automatically executing the test.
|
||||
|logfiles|, if set to False, indicates not to save output to log files.
|
||||
Non-interactive only option.
|
||||
|debuggerInfo|, if set, specifies the debugger and debugger arguments
|
||||
that will be used to launch xpcshell.
|
||||
"""
|
||||
def runTests(self, xpcshell, xrePath=None, symbolsPath=None,
|
||||
manifest=None, testdirs=[], testPath=None,
|
||||
interactive=False, logfiles=True,
|
||||
debuggerInfo=None):
|
||||
"""Run xpcshell tests.
|
||||
|
||||
if not testdirs and not manifest:
|
||||
# nothing to test!
|
||||
print >>sys.stderr, "Error: No test dirs or test manifest specified!"
|
||||
return False
|
||||
|xpcshell|, is the xpcshell executable to use to run the tests.
|
||||
|xrePath|, if provided, is the path to the XRE to use.
|
||||
|symbolsPath|, if provided is the path to a directory containing
|
||||
breakpad symbols for processing crashes in tests.
|
||||
|manifest|, if provided, is a file containing a list of
|
||||
test directories to run.
|
||||
|testdirs|, if provided, is a list of absolute paths of test directories.
|
||||
No-manifest only option.
|
||||
|testPath|, if provided, indicates a single path and/or test to run.
|
||||
|interactive|, if set to True, indicates to provide an xpcshell prompt
|
||||
instead of automatically executing the test.
|
||||
|logfiles|, if set to False, indicates not to save output to log files.
|
||||
Non-interactive only option.
|
||||
|debuggerInfo|, if set, specifies the debugger and debugger arguments
|
||||
that will be used to launch xpcshell.
|
||||
"""
|
||||
|
||||
passCount = 0
|
||||
failCount = 0
|
||||
if not testdirs and not manifest:
|
||||
# nothing to test!
|
||||
print >>sys.stderr, "Error: No test dirs or test manifest specified!"
|
||||
return False
|
||||
|
||||
testharnessdir = os.path.dirname(os.path.abspath(__file__))
|
||||
xpcshell = os.path.abspath(xpcshell)
|
||||
# we assume that httpd.js lives in components/ relative to xpcshell
|
||||
httpdJSPath = os.path.join(os.path.dirname(xpcshell), "components", "httpd.js").replace("\\", "/");
|
||||
passCount = 0
|
||||
failCount = 0
|
||||
|
||||
env = dict(os.environ)
|
||||
# Make assertions fatal
|
||||
env["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
|
||||
# Don't launch the crash reporter client
|
||||
env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
|
||||
testharnessdir = os.path.dirname(os.path.abspath(__file__))
|
||||
xpcshell = os.path.abspath(xpcshell)
|
||||
# we assume that httpd.js lives in components/ relative to xpcshell
|
||||
httpdJSPath = os.path.join(os.path.dirname(xpcshell), "components", "httpd.js").replace("\\", "/");
|
||||
|
||||
if xrePath is None:
|
||||
xrePath = os.path.dirname(xpcshell)
|
||||
else:
|
||||
xrePath = os.path.abspath(xrePath)
|
||||
if sys.platform == 'win32':
|
||||
env["PATH"] = env["PATH"] + ";" + xrePath
|
||||
elif sys.platform in ('os2emx', 'os2knix'):
|
||||
os.environ["BEGINLIBPATH"] = xrePath + ";" + env["BEGINLIBPATH"]
|
||||
os.environ["LIBPATHSTRICT"] = "T"
|
||||
elif sys.platform == 'osx':
|
||||
env["DYLD_LIBRARY_PATH"] = xrePath
|
||||
else: # unix or linux?
|
||||
env["LD_LIBRARY_PATH"] = xrePath
|
||||
env = dict(os.environ)
|
||||
# Make assertions fatal
|
||||
env["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
|
||||
# Don't launch the crash reporter client
|
||||
env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
|
||||
|
||||
# xpcsRunArgs: <head.js> function to call to run the test.
|
||||
# pStdout, pStderr: Parameter values for later |Popen()| call.
|
||||
if interactive:
|
||||
xpcsRunArgs = [
|
||||
if xrePath is None:
|
||||
xrePath = os.path.dirname(xpcshell)
|
||||
else:
|
||||
xrePath = os.path.abspath(xrePath)
|
||||
if sys.platform == 'win32':
|
||||
env["PATH"] = env["PATH"] + ";" + xrePath
|
||||
elif sys.platform in ('os2emx', 'os2knix'):
|
||||
os.environ["BEGINLIBPATH"] = xrePath + ";" + env["BEGINLIBPATH"]
|
||||
os.environ["LIBPATHSTRICT"] = "T"
|
||||
elif sys.platform == 'osx':
|
||||
env["DYLD_LIBRARY_PATH"] = xrePath
|
||||
else: # unix or linux?
|
||||
env["LD_LIBRARY_PATH"] = xrePath
|
||||
|
||||
# xpcsRunArgs: <head.js> function to call to run the test.
|
||||
# pStdout, pStderr: Parameter values for later |Popen()| call.
|
||||
if interactive:
|
||||
xpcsRunArgs = [
|
||||
'-e', 'print("To start the test, type |_execute_test();|.");',
|
||||
'-i']
|
||||
pStdout = None
|
||||
pStderr = None
|
||||
else:
|
||||
xpcsRunArgs = ['-e', '_execute_test();']
|
||||
if (debuggerInfo and debuggerInfo["interactive"]):
|
||||
pStdout = None
|
||||
pStderr = None
|
||||
else:
|
||||
if sys.platform == 'os2emx':
|
||||
xpcsRunArgs = ['-e', '_execute_test();']
|
||||
if (debuggerInfo and debuggerInfo["interactive"]):
|
||||
pStdout = None
|
||||
pStderr = None
|
||||
else:
|
||||
pStdout = PIPE
|
||||
pStderr = STDOUT
|
||||
if sys.platform == 'os2emx':
|
||||
pStdout = None
|
||||
else:
|
||||
pStdout = PIPE
|
||||
pStderr = STDOUT
|
||||
|
||||
# <head.js> has to be loaded by xpchell: it can't load itself.
|
||||
xpcsCmd = [xpcshell, '-g', xrePath, '-j', '-s'] + \
|
||||
['-e', 'const _HTTPD_JS_PATH = "%s";' % httpdJSPath,
|
||||
'-f', os.path.join(testharnessdir, 'head.js')]
|
||||
# <head.js> has to be loaded by xpchell: it can't load itself.
|
||||
xpcsCmd = [xpcshell, '-g', xrePath, '-j', '-s'] + \
|
||||
['-e', 'const _HTTPD_JS_PATH = "%s";' % httpdJSPath,
|
||||
'-f', os.path.join(testharnessdir, 'head.js')]
|
||||
|
||||
if debuggerInfo:
|
||||
xpcsCmd = [debuggerInfo["path"]] + debuggerInfo["args"] + xpcsCmd
|
||||
if debuggerInfo:
|
||||
xpcsCmd = [debuggerInfo["path"]] + debuggerInfo["args"] + xpcsCmd
|
||||
|
||||
# |testPath| will be the optional path only, or |None|.
|
||||
# |singleFile| will be the optional test only, or |None|.
|
||||
singleFile = None
|
||||
if testPath:
|
||||
if testPath.endswith('.js'):
|
||||
# Split into path and file.
|
||||
if testPath.find('/') == -1:
|
||||
# Test only.
|
||||
singleFile = testPath
|
||||
testPath = None
|
||||
# |testPath| will be the optional path only, or |None|.
|
||||
# |singleFile| will be the optional test only, or |None|.
|
||||
singleFile = None
|
||||
if testPath:
|
||||
if testPath.endswith('.js'):
|
||||
# Split into path and file.
|
||||
if testPath.find('/') == -1:
|
||||
# Test only.
|
||||
singleFile = testPath
|
||||
testPath = None
|
||||
else:
|
||||
# Both path and test.
|
||||
# Reuse |testPath| temporarily.
|
||||
testPath = testPath.rsplit('/', 1)
|
||||
singleFile = testPath[1]
|
||||
testPath = testPath[0]
|
||||
else:
|
||||
# Both path and test.
|
||||
# Reuse |testPath| temporarily.
|
||||
testPath = testPath.rsplit('/', 1)
|
||||
singleFile = testPath[1]
|
||||
testPath = testPath[0]
|
||||
else:
|
||||
# Path only.
|
||||
# Simply remove optional ending separator.
|
||||
testPath = testPath.rstrip("/")
|
||||
# Path only.
|
||||
# Simply remove optional ending separator.
|
||||
testPath = testPath.rstrip("/")
|
||||
|
||||
# Override testdirs.
|
||||
if manifest is not None:
|
||||
testdirs = readManifest(os.path.abspath(manifest))
|
||||
# Override testdirs.
|
||||
if manifest is not None:
|
||||
testdirs = self.readManifest(os.path.abspath(manifest))
|
||||
|
||||
# Process each test directory individually.
|
||||
for testdir in testdirs:
|
||||
if testPath and not testdir.endswith(testPath):
|
||||
continue
|
||||
|
||||
testdir = os.path.abspath(testdir)
|
||||
|
||||
# get the list of head and tail files from the directory
|
||||
testHeadFiles = []
|
||||
for f in sorted(glob(os.path.join(testdir, "head_*.js"))):
|
||||
if os.path.isfile(f):
|
||||
testHeadFiles += [f]
|
||||
testTailFiles = []
|
||||
# Tails are executed in the reverse order, to "match" heads order,
|
||||
# as in "h1-h2-h3 then t3-t2-t1".
|
||||
for f in reversed(sorted(glob(os.path.join(testdir, "tail_*.js")))):
|
||||
if os.path.isfile(f):
|
||||
testTailFiles += [f]
|
||||
|
||||
# if a single test file was specified, we only want to execute that test
|
||||
testfiles = sorted(glob(os.path.join(testdir, "test_*.js")))
|
||||
if singleFile:
|
||||
if singleFile in [os.path.basename(x) for x in testfiles]:
|
||||
testfiles = [os.path.join(testdir, singleFile)]
|
||||
else: # not in this dir? skip it
|
||||
# Process each test directory individually.
|
||||
for testdir in testdirs:
|
||||
if testPath and not testdir.endswith(testPath):
|
||||
continue
|
||||
|
||||
cmdH = ", ".join(['"' + f.replace('\\', '/') + '"'
|
||||
for f in testHeadFiles])
|
||||
cmdT = ", ".join(['"' + f.replace('\\', '/') + '"'
|
||||
for f in testTailFiles])
|
||||
cmdH = xpcsCmd + \
|
||||
['-e', 'const _HEAD_FILES = [%s];' % cmdH] + \
|
||||
['-e', 'const _TAIL_FILES = [%s];' % cmdT]
|
||||
testdir = os.path.abspath(testdir)
|
||||
|
||||
# Now execute each test individually.
|
||||
for test in testfiles:
|
||||
# The test file will have to be loaded after the head files.
|
||||
cmdT = ['-e', 'const _TEST_FILE = ["%s"];' %
|
||||
os.path.join(testdir, test).replace('\\', '/')]
|
||||
# get the list of head and tail files from the directory
|
||||
testHeadFiles = []
|
||||
for f in sorted(glob(os.path.join(testdir, "head_*.js"))):
|
||||
if os.path.isfile(f):
|
||||
testHeadFiles += [f]
|
||||
testTailFiles = []
|
||||
# Tails are executed in the reverse order, to "match" heads order,
|
||||
# as in "h1-h2-h3 then t3-t2-t1".
|
||||
for f in reversed(sorted(glob(os.path.join(testdir, "tail_*.js")))):
|
||||
if os.path.isfile(f):
|
||||
testTailFiles += [f]
|
||||
|
||||
# create a temp dir that the JS harness can stick a profile in
|
||||
profileDir = None
|
||||
try:
|
||||
profileDir = mkdtemp()
|
||||
env["XPCSHELL_TEST_PROFILE_DIR"] = profileDir
|
||||
# if a single test file was specified, we only want to execute that test
|
||||
testfiles = sorted(glob(os.path.join(testdir, "test_*.js")))
|
||||
if singleFile:
|
||||
if singleFile in [os.path.basename(x) for x in testfiles]:
|
||||
testfiles = [os.path.join(testdir, singleFile)]
|
||||
else: # not in this dir? skip it
|
||||
continue
|
||||
|
||||
# Enable leaks (only) detection to its own log file.
|
||||
leakLogFile = os.path.join(profileDir, "runxpcshelltests_leaks.log")
|
||||
env["XPCOM_MEM_LEAK_LOG"] = leakLogFile
|
||||
cmdH = ", ".join(['"' + f.replace('\\', '/') + '"'
|
||||
for f in testHeadFiles])
|
||||
cmdT = ", ".join(['"' + f.replace('\\', '/') + '"'
|
||||
for f in testTailFiles])
|
||||
cmdH = xpcsCmd + \
|
||||
['-e', 'const _HEAD_FILES = [%s];' % cmdH] + \
|
||||
['-e', 'const _TAIL_FILES = [%s];' % cmdT]
|
||||
|
||||
proc = Popen(cmdH + cmdT + xpcsRunArgs,
|
||||
stdout=pStdout, stderr=pStderr, env=env, cwd=testdir)
|
||||
# Now execute each test individually.
|
||||
for test in testfiles:
|
||||
# The test file will have to be loaded after the head files.
|
||||
cmdT = ['-e', 'const _TEST_FILE = ["%s"];' %
|
||||
os.path.join(testdir, test).replace('\\', '/')]
|
||||
|
||||
# allow user to kill hung subprocess with SIGINT w/o killing this script
|
||||
# - don't move this line above Popen, or child will inherit the SIG_IGN
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
# |stderr == None| as |pStderr| was either |None| or redirected to |stdout|.
|
||||
stdout, stderr = proc.communicate()
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
# create a temp dir that the JS harness can stick a profile in
|
||||
profileDir = None
|
||||
try:
|
||||
profileDir = mkdtemp()
|
||||
env["XPCSHELL_TEST_PROFILE_DIR"] = profileDir
|
||||
|
||||
if interactive:
|
||||
# Not sure what else to do here...
|
||||
return True
|
||||
# Enable leaks (only) detection to its own log file.
|
||||
leakLogFile = os.path.join(profileDir, "runxpcshelltests_leaks.log")
|
||||
env["XPCOM_MEM_LEAK_LOG"] = leakLogFile
|
||||
|
||||
if proc.returncode != 0 or (stdout and re.search("^TEST-UNEXPECTED-FAIL", stdout, re.MULTILINE)):
|
||||
print """TEST-UNEXPECTED-FAIL | %s | test failed (with xpcshell return code: %d), see following log:
|
||||
proc = Popen(cmdH + cmdT + xpcsRunArgs,
|
||||
stdout=pStdout, stderr=pStderr, env=env, cwd=testdir)
|
||||
|
||||
# allow user to kill hung subprocess with SIGINT w/o killing this script
|
||||
# - don't move this line above Popen, or child will inherit the SIG_IGN
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
# |stderr == None| as |pStderr| was either |None| or redirected to |stdout|.
|
||||
stdout, stderr = proc.communicate()
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
|
||||
if interactive:
|
||||
# Not sure what else to do here...
|
||||
return True
|
||||
|
||||
if proc.returncode != 0 or (stdout and re.search("^TEST-UNEXPECTED-FAIL", stdout, re.MULTILINE)):
|
||||
print """TEST-UNEXPECTED-FAIL | %s | test failed (with xpcshell return code: %d), see following log:
|
||||
>>>>>>>
|
||||
%s
|
||||
<<<<<<<""" % (test, proc.returncode, stdout)
|
||||
checkForCrashes(testdir, symbolsPath, testName=test)
|
||||
failCount += 1
|
||||
else:
|
||||
print "TEST-PASS | %s | test passed" % test
|
||||
passCount += 1
|
||||
checkForCrashes(testdir, symbolsPath, testName=test)
|
||||
failCount += 1
|
||||
else:
|
||||
print "TEST-PASS | %s | test passed" % test
|
||||
passCount += 1
|
||||
|
||||
dumpLeakLog(leakLogFile, True)
|
||||
dumpLeakLog(leakLogFile, True)
|
||||
|
||||
if logfiles and stdout:
|
||||
try:
|
||||
f = open(test + ".log", "w")
|
||||
f.write(stdout)
|
||||
if logfiles and stdout:
|
||||
try:
|
||||
f = open(test + ".log", "w")
|
||||
f.write(stdout)
|
||||
|
||||
if os.path.exists(leakLogFile):
|
||||
leaks = open(leakLogFile, "r")
|
||||
f.write(leaks.read())
|
||||
leaks.close()
|
||||
finally:
|
||||
if f:
|
||||
f.close()
|
||||
finally:
|
||||
if profileDir:
|
||||
shutil.rmtree(profileDir)
|
||||
if os.path.exists(leakLogFile):
|
||||
leaks = open(leakLogFile, "r")
|
||||
f.write(leaks.read())
|
||||
leaks.close()
|
||||
finally:
|
||||
if f:
|
||||
f.close()
|
||||
finally:
|
||||
if profileDir:
|
||||
shutil.rmtree(profileDir)
|
||||
|
||||
if passCount == 0 and failCount == 0:
|
||||
print "TEST-UNEXPECTED-FAIL | runxpcshelltests.py | No tests run. Did you pass an invalid --test-path?"
|
||||
failCount = 1
|
||||
if passCount == 0 and failCount == 0:
|
||||
print "TEST-UNEXPECTED-FAIL | runxpcshelltests.py | No tests run. Did you pass an invalid --test-path?"
|
||||
failCount = 1
|
||||
|
||||
print """INFO | Result summary:
|
||||
INFO | Passed: %d
|
||||
INFO | Failed: %d""" % (passCount, failCount)
|
||||
print """INFO | Result summary:
|
||||
INFO | Passed: %d
|
||||
INFO | Failed: %d""" % (passCount, failCount)
|
||||
|
||||
return failCount == 0
|
||||
return failCount == 0
|
||||
|
||||
def main():
|
||||
"""Process command line arguments and call runTests() to do the real work."""
|
||||
@ -307,27 +311,29 @@ def main():
|
||||
|
||||
if len(args) < 2 and options.manifest is None or \
|
||||
(len(args) < 1 and options.manifest is not None):
|
||||
print >>sys.stderr, """Usage: %s <path to xpcshell> <test dirs>
|
||||
or: %s --manifest=test.manifest <path to xpcshell>""" % (sys.argv[0],
|
||||
print >>sys.stderr, """Usage: %s <path to xpcshell> <test dirs>
|
||||
or: %s --manifest=test.manifest <path to xpcshell>""" % (sys.argv[0],
|
||||
sys.argv[0])
|
||||
sys.exit(1)
|
||||
sys.exit(1)
|
||||
|
||||
debuggerInfo = getDebuggerInfo(oldcwd, options.debugger, options.debuggerArgs,
|
||||
xpcsh = XPCShellTests()
|
||||
debuggerInfo = getDebuggerInfo(xpcsh.oldcwd, options.debugger, options.debuggerArgs,
|
||||
options.debuggerInteractive);
|
||||
|
||||
if options.interactive and not options.testPath:
|
||||
print >>sys.stderr, "Error: You must specify a test filename in interactive mode!"
|
||||
sys.exit(1)
|
||||
|
||||
if not runTests(args[0],
|
||||
xrePath=options.xrePath,
|
||||
symbolsPath=options.symbolsPath,
|
||||
manifest=options.manifest,
|
||||
testdirs=args[1:],
|
||||
testPath=options.testPath,
|
||||
interactive=options.interactive,
|
||||
logfiles=options.logfiles,
|
||||
debuggerInfo=debuggerInfo):
|
||||
|
||||
if not xpcsh.runTests(args[0],
|
||||
xrePath=options.xrePath,
|
||||
symbolsPath=options.symbolsPath,
|
||||
manifest=options.manifest,
|
||||
testdirs=args[1:],
|
||||
testPath=options.testPath,
|
||||
interactive=options.interactive,
|
||||
logfiles=options.logfiles,
|
||||
debuggerInfo=debuggerInfo):
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
Loading…
Reference in New Issue
Block a user