gecko/testing/mochitest/runtestsb2g.py
Andrew Halberstadt 8cffe8fa6b Bug 1014760 - Move mozlog.structured to mozlog; Move mozlog to mozlog.unstructured, r=jgraham
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.
2015-07-16 10:38:40 -04:00

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())