mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
8cffe8fa6b
Mozlog currently has two implementations. The top level package is based on the logging module and is deprecated. The newer structured logging implementation lives in mozlog.structured. This patch swaps the two, so the top level mozlog module contains the recommended implementation, while mozlog.unstructured contains the old one.
538 lines
20 KiB
Python
538 lines
20 KiB
Python
# 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/.
|
|
|
|
import json
|
|
import os
|
|
import posixpath
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
import traceback
|
|
|
|
here = os.path.abspath(os.path.dirname(__file__))
|
|
sys.path.insert(0, here)
|
|
|
|
from runtests import Mochitest
|
|
from runtests import MochitestUtilsMixin
|
|
from mochitest_options import MochitestArgumentParser
|
|
from marionette import Marionette
|
|
from mozprofile import Profile, Preferences
|
|
import mozinfo
|
|
import mozleak
|
|
|
|
|
|
class B2GMochitest(MochitestUtilsMixin):
|
|
marionette = None
|
|
|
|
def __init__(self, marionette_args,
|
|
logger_options,
|
|
out_of_process=True,
|
|
profile_data_dir=None,
|
|
locations=os.path.join(here, 'server-locations.txt')):
|
|
super(B2GMochitest, self).__init__(logger_options)
|
|
self.marionette_args = marionette_args
|
|
self.out_of_process = out_of_process
|
|
self.locations_file = locations
|
|
self.preferences = []
|
|
self.webapps = None
|
|
self.test_script = os.path.join(here, 'b2g_start_script.js')
|
|
self.test_script_args = [self.out_of_process]
|
|
self.product = 'b2g'
|
|
self.remote_chrome_test_dir = None
|
|
|
|
if profile_data_dir:
|
|
self.preferences = [
|
|
os.path.join(
|
|
profile_data_dir,
|
|
f) for f in os.listdir(profile_data_dir) if f.startswith('pref')]
|
|
self.webapps = [
|
|
os.path.join(
|
|
profile_data_dir,
|
|
f) for f in os.listdir(profile_data_dir) if f.startswith('webapp')]
|
|
|
|
# mozinfo is populated by the parent class
|
|
if mozinfo.info['debug']:
|
|
self.SERVER_STARTUP_TIMEOUT = 180
|
|
else:
|
|
self.SERVER_STARTUP_TIMEOUT = 90
|
|
|
|
def setup_common_options(self, options):
|
|
test_url = self.buildTestPath(options)
|
|
# For B2G emulators buildURLOptions has been called
|
|
# without calling buildTestPath first and that
|
|
# causes manifestFile not to be set
|
|
if not "manifestFile=tests.json" in self.urlOpts:
|
|
self.urlOpts.append("manifestFile=%s" % options.manifestFile)
|
|
|
|
if len(self.urlOpts) > 0:
|
|
test_url += "?" + "&".join(self.urlOpts)
|
|
self.test_script_args.append(test_url)
|
|
|
|
def buildTestPath(self, options, testsToFilter=None):
|
|
if options.manifestFile != 'tests.json':
|
|
super(B2GMochitest, self).buildTestPath(options, testsToFilter, disabled=False)
|
|
return self.buildTestURL(options)
|
|
|
|
def build_profile(self, options):
|
|
# preferences
|
|
prefs = {}
|
|
for path in self.preferences:
|
|
prefs.update(Preferences.read_prefs(path))
|
|
|
|
for v in options.extraPrefs:
|
|
thispref = v.split("=", 1)
|
|
if len(thispref) < 2:
|
|
print "Error: syntax error in --setpref=" + v
|
|
sys.exit(1)
|
|
prefs[thispref[0]] = thispref[1]
|
|
|
|
# interpolate the preferences
|
|
interpolation = {
|
|
"server": "%s:%s" %
|
|
(options.webServer,
|
|
options.httpPort),
|
|
"OOP": "true" if self.out_of_process else "false"}
|
|
prefs = json.loads(json.dumps(prefs) % interpolation)
|
|
for pref in prefs:
|
|
prefs[pref] = Preferences.cast(prefs[pref])
|
|
|
|
kwargs = {
|
|
'addons': self.getExtensionsToInstall(options),
|
|
'apps': self.webapps,
|
|
'locations': self.locations_file,
|
|
'preferences': prefs,
|
|
'proxy': {"remote": options.webServer}
|
|
}
|
|
|
|
if options.profile:
|
|
self.profile = Profile.clone(options.profile, **kwargs)
|
|
else:
|
|
self.profile = Profile(**kwargs)
|
|
|
|
options.profilePath = self.profile.profile
|
|
# TODO bug 839108 - mozprofile should probably handle this
|
|
manifest = self.addChromeToProfile(options)
|
|
self.copyExtraFilesToProfile(options)
|
|
return manifest
|
|
|
|
def run_tests(self, options):
|
|
""" Prepare, configure, run tests and cleanup """
|
|
|
|
self.setTestRoot(options)
|
|
|
|
manifest = self.build_profile(options)
|
|
self.logPreamble(self.getActiveTests(options))
|
|
|
|
# configuring the message logger's buffering
|
|
self.message_logger.buffering = options.quiet
|
|
|
|
if options.debugger or not options.autorun:
|
|
timeout = None
|
|
else:
|
|
if not options.timeout:
|
|
if mozinfo.info['debug']:
|
|
options.timeout = 420
|
|
else:
|
|
options.timeout = 300
|
|
timeout = options.timeout + 30.0
|
|
|
|
self.log.info("runtestsb2g.py | Running tests: start.")
|
|
status = 0
|
|
try:
|
|
def on_output(line):
|
|
messages = self.message_logger.write(line)
|
|
for message in messages:
|
|
if message['action'] == 'test_start':
|
|
self.runner.last_test = message['test']
|
|
|
|
# The logging will be handled by on_output, so we set the stream to
|
|
# None
|
|
process_args = {'processOutputLine': on_output,
|
|
'stream': None}
|
|
self.marionette_args['process_args'] = process_args
|
|
self.marionette_args['profile'] = self.profile
|
|
|
|
self.marionette = Marionette(**self.marionette_args)
|
|
self.runner = self.marionette.runner
|
|
self.app_ctx = self.runner.app_ctx
|
|
|
|
self.remote_log = posixpath.join(self.app_ctx.remote_test_root,
|
|
'log', 'mochitest.log')
|
|
if not self.app_ctx.dm.dirExists(
|
|
posixpath.dirname(
|
|
self.remote_log)):
|
|
self.app_ctx.dm.mkDirs(self.remote_log)
|
|
|
|
if options.chrome:
|
|
# Update chrome manifest file in profile with correct path.
|
|
self.writeChromeManifest(options)
|
|
|
|
self.leak_report_file = posixpath.join(
|
|
self.app_ctx.remote_test_root,
|
|
'log',
|
|
'runtests_leaks.log')
|
|
|
|
# We don't want to copy the host env onto the device, so pass in an
|
|
# empty env.
|
|
self.browserEnv = self.buildBrowserEnv(options, env={})
|
|
|
|
# B2G emulator debug tests still make external connections, so don't
|
|
# pass MOZ_DISABLE_NONLOCAL_CONNECTIONS to them for now (bug
|
|
# 1039019).
|
|
if mozinfo.info[
|
|
'debug'] and 'MOZ_DISABLE_NONLOCAL_CONNECTIONS' in self.browserEnv:
|
|
del self.browserEnv['MOZ_DISABLE_NONLOCAL_CONNECTIONS']
|
|
self.runner.env.update(self.browserEnv)
|
|
|
|
# Despite our efforts to clean up servers started by this script, in practice
|
|
# we still see infrequent cases where a process is orphaned and interferes
|
|
# with future tests, typically because the old server is keeping the port in use.
|
|
# Try to avoid those failures by checking for and killing orphan servers before
|
|
# trying to start new ones.
|
|
self.killNamedOrphans('ssltunnel')
|
|
self.killNamedOrphans('xpcshell')
|
|
|
|
self.startServers(options, None)
|
|
|
|
# In desktop mochitests buildTestPath is called before buildURLOptions. This
|
|
# means options.manifestFile has already been converted to the proper json
|
|
# style manifest. Not so with B2G, that conversion along with updating the URL
|
|
# option will happen later. So backup and restore options.manifestFile to
|
|
# prevent us from trying to pass in an instance of TestManifest via url param.
|
|
manifestFile = options.manifestFile
|
|
options.manifestFile = None
|
|
self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'})
|
|
options.manifestFile = manifestFile
|
|
|
|
self.test_script_args.append(not options.emulator)
|
|
self.test_script_args.append(options.wifi)
|
|
self.test_script_args.append(options.chrome)
|
|
|
|
self.runner.start(outputTimeout=timeout)
|
|
|
|
self.marionette.wait_for_port()
|
|
self.marionette.start_session()
|
|
self.marionette.set_context(self.marionette.CONTEXT_CHROME)
|
|
|
|
# Disable offline status management (bug 777145), otherwise the network
|
|
# will be 'offline' when the mochitests start. Presumably, the network
|
|
# won't be offline on a real device, so we only do this for
|
|
# emulators.
|
|
self.marionette.execute_script("""
|
|
Components.utils.import("resource://gre/modules/Services.jsm");
|
|
Services.io.manageOfflineStatus = false;
|
|
Services.io.offline = false;
|
|
""")
|
|
|
|
self.marionette.execute_script("""
|
|
let SECURITY_PREF = "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer";
|
|
Services.prefs.setBoolPref(SECURITY_PREF, true);
|
|
|
|
if (!testUtils.hasOwnProperty("specialPowersObserver")) {
|
|
let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
|
|
.getService(Components.interfaces.mozIJSSubScriptLoader);
|
|
loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js",
|
|
testUtils);
|
|
testUtils.specialPowersObserver = new testUtils.SpecialPowersObserver();
|
|
testUtils.specialPowersObserver.init();
|
|
testUtils.specialPowersObserver._loadFrameScript();
|
|
}
|
|
""")
|
|
|
|
if options.chrome:
|
|
self.app_ctx.dm.removeDir(self.remote_chrome_test_dir)
|
|
self.app_ctx.dm.mkDir(self.remote_chrome_test_dir)
|
|
local = super(B2GMochitest, self).getChromeTestDir(options)
|
|
local = os.path.join(local, "chrome")
|
|
remote = self.remote_chrome_test_dir
|
|
self.log.info(
|
|
"pushing %s to %s on device..." %
|
|
(local, remote))
|
|
self.app_ctx.dm.pushDir(local, remote)
|
|
|
|
if os.path.isfile(self.test_script):
|
|
with open(self.test_script, 'r') as script:
|
|
self.marionette.execute_script(
|
|
script.read(),
|
|
script_args=self.test_script_args)
|
|
else:
|
|
self.marionette.execute_script(
|
|
self.test_script,
|
|
script_args=self.test_script_args)
|
|
status = self.runner.wait()
|
|
|
|
if status is None:
|
|
# the runner has timed out
|
|
status = 124
|
|
|
|
local_leak_file = tempfile.NamedTemporaryFile()
|
|
self.app_ctx.dm.getFile(
|
|
self.leak_report_file,
|
|
local_leak_file.name)
|
|
self.app_ctx.dm.removeFile(self.leak_report_file)
|
|
|
|
mozleak.process_leak_log(
|
|
local_leak_file.name,
|
|
leak_thresholds=options.leakThresholds,
|
|
ignore_missing_leaks=options.ignoreMissingLeaks,
|
|
log=self.log,
|
|
)
|
|
except KeyboardInterrupt:
|
|
self.log.info("runtests.py | Received keyboard interrupt.\n")
|
|
status = -1
|
|
except:
|
|
traceback.print_exc()
|
|
self.log.error(
|
|
"Automation Error: Received unexpected exception while running application\n")
|
|
if hasattr(self, 'runner'):
|
|
self.runner.check_for_crashes()
|
|
status = 1
|
|
|
|
self.stopServers()
|
|
|
|
self.log.info("runtestsb2g.py | Running tests: end.")
|
|
|
|
if manifest is not None:
|
|
self.cleanup(manifest, options)
|
|
return status
|
|
|
|
def getGMPPluginPath(self, options):
|
|
if options.gmp_path:
|
|
return options.gmp_path
|
|
return '/system/b2g/gmp-clearkey/0.1'
|
|
|
|
def getChromeTestDir(self, options):
|
|
# The chrome test directory returned here is the remote location
|
|
# of chrome test files. A reference to this directory is requested
|
|
# when building the profile locally, before self.app_ctx is defined.
|
|
# To get around this, return a dummy directory until self.app_ctx
|
|
# is defined; the correct directory will be returned later, over-
|
|
# writing the dummy.
|
|
if hasattr(self, 'app_ctx'):
|
|
self.remote_chrome_test_dir = posixpath.join(
|
|
self.app_ctx.remote_test_root,
|
|
'chrome')
|
|
return self.remote_chrome_test_dir
|
|
return 'dummy-chrome-test-dir'
|
|
|
|
|
|
class B2GDeviceMochitest(B2GMochitest, Mochitest):
|
|
remote_log = None
|
|
|
|
def __init__(
|
|
self,
|
|
marionette_args,
|
|
logger_options,
|
|
profile_data_dir,
|
|
local_binary_dir,
|
|
remote_test_root=None,
|
|
remote_log_file=None):
|
|
B2GMochitest.__init__(
|
|
self,
|
|
marionette_args,
|
|
logger_options,
|
|
out_of_process=True,
|
|
profile_data_dir=profile_data_dir)
|
|
self.local_log = None
|
|
self.local_binary_dir = local_binary_dir
|
|
|
|
def cleanup(self, manifest, options):
|
|
if self.local_log:
|
|
self.app_ctx.dm.getFile(self.remote_log, self.local_log)
|
|
self.app_ctx.dm.removeFile(self.remote_log)
|
|
|
|
if options.pidFile != "":
|
|
try:
|
|
os.remove(options.pidFile)
|
|
os.remove(options.pidFile + ".xpcshell.pid")
|
|
except:
|
|
print "Warning: cleaning up pidfile '%s' was unsuccessful from the test harness" % options.pidFile
|
|
|
|
# stop and clean up the runner
|
|
if getattr(self, 'runner', False):
|
|
if self.local_log:
|
|
self.app_ctx.dm.getFile(self.remote_log, self.local_log)
|
|
self.app_ctx.dm.removeFile(self.remote_log)
|
|
|
|
self.runner.cleanup()
|
|
self.runner = None
|
|
|
|
def startServers(self, options, debuggerInfo):
|
|
""" Create the servers on the host and start them up """
|
|
savedXre = options.xrePath
|
|
savedUtility = options.utilityPath
|
|
savedProfie = options.profilePath
|
|
options.xrePath = self.local_binary_dir
|
|
options.utilityPath = self.local_binary_dir
|
|
options.profilePath = tempfile.mkdtemp()
|
|
|
|
MochitestUtilsMixin.startServers(self, options, debuggerInfo)
|
|
|
|
options.xrePath = savedXre
|
|
options.utilityPath = savedUtility
|
|
options.profilePath = savedProfie
|
|
|
|
def buildURLOptions(self, options, env):
|
|
self.local_log = options.logFile
|
|
options.logFile = self.remote_log
|
|
options.profilePath = self.profile.profile
|
|
super(B2GDeviceMochitest, self).buildURLOptions(options, env)
|
|
|
|
self.setup_common_options(options)
|
|
|
|
options.profilePath = self.app_ctx.remote_profile
|
|
options.logFile = self.local_log
|
|
|
|
|
|
class B2GDesktopMochitest(B2GMochitest, Mochitest):
|
|
|
|
def __init__(self, marionette_args, logger_options, profile_data_dir):
|
|
B2GMochitest.__init__(
|
|
self,
|
|
marionette_args,
|
|
logger_options,
|
|
out_of_process=False,
|
|
profile_data_dir=profile_data_dir)
|
|
Mochitest.__init__(self, logger_options)
|
|
self.certdbNew = True
|
|
|
|
def runMarionetteScript(self, marionette, test_script, test_script_args):
|
|
assert(marionette.wait_for_port())
|
|
marionette.start_session()
|
|
marionette.set_context(marionette.CONTEXT_CHROME)
|
|
|
|
if os.path.isfile(test_script):
|
|
f = open(test_script, 'r')
|
|
test_script = f.read()
|
|
f.close()
|
|
self.marionette.execute_script(test_script,
|
|
script_args=test_script_args)
|
|
|
|
def startTests(self):
|
|
# This is run in a separate thread because otherwise, the app's
|
|
# stdout buffer gets filled (which gets drained only after this
|
|
# function returns, by waitForFinish), which causes the app to hang.
|
|
self.marionette = Marionette(**self.marionette_args)
|
|
thread = threading.Thread(target=self.runMarionetteScript,
|
|
args=(self.marionette,
|
|
self.test_script,
|
|
self.test_script_args))
|
|
thread.start()
|
|
|
|
def buildURLOptions(self, options, env):
|
|
super(B2GDesktopMochitest, self).buildURLOptions(options, env)
|
|
|
|
self.setup_common_options(options)
|
|
|
|
# Copy the extensions to the B2G bundles dir.
|
|
extensionDir = os.path.join(
|
|
options.profilePath,
|
|
'extensions',
|
|
'staged')
|
|
bundlesDir = os.path.join(os.path.dirname(options.app),
|
|
'distribution', 'bundles')
|
|
|
|
for filename in os.listdir(extensionDir):
|
|
shutil.rmtree(os.path.join(bundlesDir, filename), True)
|
|
shutil.copytree(os.path.join(extensionDir, filename),
|
|
os.path.join(bundlesDir, filename))
|
|
|
|
def buildProfile(self, options):
|
|
return self.build_profile(options)
|
|
|
|
|
|
def run_remote_mochitests(options):
|
|
# create our Marionette instance
|
|
marionette_args = {
|
|
'adb_path': options.adbPath,
|
|
'emulator': options.emulator,
|
|
'no_window': options.noWindow,
|
|
'logdir': options.logdir,
|
|
'busybox': options.busybox,
|
|
'symbols_path': options.symbolsPath,
|
|
'sdcard': options.sdcard,
|
|
'homedir': options.b2gPath,
|
|
}
|
|
if options.marionette:
|
|
host, port = options.marionette.split(':')
|
|
marionette_args['host'] = host
|
|
marionette_args['port'] = int(port)
|
|
|
|
if (options is None):
|
|
print "ERROR: Invalid options specified, use --help for a list of valid options"
|
|
sys.exit(1)
|
|
|
|
mochitest = B2GDeviceMochitest(
|
|
marionette_args,
|
|
options,
|
|
options.profile_data_dir,
|
|
options.xrePath,
|
|
remote_log_file=options.remoteLogFile)
|
|
|
|
if (options is None):
|
|
sys.exit(1)
|
|
|
|
retVal = 1
|
|
try:
|
|
mochitest.cleanup(None, options)
|
|
retVal = mochitest.run_tests(options)
|
|
except:
|
|
print "Automation Error: Exception caught while running tests"
|
|
traceback.print_exc()
|
|
mochitest.stopServers()
|
|
try:
|
|
mochitest.cleanup(None, options)
|
|
except:
|
|
pass
|
|
retVal = 1
|
|
|
|
mochitest.message_logger.finish()
|
|
|
|
return retVal
|
|
|
|
|
|
def run_desktop_mochitests(options):
|
|
# create our Marionette instance
|
|
marionette_args = {}
|
|
if options.marionette:
|
|
host, port = options.marionette.split(':')
|
|
marionette_args['host'] = host
|
|
marionette_args['port'] = int(port)
|
|
|
|
# add a -bin suffix if b2g-bin exists, but just b2g was specified
|
|
if options.app[-4:] != '-bin':
|
|
if os.path.isfile("%s-bin" % options.app):
|
|
options.app = "%s-bin" % options.app
|
|
|
|
mochitest = B2GDesktopMochitest(
|
|
marionette_args,
|
|
options,
|
|
options.profile_data_dir)
|
|
if options is None:
|
|
sys.exit(1)
|
|
|
|
if options.desktop and not options.profile:
|
|
raise Exception("must specify --profile when specifying --desktop")
|
|
|
|
options.browserArgs += ['-marionette']
|
|
|
|
retVal = mochitest.runTests(options, onLaunch=mochitest.startTests)
|
|
mochitest.message_logger.finish()
|
|
|
|
return retVal
|
|
|
|
|
|
def main():
|
|
parser = MochitestArgumentParser(app='b2g')
|
|
options = parser.parse_args()
|
|
|
|
if options.desktop:
|
|
return run_desktop_mochitests(options)
|
|
else:
|
|
return run_remote_mochitests(options)
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|