2008-01-28 19:48:34 -08:00
|
|
|
#
|
2012-05-21 04:12:37 -07:00
|
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
2008-01-28 19:48:34 -08:00
|
|
|
|
|
|
|
"""
|
|
|
|
Runs the Mochitest test harness.
|
|
|
|
"""
|
|
|
|
|
2010-09-30 17:10:19 -07:00
|
|
|
from __future__ import with_statement
|
2008-01-28 19:48:34 -08:00
|
|
|
import optparse
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import time
|
2013-07-26 11:40:04 -07:00
|
|
|
import traceback
|
2010-11-07 12:47:02 -08:00
|
|
|
|
2013-07-26 11:40:04 -07:00
|
|
|
SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
|
2010-11-07 12:47:02 -08:00
|
|
|
sys.path.insert(0, SCRIPT_DIR);
|
|
|
|
|
2009-02-09 10:57:27 -08:00
|
|
|
import shutil
|
2008-01-28 19:48:34 -08:00
|
|
|
from urllib import quote_plus as encodeURIComponent
|
|
|
|
import urllib2
|
2010-01-15 09:22:54 -08:00
|
|
|
from automation import Automation
|
2013-07-26 11:40:04 -07:00
|
|
|
from automationutils import getDebuggerInfo, isURL, processLeakLog
|
|
|
|
from mochitest_options import MochitestOptions
|
2013-08-02 05:48:06 -07:00
|
|
|
from manifestparser import TestManifest
|
2013-07-26 11:40:04 -07:00
|
|
|
import mozinfo
|
2013-08-02 05:48:06 -07:00
|
|
|
import json
|
|
|
|
|
2013-07-26 11:40:04 -07:00
|
|
|
import mozlog
|
2010-03-15 14:44:29 -07:00
|
|
|
|
2013-07-26 11:40:04 -07:00
|
|
|
log = mozlog.getLogger('Mochitest')
|
2010-03-13 09:56:24 -08:00
|
|
|
|
|
|
|
|
2008-01-28 19:48:34 -08:00
|
|
|
#######################
|
|
|
|
# HTTP SERVER SUPPORT #
|
|
|
|
#######################
|
|
|
|
|
|
|
|
class MochitestServer:
|
|
|
|
"Web server used to serve Mochitests, for closer fidelity to the real web."
|
|
|
|
|
2010-03-13 09:56:24 -08:00
|
|
|
def __init__(self, automation, options):
|
2013-07-26 11:40:04 -07:00
|
|
|
if isinstance(options, optparse.Values):
|
|
|
|
options = vars(options)
|
|
|
|
self._automation = automation or Automation()
|
|
|
|
self._closeWhenDone = options['closeWhenDone']
|
|
|
|
self._utilityPath = options['utilityPath']
|
|
|
|
self._xrePath = options['xrePath']
|
|
|
|
self._profileDir = options['profilePath']
|
|
|
|
self.webServer = options['webServer']
|
|
|
|
self.httpPort = options['httpPort']
|
2010-03-13 09:56:24 -08:00
|
|
|
self.shutdownURL = "http://%(server)s:%(port)s/server/shutdown" % { "server" : self.webServer, "port" : self.httpPort }
|
2013-07-26 11:40:04 -07:00
|
|
|
self.testPrefix = "'webapprt_'" if options.get('webapprtContent') else "undefined"
|
|
|
|
|
|
|
|
if options.get('httpdPath'):
|
|
|
|
self._httpdPath = options['httpdPath']
|
2013-06-28 19:20:08 -07:00
|
|
|
else:
|
|
|
|
self._httpdPath = '.'
|
|
|
|
self._httpdPath = os.path.abspath(self._httpdPath)
|
2008-01-28 19:48:34 -08:00
|
|
|
|
|
|
|
def start(self):
|
|
|
|
"Run the Mochitest server, returning the process ID of the server."
|
2013-07-26 11:40:04 -07:00
|
|
|
|
2010-01-15 09:22:54 -08:00
|
|
|
env = self._automation.environment(xrePath = self._xrePath)
|
2009-10-08 11:10:47 -07:00
|
|
|
env["XPCOM_DEBUG_BREAK"] = "warn"
|
2013-01-22 07:48:02 -08:00
|
|
|
|
|
|
|
# When running with an ASan build, our xpcshell server will also be ASan-enabled,
|
|
|
|
# thus consuming too much resources when running together with the browser on
|
|
|
|
# the test slaves. Try to limit the amount of resources by disabling certain
|
|
|
|
# features.
|
|
|
|
env["ASAN_OPTIONS"] = "quarantine_size=1:redzone=32"
|
|
|
|
|
2013-07-26 11:40:04 -07:00
|
|
|
if mozinfo.isWin:
|
2013-07-30 07:02:28 -07:00
|
|
|
env["PATH"] = env["PATH"] + ";" + str(self._xrePath)
|
2008-01-28 19:48:34 -08:00
|
|
|
|
2009-02-09 10:57:27 -08:00
|
|
|
args = ["-g", self._xrePath,
|
|
|
|
"-v", "170",
|
2013-06-26 20:42:46 -07:00
|
|
|
"-f", self._httpdPath + "/httpd.js",
|
2012-09-18 16:28:39 -07:00
|
|
|
"-e", """const _PROFILE_PATH = '%(profile)s';const _SERVER_PORT = '%(port)s'; const _SERVER_ADDR = '%(server)s';
|
|
|
|
const _TEST_PREFIX = %(testPrefix)s; const _DISPLAY_RESULTS = %(displayResults)s;""" %
|
|
|
|
{"profile" : self._profileDir.replace('\\', '\\\\'), "port" : self.httpPort, "server" : self.webServer,
|
|
|
|
"testPrefix" : self.testPrefix, "displayResults" : str(not self._closeWhenDone).lower() },
|
2008-01-28 19:48:34 -08:00
|
|
|
"-f", "./" + "server.js"]
|
|
|
|
|
2009-02-09 10:57:27 -08:00
|
|
|
xpcshell = os.path.join(self._utilityPath,
|
2013-07-26 11:40:04 -07:00
|
|
|
"xpcshell" + mozinfo.info['bin_suffix'])
|
2010-01-15 09:22:54 -08:00
|
|
|
self._process = self._automation.Process([xpcshell] + args, env = env)
|
2008-01-28 19:48:34 -08:00
|
|
|
pid = self._process.pid
|
|
|
|
if pid < 0:
|
2013-07-26 11:40:04 -07:00
|
|
|
log.error("Error starting server.")
|
2008-01-28 19:48:34 -08:00
|
|
|
sys.exit(2)
|
2013-07-26 11:40:04 -07:00
|
|
|
log.info("runtests.py | Server pid: %d", pid)
|
2008-01-28 19:48:34 -08:00
|
|
|
|
|
|
|
def ensureReady(self, timeout):
|
|
|
|
assert timeout >= 0
|
|
|
|
|
2010-01-15 09:22:54 -08:00
|
|
|
aliveFile = os.path.join(self._profileDir, "server_alive.txt")
|
2008-01-28 19:48:34 -08:00
|
|
|
i = 0
|
|
|
|
while i < timeout:
|
|
|
|
if os.path.exists(aliveFile):
|
|
|
|
break
|
|
|
|
time.sleep(1)
|
|
|
|
i += 1
|
|
|
|
else:
|
2013-07-26 11:40:04 -07:00
|
|
|
log.error("Timed out while waiting for server startup.")
|
2008-01-28 19:48:34 -08:00
|
|
|
self.stop()
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
try:
|
2011-04-18 09:41:57 -07:00
|
|
|
with urllib2.urlopen(self.shutdownURL) as c:
|
|
|
|
c.read()
|
2010-06-24 02:32:01 -07:00
|
|
|
|
|
|
|
rtncode = self._process.poll()
|
2010-09-30 17:10:19 -07:00
|
|
|
if rtncode is None:
|
2010-06-24 02:32:01 -07:00
|
|
|
self._process.terminate()
|
2008-01-28 19:48:34 -08:00
|
|
|
except:
|
2008-04-07 22:18:45 -07:00
|
|
|
self._process.kill()
|
2008-01-28 19:48:34 -08:00
|
|
|
|
2010-06-16 22:38:55 -07:00
|
|
|
class WebSocketServer(object):
|
|
|
|
"Class which encapsulates the mod_pywebsocket server"
|
|
|
|
|
2010-11-20 19:29:58 -08:00
|
|
|
def __init__(self, automation, options, scriptdir, debuggerInfo=None):
|
2010-06-16 22:38:55 -07:00
|
|
|
self.port = options.webSocketPort
|
|
|
|
self._automation = automation
|
|
|
|
self._scriptdir = scriptdir
|
2010-11-20 19:29:58 -08:00
|
|
|
self.debuggerInfo = debuggerInfo
|
2010-06-16 22:38:55 -07:00
|
|
|
|
|
|
|
def start(self):
|
2011-05-09 14:54:35 -07:00
|
|
|
# Invoke pywebsocket through a wrapper which adds special SIGINT handling.
|
|
|
|
#
|
|
|
|
# If we're in an interactive debugger, the wrapper causes the server to
|
|
|
|
# ignore SIGINT so the server doesn't capture a ctrl+c meant for the
|
|
|
|
# debugger.
|
|
|
|
#
|
|
|
|
# If we're not in an interactive debugger, the wrapper causes the server to
|
|
|
|
# die silently upon receiving a SIGINT.
|
|
|
|
scriptPath = 'pywebsocket_wrapper.py'
|
2010-11-20 19:29:58 -08:00
|
|
|
script = os.path.join(self._scriptdir, scriptPath)
|
2011-05-09 14:54:35 -07:00
|
|
|
|
|
|
|
cmd = [sys.executable, script]
|
|
|
|
if self.debuggerInfo and self.debuggerInfo['interactive']:
|
|
|
|
cmd += ['--interactive']
|
2011-12-20 00:20:12 -08:00
|
|
|
cmd += ['-p', str(self.port), '-w', self._scriptdir, '-l', \
|
|
|
|
os.path.join(self._scriptdir, "websock.log"), \
|
|
|
|
'--log-level=debug', '--allow-handlers-outside-root-dir']
|
2010-06-16 22:38:55 -07:00
|
|
|
|
|
|
|
self._process = self._automation.Process(cmd)
|
|
|
|
pid = self._process.pid
|
|
|
|
if pid < 0:
|
2013-07-26 11:40:04 -07:00
|
|
|
log.error("Error starting websocket server.")
|
2010-06-16 22:38:55 -07:00
|
|
|
sys.exit(2)
|
2013-07-26 11:40:04 -07:00
|
|
|
log.info("runtests.py | Websocket server pid: %d", pid)
|
2010-06-16 22:38:55 -07:00
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
self._process.kill()
|
2009-09-22 06:12:58 -07:00
|
|
|
|
2013-07-26 11:40:04 -07:00
|
|
|
class MochitestUtilsMixin(object):
|
|
|
|
"""
|
|
|
|
Class containing some utility functions common to both local and remote
|
|
|
|
mochitest runners
|
|
|
|
"""
|
|
|
|
|
|
|
|
# TODO Utility classes are a code smell. This class is temporary
|
|
|
|
# and should be removed when desktop mochitests are refactored
|
|
|
|
# on top of mozbase. Each of the functions in here should
|
|
|
|
# probably live somewhere in mozbase
|
|
|
|
|
|
|
|
oldcwd = os.getcwd()
|
|
|
|
jarDir = 'mochijar'
|
|
|
|
|
2010-01-15 09:22:54 -08:00
|
|
|
# Path to the test script on the server
|
2011-08-25 11:35:35 -07:00
|
|
|
TEST_PATH = "tests"
|
|
|
|
CHROME_PATH = "redirect.html"
|
2010-01-19 11:45:04 -08:00
|
|
|
urlOpts = []
|
2010-01-15 09:22:54 -08:00
|
|
|
|
2013-07-26 11:40:04 -07:00
|
|
|
def __init__(self):
|
|
|
|
os.chdir(SCRIPT_DIR)
|
2013-08-14 10:57:43 -07:00
|
|
|
path = SCRIPT_DIR
|
|
|
|
dirs = []
|
|
|
|
while path != os.path.expanduser('~'):
|
|
|
|
if path in dirs:
|
|
|
|
break
|
|
|
|
dirs.append(path)
|
|
|
|
path = os.path.split(path)[0]
|
|
|
|
|
|
|
|
mozinfo.find_and_update_from_json(*dirs)
|
2010-01-15 09:22:54 -08:00
|
|
|
|
|
|
|
def getFullPath(self, path):
|
2010-03-13 09:56:24 -08:00
|
|
|
" Get an absolute path relative to self.oldcwd."
|
2010-01-15 09:22:54 -08:00
|
|
|
return os.path.normpath(os.path.join(self.oldcwd, os.path.expanduser(path)))
|
|
|
|
|
2013-07-26 11:40:04 -07:00
|
|
|
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 buildURLOptions(self, options, env):
|
|
|
|
""" Add test control options from the command line to the url
|
|
|
|
|
|
|
|
URL parameters to test URL:
|
|
|
|
|
|
|
|
autorun -- kick off tests automatically
|
|
|
|
closeWhenDone -- closes the browser after the tests
|
|
|
|
hideResultsTable -- hides the table of individual test results
|
|
|
|
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
|
|
|
|
repeat -- How many times to repeat the test, ie: repeat=1 will run the test twice.
|
|
|
|
"""
|
|
|
|
|
|
|
|
# allow relative paths for logFile
|
|
|
|
if options.logFile:
|
|
|
|
options.logFile = self.getLogFilePath(options.logFile)
|
|
|
|
if options.browserChrome or options.chrome or options.a11y or options.webapprtChrome:
|
|
|
|
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")
|
|
|
|
if "MOZ_HIDE_RESULTS_TABLE" in env and env["MOZ_HIDE_RESULTS_TABLE"] == "1":
|
|
|
|
self.urlOpts.append("hideResultsTable=1")
|
|
|
|
if options.runUntilFailure:
|
|
|
|
self.urlOpts.append("runUntilFailure=1")
|
|
|
|
if options.repeat:
|
|
|
|
self.urlOpts.append("repeat=%d" % options.repeat)
|
|
|
|
if os.path.isfile(os.path.join(self.oldcwd, os.path.dirname(__file__), self.TEST_PATH, options.testPath)) and options.repeat > 0:
|
|
|
|
self.urlOpts.append("testname=%s" % ("/").join([self.TEST_PATH, options.testPath]))
|
|
|
|
if options.testManifest:
|
|
|
|
self.urlOpts.append("testManifest=%s" % options.testManifest)
|
|
|
|
if hasattr(options, 'runOnly') and options.runOnly:
|
|
|
|
self.urlOpts.append("runOnly=true")
|
|
|
|
else:
|
|
|
|
self.urlOpts.append("runOnly=false")
|
2013-08-02 05:48:06 -07:00
|
|
|
if options.manifestFile:
|
|
|
|
self.urlOpts.append("manifestFile=%s" % options.manifestFile)
|
2013-07-26 11:40:04 -07:00
|
|
|
if options.failureFile:
|
|
|
|
self.urlOpts.append("failureFile=%s" % self.getFullPath(options.failureFile))
|
|
|
|
if options.runSlower:
|
|
|
|
self.urlOpts.append("runSlower=true")
|
|
|
|
|
2010-01-19 11:45:04 -08:00
|
|
|
def buildTestPath(self, options):
|
2013-08-02 05:48:06 -07:00
|
|
|
""" Build the url path to the specific test harness and test file or directory
|
|
|
|
Build a manifest of tests to run and write out a json file for the harness to read
|
|
|
|
"""
|
|
|
|
if options.manifestFile and os.path.isfile(options.manifestFile):
|
|
|
|
manifest = TestManifest(strict=False)
|
|
|
|
manifest.read(options.manifestFile)
|
|
|
|
# Bug 883858 - return all tests including disabled tests
|
|
|
|
tests = manifest.active_tests(disabled=False, **mozinfo.info)
|
|
|
|
paths = []
|
|
|
|
for test in tests:
|
|
|
|
tp = test['path'].split(self.getTestRoot(options), 1)[1].strip('/')
|
|
|
|
|
|
|
|
# Filter out tests if we are using --test-path
|
|
|
|
if options.testPath and not tp.startswith(options.testPath):
|
|
|
|
continue
|
|
|
|
|
|
|
|
paths.append({'path': tp})
|
|
|
|
|
|
|
|
# Bug 883865 - add this functionality into manifestDestiny
|
|
|
|
with open('tests.json', 'w') as manifestFile:
|
|
|
|
manifestFile.write(json.dumps({'tests': paths}))
|
|
|
|
options.manifestFile = 'tests.json'
|
|
|
|
|
2010-03-13 09:56:24 -08:00
|
|
|
testHost = "http://mochi.test:8888"
|
2011-08-25 11:35:35 -07:00
|
|
|
testURL = ("/").join([testHost, self.TEST_PATH, options.testPath])
|
2011-08-29 13:14:33 -07:00
|
|
|
if os.path.isfile(os.path.join(self.oldcwd, os.path.dirname(__file__), self.TEST_PATH, options.testPath)) and options.repeat > 0:
|
2013-07-26 11:40:04 -07:00
|
|
|
testURL = ("/").join([testHost, self.PLAIN_LOOP_PATH])
|
2011-05-17 10:10:37 -07:00
|
|
|
if options.chrome or options.a11y:
|
2011-08-25 11:35:35 -07:00
|
|
|
testURL = ("/").join([testHost, self.CHROME_PATH])
|
2010-01-19 11:45:04 -08:00
|
|
|
elif options.browserChrome:
|
|
|
|
testURL = "about:blank"
|
2011-05-13 07:06:35 -07:00
|
|
|
elif options.ipcplugins:
|
2011-08-25 11:35:35 -07:00
|
|
|
testURL = ("/").join([testHost, self.TEST_PATH, "dom/plugins/test"])
|
2010-01-19 11:45:04 -08:00
|
|
|
return testURL
|
|
|
|
|
2010-11-20 19:29:58 -08:00
|
|
|
def startWebSocketServer(self, options, debuggerInfo):
|
2010-06-16 22:38:55 -07:00
|
|
|
""" Launch the websocket server """
|
|
|
|
if options.webServer != '127.0.0.1':
|
|
|
|
return
|
|
|
|
|
2010-11-20 19:29:58 -08:00
|
|
|
self.wsserver = WebSocketServer(self.automation, options,
|
2013-07-26 11:40:04 -07:00
|
|
|
SCRIPT_DIR, debuggerInfo)
|
2010-06-16 22:38:55 -07:00
|
|
|
self.wsserver.start()
|
|
|
|
|
|
|
|
def stopWebSocketServer(self, options):
|
|
|
|
if options.webServer != '127.0.0.1':
|
|
|
|
return
|
|
|
|
|
|
|
|
self.wsserver.stop()
|
|
|
|
|
2010-01-19 11:45:04 -08:00
|
|
|
def startWebServer(self, options):
|
2010-03-13 09:56:24 -08:00
|
|
|
if options.webServer != '127.0.0.1':
|
|
|
|
return
|
|
|
|
|
|
|
|
""" Create the webserver and start it up """
|
|
|
|
self.server = MochitestServer(self.automation, options)
|
2010-01-19 11:45:04 -08:00
|
|
|
self.server.start()
|
2010-01-15 09:22:54 -08:00
|
|
|
|
2010-01-19 11:45:04 -08:00
|
|
|
# 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)
|
|
|
|
|
2010-03-13 09:56:24 -08:00
|
|
|
def stopWebServer(self, options):
|
2010-01-19 11:45:04 -08:00
|
|
|
""" 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.
|
|
|
|
"""
|
2010-03-13 09:56:24 -08:00
|
|
|
if options.webServer != '127.0.0.1':
|
|
|
|
return
|
|
|
|
|
2010-01-19 11:45:04 -08:00
|
|
|
self.server.stop()
|
|
|
|
|
2013-07-26 11:40:04 -07:00
|
|
|
|
|
|
|
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)
|
|
|
|
if os.path.isfile(abspath):
|
|
|
|
shutil.copy2(abspath, options.profilePath)
|
|
|
|
elif os.path.isdir(abspath):
|
|
|
|
dest = os.path.join(options.profilePath, os.path.basename(abspath))
|
|
|
|
shutil.copytree(abspath, dest)
|
|
|
|
else:
|
|
|
|
log.warning("runtests.py | Failed to copy %s to profile", abspath)
|
|
|
|
continue
|
|
|
|
|
|
|
|
def copyTestsJarToProfile(self, options):
|
|
|
|
""" copy tests.jar to the profile directory so we can auto register it in the .xul harness """
|
|
|
|
testsJarFile = os.path.join(SCRIPT_DIR, "tests.jar")
|
|
|
|
if not os.path.isfile(testsJarFile):
|
|
|
|
return False
|
|
|
|
|
|
|
|
shutil.copy2(testsJarFile, options.profilePath)
|
|
|
|
return True
|
|
|
|
|
|
|
|
def installChromeJar(self, chrome, options):
|
2010-01-19 11:45:04 -08:00
|
|
|
"""
|
2013-07-26 11:40:04 -07:00
|
|
|
copy mochijar directory to profile as an extension so we have chrome://mochikit for all harness code
|
|
|
|
"""
|
|
|
|
# Write chrome.manifest.
|
|
|
|
with open(os.path.join(options.profilePath, "extensions", "staged", "mochikit@mozilla.org", "chrome.manifest"), "a") as mfile:
|
|
|
|
mfile.write(chrome)
|
|
|
|
|
|
|
|
def addChromeToProfile(self, options):
|
|
|
|
"Adds MochiKit chrome tests to the profile."
|
|
|
|
|
|
|
|
# Create (empty) chrome directory.
|
|
|
|
chromedir = os.path.join(options.profilePath, "chrome")
|
|
|
|
os.mkdir(chromedir)
|
|
|
|
|
|
|
|
# Write userChrome.css.
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
with open(os.path.join(options.profilePath, "userChrome.css"), "a") as chromeFile:
|
|
|
|
chromeFile.write(chrome)
|
|
|
|
|
|
|
|
# Call copyTestsJarToProfile(), Write tests.manifest.
|
|
|
|
manifest = os.path.join(options.profilePath, "tests.manifest")
|
|
|
|
with open(manifest, "w") as manifestFile:
|
|
|
|
if self.copyTestsJarToProfile(options):
|
|
|
|
# Register tests.jar.
|
|
|
|
manifestFile.write("content mochitests jar:tests.jar!/content/\n");
|
|
|
|
else:
|
|
|
|
# Register chrome directory.
|
|
|
|
chrometestDir = os.path.abspath(".") + "/"
|
|
|
|
if self.automation.IS_WIN32:
|
|
|
|
chrometestDir = "file:///" + chrometestDir.replace("\\", "/")
|
|
|
|
manifestFile.write("content mochitests %s contentaccessible=yes\n" % chrometestDir)
|
|
|
|
|
|
|
|
if options.testingModulesDir is not None:
|
|
|
|
manifestFile.write("resource testing-common file:///%s\n" %
|
|
|
|
options.testingModulesDir)
|
|
|
|
|
|
|
|
# Call installChromeJar().
|
|
|
|
if not os.path.isdir(os.path.join(SCRIPT_DIR, self.jarDir)):
|
|
|
|
log.testFail("invalid setup: missing mochikit extension")
|
|
|
|
return None
|
|
|
|
|
|
|
|
# Support Firefox (browser), B2G (shell), SeaMonkey (navigator), and Webapp
|
|
|
|
# Runtime (webapp).
|
|
|
|
chrome = ""
|
|
|
|
if options.browserChrome or options.chrome or options.a11y or options.webapprtChrome:
|
|
|
|
chrome += """
|
|
|
|
overlay chrome://browser/content/browser.xul chrome://mochikit/content/browser-test-overlay.xul
|
|
|
|
overlay chrome://browser/content/shell.xul chrome://mochikit/content/browser-test-overlay.xul
|
|
|
|
overlay chrome://navigator/content/navigator.xul chrome://mochikit/content/browser-test-overlay.xul
|
|
|
|
overlay chrome://webapprt/content/webapp.xul chrome://mochikit/content/browser-test-overlay.xul
|
|
|
|
"""
|
|
|
|
|
|
|
|
self.installChromeJar(chrome, options)
|
|
|
|
return manifest
|
|
|
|
|
|
|
|
|
|
|
|
def getExtensionsToInstall(self, options):
|
|
|
|
"Return a list of extensions to install in the profile"
|
|
|
|
extensions = options.extensionsToInstall or []
|
|
|
|
appDir = options.app[:options.app.rfind(os.sep)] if options.app else options.utilityPath
|
|
|
|
|
|
|
|
extensionDirs = [
|
|
|
|
# Extensions distributed with the test harness.
|
|
|
|
os.path.normpath(os.path.join(SCRIPT_DIR, "extensions")),
|
|
|
|
]
|
|
|
|
if appDir:
|
|
|
|
# Extensions distributed with the application.
|
|
|
|
extensionDirs.append(os.path.join(appDir, "distribution", "extensions"))
|
|
|
|
|
|
|
|
for extensionDir in extensionDirs:
|
|
|
|
if os.path.isdir(extensionDir):
|
|
|
|
for dirEntry in os.listdir(extensionDir):
|
|
|
|
if dirEntry not in options.extensionsToExclude:
|
|
|
|
path = os.path.join(extensionDir, dirEntry)
|
|
|
|
if os.path.isdir(path) or (os.path.isfile(path) and path.endswith(".xpi")):
|
|
|
|
extensions.append(path)
|
|
|
|
|
|
|
|
# append mochikit
|
|
|
|
extensions.append(os.path.join(SCRIPT_DIR, self.jarDir))
|
|
|
|
return extensions
|
|
|
|
|
|
|
|
|
|
|
|
class Mochitest(MochitestUtilsMixin):
|
|
|
|
runSSLTunnel = True
|
|
|
|
vmwareHelper = None
|
|
|
|
|
|
|
|
def __init__(self, automation=None):
|
|
|
|
super(Mochitest, self).__init__()
|
|
|
|
self.automation = automation or 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
|
2010-01-19 11:45:04 -08:00
|
|
|
|
|
|
|
def buildProfile(self, options):
|
|
|
|
""" create the profile and add optional chrome bits and files if requested """
|
2013-02-19 10:27:28 -08:00
|
|
|
if options.browserChrome and options.timeout:
|
|
|
|
options.extraPrefs.append("testing.browserTestHarness.timeout=%d" % options.timeout)
|
2013-07-30 05:30:40 -07:00
|
|
|
|
|
|
|
# get extensions to install
|
|
|
|
extensions = self.getExtensionsToInstall(options)
|
|
|
|
|
|
|
|
# create a profile
|
|
|
|
appsPath = os.path.join(SCRIPT_DIR, 'profile_data', 'webapps_mochitest.json')
|
|
|
|
appsPath = appsPath if os.path.exists(appsPath) else None
|
|
|
|
prefsPath = os.path.join(SCRIPT_DIR, 'profile_data', 'prefs_general.js')
|
|
|
|
profile = self.automation.initializeProfile(options.profilePath,
|
|
|
|
options.extraPrefs,
|
|
|
|
useServerLocations=True,
|
|
|
|
appsPath=appsPath,
|
|
|
|
prefsPath=prefsPath,
|
|
|
|
addons=extensions)
|
|
|
|
|
|
|
|
#if we don't do this, the profile object is destroyed when we exit this method
|
|
|
|
self.profile = profile
|
|
|
|
options.profilePath = profile.profile
|
|
|
|
|
|
|
|
manifest = self.addChromeToProfile(options)
|
2010-01-19 11:45:04 -08:00
|
|
|
self.copyExtraFilesToProfile(options)
|
2013-07-30 05:30:40 -07:00
|
|
|
return manifest
|
2010-01-19 11:45:04 -08:00
|
|
|
|
|
|
|
def buildBrowserEnv(self, options):
|
|
|
|
""" build the environment variables for the specific test and operating system """
|
2010-01-15 09:22:54 -08:00
|
|
|
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:
|
2013-07-26 11:40:04 -07:00
|
|
|
log.error("Error: syntax error in --setenv=" + v)
|
2010-01-19 11:45:04 -08:00
|
|
|
return None
|
2010-01-15 09:22:54 -08:00
|
|
|
browserEnv[v[:ix]] = v[ix + 1:]
|
|
|
|
|
2010-03-13 09:56:24 -08:00
|
|
|
browserEnv["XPCOM_MEM_BLOAT_LOG"] = self.leak_report_file
|
2010-01-15 09:22:54 -08:00
|
|
|
|
2010-01-19 11:45:04 -08:00
|
|
|
if options.fatalAssertions:
|
|
|
|
browserEnv["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
|
2010-01-15 09:22:54 -08:00
|
|
|
|
2010-01-19 11:45:04 -08:00
|
|
|
return browserEnv
|
|
|
|
|
2010-03-13 09:56:24 -08:00
|
|
|
def cleanup(self, manifest, options):
|
2010-01-19 11:45:04 -08:00
|
|
|
""" remove temporary files and profile """
|
|
|
|
os.remove(manifest)
|
2010-03-13 09:56:24 -08:00
|
|
|
shutil.rmtree(options.profilePath)
|
2010-01-15 09:22:54 -08:00
|
|
|
|
2010-05-17 11:00:13 -07:00
|
|
|
def startVMwareRecording(self, options):
|
2010-03-15 14:44:29 -07:00
|
|
|
""" starts recording inside VMware VM using the recording helper dll """
|
|
|
|
assert(self.automation.IS_WIN32)
|
|
|
|
from ctypes import cdll
|
|
|
|
self.vmwareHelper = cdll.LoadLibrary(self.vmwareHelperPath)
|
|
|
|
if self.vmwareHelper is None:
|
2013-07-26 11:40:04 -07:00
|
|
|
log.warning("runtests.py | Failed to load "
|
|
|
|
"VMware recording helper")
|
2010-03-15 14:44:29 -07:00
|
|
|
return
|
2013-07-26 11:40:04 -07:00
|
|
|
log.info("runtests.py | Starting VMware recording.")
|
2010-03-15 14:44:29 -07:00
|
|
|
try:
|
|
|
|
self.vmwareHelper.StartRecording()
|
|
|
|
except Exception, e:
|
2013-07-26 11:40:04 -07:00
|
|
|
log.warning("runtests.py | Failed to start "
|
|
|
|
"VMware recording: (%s)" % str(e))
|
2010-03-15 14:44:29 -07:00
|
|
|
self.vmwareHelper = None
|
|
|
|
|
2010-05-17 11:00:13 -07:00
|
|
|
def stopVMwareRecording(self):
|
2010-03-15 14:44:29 -07:00
|
|
|
""" stops recording inside VMware VM using the recording helper dll """
|
|
|
|
assert(self.automation.IS_WIN32)
|
|
|
|
if self.vmwareHelper is not None:
|
2013-07-26 11:40:04 -07:00
|
|
|
log.info("runtests.py | Stopping VMware "
|
|
|
|
"recording.")
|
2010-03-15 14:44:29 -07:00
|
|
|
try:
|
|
|
|
self.vmwareHelper.StopRecording()
|
|
|
|
except Exception, e:
|
2013-07-26 11:40:04 -07:00
|
|
|
log.warning("runtests.py | Failed to stop "
|
|
|
|
"VMware recording: (%s)" % str(e))
|
2010-03-15 14:44:29 -07:00
|
|
|
self.vmwareHelper = None
|
|
|
|
|
2013-01-04 10:41:34 -08:00
|
|
|
def runTests(self, options, onLaunch=None):
|
2010-01-19 11:45:04 -08:00
|
|
|
""" Prepare, configure, run tests and cleanup """
|
|
|
|
debuggerInfo = getDebuggerInfo(self.oldcwd, options.debugger, options.debuggerArgs,
|
|
|
|
options.debuggerInteractive);
|
|
|
|
|
2010-03-13 09:56:24 -08:00
|
|
|
self.leak_report_file = os.path.join(options.profilePath, "runtests_leaks.log")
|
|
|
|
|
2010-01-19 11:45:04 -08:00
|
|
|
browserEnv = self.buildBrowserEnv(options)
|
2010-09-30 17:10:19 -07:00
|
|
|
if browserEnv is None:
|
2010-01-19 11:45:04 -08:00
|
|
|
return 1
|
|
|
|
|
|
|
|
manifest = self.buildProfile(options)
|
2010-09-30 17:10:19 -07:00
|
|
|
if manifest is None:
|
|
|
|
return 1
|
2010-12-22 08:39:29 -08:00
|
|
|
|
2010-01-19 11:45:04 -08:00
|
|
|
self.startWebServer(options)
|
2010-11-20 19:29:58 -08:00
|
|
|
self.startWebSocketServer(options, debuggerInfo)
|
2010-01-19 11:45:04 -08:00
|
|
|
|
|
|
|
testURL = self.buildTestPath(options)
|
2011-07-26 16:13:20 -07:00
|
|
|
self.buildURLOptions(options, browserEnv)
|
2010-09-30 17:10:19 -07:00
|
|
|
if len(self.urlOpts) > 0:
|
2010-01-19 11:45:04 -08:00
|
|
|
testURL += "?" + "&".join(self.urlOpts)
|
|
|
|
|
2012-06-29 15:52:43 -07:00
|
|
|
if options.webapprtContent:
|
|
|
|
options.browserArgs.extend(('-test-mode', testURL))
|
|
|
|
testURL = None
|
|
|
|
|
2013-02-12 12:51:24 -08:00
|
|
|
if options.immersiveMode:
|
2013-03-26 15:01:15 -07:00
|
|
|
options.browserArgs.extend(('-firefoxpath', options.app))
|
2013-02-12 12:51:24 -08:00
|
|
|
options.app = self.immersiveHelperPath
|
|
|
|
|
2010-01-15 09:22:54 -08:00
|
|
|
# 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.
|
2010-03-13 09:56:24 -08:00
|
|
|
if os.path.exists(self.leak_report_file):
|
|
|
|
os.remove(self.leak_report_file)
|
2010-01-15 09:22:54 -08:00
|
|
|
|
|
|
|
# then again to actually run mochitest
|
|
|
|
if options.timeout:
|
2009-10-29 16:20:42 -07:00
|
|
|
timeout = options.timeout + 30
|
2013-03-13 13:12:07 -07:00
|
|
|
elif options.debugger or not options.autorun:
|
2009-10-30 10:00:19 -07:00
|
|
|
timeout = None
|
2010-01-15 09:22:54 -08:00
|
|
|
else:
|
2009-10-29 16:20:42 -07:00
|
|
|
timeout = 330.0 # default JS harness timeout is 300 seconds
|
2010-03-15 14:44:29 -07:00
|
|
|
|
|
|
|
if options.vmwareRecording:
|
2010-05-17 11:00:13 -07:00
|
|
|
self.startVMwareRecording(options);
|
2010-03-15 14:44:29 -07:00
|
|
|
|
2013-07-26 11:40:04 -07:00
|
|
|
log.info("runtests.py | Running tests: start.\n")
|
2011-05-09 14:54:35 -07:00
|
|
|
try:
|
|
|
|
status = self.automation.runApp(testURL, browserEnv, options.app,
|
|
|
|
options.profilePath, options.browserArgs,
|
2013-01-04 10:41:34 -08:00
|
|
|
runSSLTunnel=self.runSSLTunnel,
|
|
|
|
utilityPath=options.utilityPath,
|
|
|
|
xrePath=options.xrePath,
|
2011-05-09 14:54:35 -07:00
|
|
|
certPath=options.certPath,
|
|
|
|
debuggerInfo=debuggerInfo,
|
|
|
|
symbolsPath=options.symbolsPath,
|
2013-01-04 10:41:34 -08:00
|
|
|
timeout=timeout,
|
|
|
|
onLaunch=onLaunch)
|
2011-05-09 14:54:35 -07:00
|
|
|
except KeyboardInterrupt:
|
2013-07-26 11:40:04 -07:00
|
|
|
log.info("runtests.py | Received keyboard interrupt.\n");
|
2011-05-09 14:54:35 -07:00
|
|
|
status = -1
|
2011-09-14 16:20:11 -07:00
|
|
|
except:
|
2013-07-26 11:40:04 -07:00
|
|
|
traceback.print_exc()
|
|
|
|
log.error("runtests.py | Received unexpected exception while running application\n")
|
2011-09-14 16:20:11 -07:00
|
|
|
status = 1
|
2010-01-15 09:22:54 -08:00
|
|
|
|
2010-03-15 14:44:29 -07:00
|
|
|
if options.vmwareRecording:
|
2010-05-17 11:00:13 -07:00
|
|
|
self.stopVMwareRecording();
|
2010-03-15 14:44:29 -07:00
|
|
|
|
2010-03-13 09:56:24 -08:00
|
|
|
self.stopWebServer(options)
|
2010-06-16 22:38:55 -07:00
|
|
|
self.stopWebSocketServer(options)
|
2010-03-13 09:56:24 -08:00
|
|
|
processLeakLog(self.leak_report_file, options.leakThreshold)
|
2012-02-25 03:48:05 -08:00
|
|
|
|
2013-07-26 11:40:04 -07:00
|
|
|
log.info("runtests.py | Running tests: end.")
|
2010-01-15 09:22:54 -08:00
|
|
|
|
2010-09-30 17:10:19 -07:00
|
|
|
if manifest is not None:
|
|
|
|
self.cleanup(manifest, options)
|
2010-01-15 09:22:54 -08:00
|
|
|
return status
|
|
|
|
|
|
|
|
def makeTestConfig(self, options):
|
|
|
|
"Creates a test configuration file for customizing test execution."
|
2011-05-17 10:10:37 -07:00
|
|
|
def jsonString(val):
|
|
|
|
if isinstance(val, bool):
|
|
|
|
if val:
|
|
|
|
return "true"
|
|
|
|
return "false"
|
|
|
|
elif val is None:
|
|
|
|
return '""'
|
|
|
|
elif isinstance(val, basestring):
|
|
|
|
return '"%s"' % (val.replace('\\', '\\\\'))
|
|
|
|
elif isinstance(val, int):
|
|
|
|
return '%s' % (val)
|
|
|
|
elif isinstance(val, list):
|
|
|
|
content = '['
|
|
|
|
first = True
|
|
|
|
for item in val:
|
|
|
|
if first:
|
|
|
|
first = False
|
|
|
|
else:
|
|
|
|
content += ", "
|
|
|
|
content += jsonString(item)
|
|
|
|
content += ']'
|
|
|
|
return content
|
|
|
|
else:
|
|
|
|
print "unknown type: %s: %s" % (opt, val)
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
options.logFile = options.logFile.replace("\\", "\\\\")
|
|
|
|
options.testPath = options.testPath.replace("\\", "\\\\")
|
2013-03-28 09:23:03 -07:00
|
|
|
testRoot = self.getTestRoot(options)
|
2012-06-29 15:52:43 -07:00
|
|
|
|
2011-07-26 16:13:20 -07:00
|
|
|
if "MOZ_HIDE_RESULTS_TABLE" in os.environ and os.environ["MOZ_HIDE_RESULTS_TABLE"] == "1":
|
|
|
|
options.hideResultsTable = True
|
|
|
|
|
2011-05-17 10:10:37 -07:00
|
|
|
#TODO: when we upgrade to python 2.6, just use json.dumps(options.__dict__)
|
|
|
|
content = "{"
|
2013-07-26 11:40:04 -07:00
|
|
|
content += '"testRoot": "%s", ' % (testRoot)
|
2011-05-17 10:10:37 -07:00
|
|
|
first = True
|
|
|
|
for opt in options.__dict__.keys():
|
|
|
|
val = options.__dict__[opt]
|
|
|
|
if first:
|
|
|
|
first = False
|
|
|
|
else:
|
|
|
|
content += ", "
|
|
|
|
|
|
|
|
content += '"' + opt + '": '
|
|
|
|
content += jsonString(val)
|
|
|
|
content += "}"
|
2008-01-28 19:48:34 -08:00
|
|
|
|
2011-04-18 09:41:57 -07:00
|
|
|
with open(os.path.join(options.profilePath, "testConfig.js"), "w") as config:
|
|
|
|
config.write(content)
|
2008-01-28 19:48:34 -08:00
|
|
|
|
2013-03-28 09:23:03 -07:00
|
|
|
def getTestRoot(self, options):
|
|
|
|
if (options.browserChrome):
|
|
|
|
if (options.immersiveMode):
|
|
|
|
return 'metro'
|
|
|
|
return 'browser'
|
|
|
|
elif (options.a11y):
|
|
|
|
return 'a11y'
|
|
|
|
elif (options.webapprtChrome):
|
|
|
|
return 'webapprtChrome'
|
|
|
|
elif (options.chrome):
|
|
|
|
return 'chrome'
|
|
|
|
return self.TEST_PATH
|
|
|
|
|
2013-03-28 13:42:49 -07:00
|
|
|
def installExtensionFromPath(self, options, path, extensionID = None):
|
|
|
|
extensionPath = self.getFullPath(path)
|
|
|
|
|
2013-07-26 11:40:04 -07:00
|
|
|
log.info("runtests.py | Installing extension at %s to %s." %
|
|
|
|
(extensionPath, options.profilePath))
|
2013-03-28 13:42:49 -07:00
|
|
|
self.automation.installExtension(extensionPath, options.profilePath,
|
|
|
|
extensionID)
|
|
|
|
|
|
|
|
def installExtensionsToProfile(self, options):
|
|
|
|
"Install special testing extensions, application distributed extensions, and specified on the command line ones to testing profile."
|
|
|
|
for path in self.getExtensionsToInstall(options):
|
2011-10-14 08:53:06 -07:00
|
|
|
self.installExtensionFromPath(options, path)
|
2010-11-18 14:15:54 -08:00
|
|
|
|
2010-01-15 09:22:54 -08:00
|
|
|
def main():
|
|
|
|
automation = Automation()
|
|
|
|
mochitest = Mochitest(automation)
|
2013-07-26 11:40:04 -07:00
|
|
|
parser = MochitestOptions(automation)
|
2010-01-15 09:22:54 -08:00
|
|
|
options, args = parser.parse_args()
|
|
|
|
|
2010-03-13 09:56:24 -08:00
|
|
|
options = parser.verifyOptions(options, mochitest)
|
|
|
|
if options == None:
|
2010-01-15 09:22:54 -08:00
|
|
|
sys.exit(1)
|
|
|
|
|
2010-03-09 19:33:11 -08:00
|
|
|
options.utilityPath = mochitest.getFullPath(options.utilityPath)
|
|
|
|
options.certPath = mochitest.getFullPath(options.certPath)
|
2010-05-06 05:06:09 -07:00
|
|
|
if options.symbolsPath and not isURL(options.symbolsPath):
|
2010-03-09 19:33:11 -08:00
|
|
|
options.symbolsPath = mochitest.getFullPath(options.symbolsPath)
|
|
|
|
|
2013-07-26 11:40:04 -07:00
|
|
|
automation.setServerInfo(options.webServer,
|
|
|
|
options.httpPort,
|
|
|
|
options.sslPort,
|
2010-07-28 10:55:36 -07:00
|
|
|
options.webSocketPort)
|
2010-01-15 09:22:54 -08:00
|
|
|
sys.exit(mochitest.runTests(options))
|
2008-01-28 19:48:34 -08:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|