mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-18 15:55:36 +00:00
Bug 1618913- Preload Firefox instances in wpt harness, r=karlcow
The wpt harness spends a lot of time — about 20% of the total runtime on Linux64 — between tests. The majority of this is shutting down and restarting the Firefox instance under test. To reduce this overhead we add the option to preload a Firefox instance which can be immediately used when a new session is required. This makes the slow process of creating a profile and starting the browser asynchronous. Testing shows that this cuts the between-test time to between a half and a third of the previous value. The cost of this change is that we are adding background load at the time tests are running, which could influence the results and add randomness. However that seems unlikely unless we are right on the edge of the system performance. Differential Revision: https://phabricator.services.mozilla.com/D64954 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
745bf4d0a7
commit
ff743862ad
@ -172,10 +172,6 @@ class Browser(object):
|
||||
"""Boolean indicating whether the browser process is still running"""
|
||||
pass
|
||||
|
||||
def setup_ssl(self, hosts):
|
||||
"""Return a certificate to use for tests requiring ssl that will be trusted by the browser"""
|
||||
raise NotImplementedError("ssl testing not supported")
|
||||
|
||||
def cleanup(self):
|
||||
"""Browser-specific cleanup that is run after the testrun is finished"""
|
||||
pass
|
||||
|
@ -4,6 +4,7 @@ import platform
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import mozinfo
|
||||
import mozleak
|
||||
@ -96,7 +97,8 @@ def browser_kwargs(test_type, run_info_data, config, **kwargs):
|
||||
"chaos_mode_flags": kwargs["chaos_mode_flags"],
|
||||
"config": config,
|
||||
"browser_channel": kwargs["browser_channel"],
|
||||
"headless": kwargs["headless"]}
|
||||
"headless": kwargs["headless"],
|
||||
"preload_browser": kwargs["preload_browser"]}
|
||||
|
||||
|
||||
def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
|
||||
@ -202,79 +204,60 @@ def update_properties():
|
||||
{"os": ["version"], "processor": ["bits"]})
|
||||
|
||||
|
||||
class FirefoxBrowser(Browser):
|
||||
init_timeout = 70
|
||||
shutdown_timeout = 70
|
||||
class FirefoxInstanceManager(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
def __init__(self, logger, binary, prefs_root, test_type, extra_prefs=None, debug_info=None,
|
||||
symbols_path=None, stackwalk_binary=None, certutil_binary=None,
|
||||
ca_certificate_path=None, e10s=False, enable_webrender=False, stackfix_dir=None,
|
||||
binary_args=None, timeout_multiplier=None, leak_check=False, asan=False,
|
||||
stylo_threads=1, chaos_mode_flags=None, config=None, browser_channel="nightly", headless=None, **kwargs):
|
||||
Browser.__init__(self, logger)
|
||||
def __init__(self, logger, binary, binary_args, profile_creator, debug_info,
|
||||
chaos_mode_flags, headless, enable_webrender, stylo_threads,
|
||||
leak_check, stackfix_dir, symbols_path, asan):
|
||||
"""Object that manages starting and stopping instances of Firefox."""
|
||||
self.logger = logger
|
||||
self.binary = binary
|
||||
self.prefs_root = prefs_root
|
||||
self.test_type = test_type
|
||||
self.extra_prefs = extra_prefs
|
||||
self.marionette_port = None
|
||||
self.runner = None
|
||||
self.debug_info = debug_info
|
||||
self.profile = None
|
||||
self.symbols_path = symbols_path
|
||||
self.stackwalk_binary = stackwalk_binary
|
||||
self.ca_certificate_path = ca_certificate_path
|
||||
self.certutil_binary = certutil_binary
|
||||
self.e10s = e10s
|
||||
self.enable_webrender = enable_webrender
|
||||
self.binary_args = binary_args
|
||||
self.config = config
|
||||
if stackfix_dir:
|
||||
self.stack_fixer = get_stack_fixer_function(stackfix_dir,
|
||||
self.symbols_path)
|
||||
else:
|
||||
self.stack_fixer = None
|
||||
|
||||
if timeout_multiplier:
|
||||
self.init_timeout = self.init_timeout * timeout_multiplier
|
||||
|
||||
self.asan = asan
|
||||
self.lsan_allowed = None
|
||||
self.lsan_max_stack_depth = None
|
||||
self.mozleak_allowed = None
|
||||
self.mozleak_thresholds = None
|
||||
self.leak_check = leak_check
|
||||
self.leak_report_file = None
|
||||
self.lsan_handler = None
|
||||
self.stylo_threads = stylo_threads
|
||||
self.base_profile = profile_creator.create()
|
||||
self.debug_info = debug_info
|
||||
self.chaos_mode_flags = chaos_mode_flags
|
||||
self.browser_channel = browser_channel
|
||||
self.headless = headless
|
||||
self.enable_webrender = enable_webrender
|
||||
self.stylo_threads = stylo_threads
|
||||
self.leak_check = leak_check
|
||||
self.stackfix_dir = stackfix_dir
|
||||
self.symbols_path = symbols_path
|
||||
self.asan = asan
|
||||
|
||||
def settings(self, test):
|
||||
return {"check_leaks": self.leak_check and not test.leaks,
|
||||
"lsan_allowed": test.lsan_allowed,
|
||||
"lsan_max_stack_depth": test.lsan_max_stack_depth,
|
||||
"mozleak_allowed": self.leak_check and test.mozleak_allowed,
|
||||
"mozleak_thresholds": self.leak_check and test.mozleak_threshold}
|
||||
self.previous = None
|
||||
self.current = None
|
||||
|
||||
def start(self, group_metadata=None, **kwargs):
|
||||
if group_metadata is None:
|
||||
group_metadata = {}
|
||||
@abstractmethod
|
||||
def teardown(self, force=False):
|
||||
pass
|
||||
|
||||
self.group_metadata = group_metadata
|
||||
self.lsan_allowed = kwargs.get("lsan_allowed")
|
||||
self.lsan_max_stack_depth = kwargs.get("lsan_max_stack_depth")
|
||||
self.mozleak_allowed = kwargs.get("mozleak_allowed")
|
||||
self.mozleak_thresholds = kwargs.get("mozleak_thresholds")
|
||||
@abstractmethod
|
||||
def get(self):
|
||||
"""Get a BrowserInstance for a running Firefox.
|
||||
|
||||
if self.marionette_port is None:
|
||||
self.marionette_port = get_free_port()
|
||||
This can only be called once per instance, and between calls stop_current()
|
||||
must be called."""
|
||||
pass
|
||||
|
||||
if self.asan:
|
||||
self.lsan_handler = mozleak.LSANLeaks(self.logger,
|
||||
scope=group_metadata.get("scope", "/"),
|
||||
allowed=self.lsan_allowed,
|
||||
maxNumRecordedFrames=self.lsan_max_stack_depth)
|
||||
def stop_current(self, force=False):
|
||||
"""Shutdown the current instance of Firefox.
|
||||
|
||||
The BrowserInstance remains available through self.previous, since some
|
||||
operations happen after shutdown."""
|
||||
if not self.current:
|
||||
return
|
||||
|
||||
self.current.stop(force)
|
||||
self.previous = self.current
|
||||
self.current = None
|
||||
|
||||
def start(self):
|
||||
"""Start an instance of Firefox, returning a BrowserInstance handle"""
|
||||
profile = self.base_profile.clone(self.base_profile.profile)
|
||||
|
||||
marionette_port = get_free_port()
|
||||
profile.set_preferences({"marionette.port": marionette_port})
|
||||
|
||||
env = test_environment(xrePath=os.path.dirname(self.binary),
|
||||
debugger=self.debug_info is not None,
|
||||
@ -291,40 +274,6 @@ class FirefoxBrowser(Browser):
|
||||
else:
|
||||
env["MOZ_WEBRENDER"] = "0"
|
||||
|
||||
preferences = self.load_prefs()
|
||||
|
||||
self.profile = FirefoxProfile(preferences=preferences)
|
||||
self.profile.set_preferences({
|
||||
"marionette.port": self.marionette_port,
|
||||
"network.dns.localDomains": ",".join(self.config.domains_set),
|
||||
"dom.file.createInChild": True,
|
||||
# TODO: Remove preferences once Firefox 64 is stable (Bug 905404)
|
||||
"network.proxy.type": 0,
|
||||
"places.history.enabled": False,
|
||||
"network.preload": True,
|
||||
})
|
||||
if self.e10s:
|
||||
self.profile.set_preferences({"browser.tabs.remote.autostart": True})
|
||||
|
||||
if self.test_type == "reftest":
|
||||
self.profile.set_preferences({"layout.interruptible-reflow.enabled": False})
|
||||
|
||||
if self.leak_check:
|
||||
self.leak_report_file = os.path.join(self.profile.profile, "runtests_leaks_%s.log" % os.getpid())
|
||||
if os.path.exists(self.leak_report_file):
|
||||
os.remove(self.leak_report_file)
|
||||
env["XPCOM_MEM_BLOAT_LOG"] = self.leak_report_file
|
||||
else:
|
||||
self.leak_report_file = None
|
||||
|
||||
# Bug 1262954: winxp + e10s, disable hwaccel
|
||||
if (self.e10s and platform.system() in ("Windows", "Microsoft") and
|
||||
'5.1' in platform.version()):
|
||||
self.profile.set_preferences({"layers.acceleration.disabled": True})
|
||||
|
||||
if self.ca_certificate_path is not None:
|
||||
self.setup_ssl()
|
||||
|
||||
args = self.binary_args[:] if self.binary_args else []
|
||||
args += [cmd_arg("marionette"), "about:blank"]
|
||||
|
||||
@ -332,19 +281,253 @@ class FirefoxBrowser(Browser):
|
||||
args,
|
||||
self.debug_info)
|
||||
|
||||
self.runner = FirefoxRunner(profile=self.profile,
|
||||
binary=cmd[0],
|
||||
cmdargs=cmd[1:],
|
||||
env=cast_env(env),
|
||||
process_class=ProcessHandler,
|
||||
process_args={"processOutputLine": [self.on_output]})
|
||||
if self.leak_check:
|
||||
leak_report_file = os.path.join(profile.profile, "runtests_leaks_%s.log" % os.getpid())
|
||||
if os.path.exists(leak_report_file):
|
||||
os.remove(leak_report_file)
|
||||
env["XPCOM_MEM_BLOAT_LOG"] = leak_report_file
|
||||
else:
|
||||
leak_report_file = None
|
||||
|
||||
output_handler = OutputHandler(self.logger, self.stackfix_dir, self.symbols_path, self.asan)
|
||||
runner = FirefoxRunner(profile=profile,
|
||||
binary=cmd[0],
|
||||
cmdargs=cmd[1:],
|
||||
env=cast_env(env),
|
||||
process_class=ProcessHandler,
|
||||
process_args={"processOutputLine": [output_handler]})
|
||||
instance = BrowserInstance(self.logger, runner, marionette_port,
|
||||
output_handler, leak_report_file)
|
||||
|
||||
self.logger.debug("Starting Firefox")
|
||||
|
||||
self.runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive)
|
||||
runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive)
|
||||
self.logger.debug("Firefox Started")
|
||||
|
||||
def load_prefs(self):
|
||||
return instance
|
||||
|
||||
|
||||
class SingleInstanceManager(FirefoxInstanceManager):
|
||||
"""FirefoxInstanceManager that manages a single Firefox instance"""
|
||||
def get(self):
|
||||
assert not self.current, ("Tried to call get() on InstanceManager that has "
|
||||
"an existing instance")
|
||||
if self.previous:
|
||||
self.previous.cleanup()
|
||||
self.previous = None
|
||||
self.current = self.start()
|
||||
return self.current
|
||||
|
||||
def teardown(self, force=False):
|
||||
for instance, skip_marionette in [self.previous, self.current]:
|
||||
if instance:
|
||||
instance.stop(force)
|
||||
instance.cleanup()
|
||||
|
||||
|
||||
class PreloadInstanceManager(FirefoxInstanceManager):
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""FirefoxInstanceManager that keeps once Firefox instance preloaded
|
||||
to allow rapid resumption after an instance shuts down."""
|
||||
super(PreloadInstanceManager, self).__init__(*args, **kwargs)
|
||||
self.pending = None
|
||||
|
||||
def get(self):
|
||||
assert not self.current, ("Tried to call get() on InstanceManager that has "
|
||||
"an existing instance")
|
||||
if self.previous:
|
||||
self.previous.cleanup()
|
||||
self.previous = None
|
||||
if not self.pending:
|
||||
self.pending = self.start()
|
||||
self.current = self.pending
|
||||
self.pending = self.start()
|
||||
return self.current
|
||||
|
||||
def teardown(self, force=False):
|
||||
for instance, skip_marionette in [(self.previous, False), (self.current, False), (self.pending, True)]:
|
||||
if instance:
|
||||
instance.stop(force, skip_marionette=skip_marionette)
|
||||
instance.cleanup()
|
||||
|
||||
class BrowserInstance(object):
|
||||
shutdown_timeout = 70
|
||||
|
||||
def __init__(self, logger, runner, marionette_port, output_handler, leak_report_file):
|
||||
"""Handle to a running Firefox instance"""
|
||||
self.logger = logger
|
||||
self.runner = runner
|
||||
self.marionette_port = marionette_port
|
||||
self.output_handler = output_handler
|
||||
self.leak_report_file = leak_report_file
|
||||
|
||||
def stop(self, force=False, skip_marionette=False):
|
||||
"""Stop Firefox"""
|
||||
if self.runner is not None and self.runner.is_running():
|
||||
self.logger.debug("Stopping Firefox %s" % self.pid())
|
||||
shutdown_methods = [(True, lambda: self.runner.wait(self.shutdown_timeout)),
|
||||
(False, lambda: self.runner.stop(signal.SIGTERM)),
|
||||
(False, lambda: self.runner.stop(signal.SIGKILL))]
|
||||
if skip_marionette:
|
||||
shutdown_methods = shutdown_methods[1:]
|
||||
try:
|
||||
# For Firefox we assume that stopping the runner prompts the
|
||||
# browser to shut down. This allows the leak log to be written
|
||||
for clean, stop_f in shutdown_methods:
|
||||
if not force or not clean:
|
||||
retcode = stop_f()
|
||||
if retcode is not None:
|
||||
self.logger.info("Browser exited with return code %s" % retcode)
|
||||
break
|
||||
except OSError:
|
||||
# This can happen on Windows if the process is already dead
|
||||
pass
|
||||
if not skip_marionette:
|
||||
self.output_handler.after_stop()
|
||||
|
||||
def pid(self):
|
||||
if self.runner.process_handler is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
return self.runner.process_handler.pid
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def is_alive(self):
|
||||
if self.runner:
|
||||
return self.runner.is_running()
|
||||
return False
|
||||
|
||||
def cleanup(self):
|
||||
# mozprofile handles deleting the profile when the refcount reaches 0
|
||||
self.runner = None
|
||||
|
||||
|
||||
class OutputHandler(object):
|
||||
def __init__(self, logger, stackfix_dir, symbols_path, asan):
|
||||
"""Filter for handling Firefox process output.
|
||||
|
||||
This receives Firefox process output in the __call__ function, does
|
||||
any additional processing that's required, and decides whether to log
|
||||
the output. Because the Firefox process can be started before we know
|
||||
which filters are going to be required, we buffer all output until
|
||||
setup() is called. This is responsible for doing the final configuration
|
||||
of the output handlers.
|
||||
"""
|
||||
|
||||
self.logger = logger
|
||||
# These are filled in after setup() is called
|
||||
self.instance = None
|
||||
|
||||
self.symbols_path = symbols_path
|
||||
if stackfix_dir:
|
||||
self.stack_fixer = get_stack_fixer_function(stackfix_dir,
|
||||
self.symbols_path)
|
||||
else:
|
||||
self.stack_fixer = None
|
||||
self.asan = asan
|
||||
|
||||
self.lsan_handler = None
|
||||
self.mozleak_allowed = None
|
||||
self.mozleak_thresholds = None
|
||||
self.group_metadata = {}
|
||||
|
||||
self.line_buffer = []
|
||||
self.setup_ran = False
|
||||
|
||||
def setup(self, instance=None, group_metadata=None, lsan_allowed=None,
|
||||
lsan_max_stack_depth=None, mozleak_allowed=None,
|
||||
mozleak_thresholds=None, **kwargs):
|
||||
"""Configure the output handler"""
|
||||
self.instance = instance
|
||||
|
||||
if group_metadata is None:
|
||||
group_metadata = {}
|
||||
self.group_metadata = group_metadata
|
||||
|
||||
self.mozleak_allowed = mozleak_allowed
|
||||
self.mozleak_thresholds = mozleak_thresholds
|
||||
|
||||
if self.asan:
|
||||
self.lsan_handler = mozleak.LSANLeaks(self.logger,
|
||||
scope=group_metadata.get("scope", "/"),
|
||||
allowed=lsan_allowed,
|
||||
maxNumRecordedFrames=lsan_max_stack_depth)
|
||||
else:
|
||||
self.lsan_handler = None
|
||||
|
||||
self.setup_ran = True
|
||||
|
||||
for line in self.line_buffer:
|
||||
self.__call__(line)
|
||||
self.line_buffer = []
|
||||
|
||||
def after_stop(self):
|
||||
self.logger.info("PROCESS LEAKS %s" % self.instance.leak_report_file)
|
||||
if self.lsan_handler:
|
||||
self.lsan_handler.process()
|
||||
if self.instance.leak_report_file is not None:
|
||||
# We have to ignore missing leaks in the tab because it can happen that the
|
||||
# content process crashed and in that case we don't want the test to fail.
|
||||
# Ideally we would record which content process crashed and just skip those.
|
||||
mozleak.process_leak_log(
|
||||
self.instance.leak_report_file,
|
||||
leak_thresholds=self.mozleak_thresholds,
|
||||
ignore_missing_leaks=["tab", "gmplugin"],
|
||||
log=self.logger,
|
||||
stack_fixer=self.stack_fixer,
|
||||
scope=self.group_metadata.get("scope"),
|
||||
allowed=self.mozleak_allowed)
|
||||
|
||||
def __call__(self, line):
|
||||
"""Write a line of output from the firefox process to the log"""
|
||||
if "GLib-GObject-CRITICAL" in line:
|
||||
return
|
||||
if line:
|
||||
if not self.setup_ran:
|
||||
self.line_buffer.append(line)
|
||||
return
|
||||
data = line.decode("utf8", "replace")
|
||||
if self.stack_fixer:
|
||||
data = self.stack_fixer(data)
|
||||
if self.lsan_handler:
|
||||
data = self.lsan_handler.log(data)
|
||||
if data is not None:
|
||||
self.logger.process_output(self.instance and
|
||||
self.instance.runner.process_handler and
|
||||
self.instance.runner.process_handler.pid,
|
||||
data,
|
||||
command=" ".join(self.instance.runner.command))
|
||||
|
||||
|
||||
class ProfileCreator(object):
|
||||
def __init__(self, logger, prefs_root, config, test_type, extra_prefs, e10s,
|
||||
browser_channel, binary, certutil_binary, ca_certificate_path):
|
||||
self.logger = logger
|
||||
self.prefs_root = prefs_root
|
||||
self.config = config
|
||||
self.test_type = test_type
|
||||
self.extra_prefs = extra_prefs
|
||||
self.e10s = e10s
|
||||
self.browser_channel = browser_channel
|
||||
self.ca_certificate_path = ca_certificate_path
|
||||
self.binary = binary
|
||||
self.certutil_binary = certutil_binary
|
||||
self.ca_certificate_path = ca_certificate_path
|
||||
|
||||
def create(self):
|
||||
"""Create a Firefox profile and return the mozprofile Profile object pointing at that
|
||||
profile"""
|
||||
preferences = self._load_prefs()
|
||||
|
||||
profile = FirefoxProfile(preferences=preferences)
|
||||
self._set_required_prefs(profile)
|
||||
if self.ca_certificate_path is not None:
|
||||
self._setup_ssl(profile)
|
||||
|
||||
return profile
|
||||
|
||||
def _load_prefs(self):
|
||||
prefs = Preferences()
|
||||
|
||||
pref_paths = []
|
||||
@ -378,94 +561,32 @@ class FirefoxBrowser(Browser):
|
||||
|
||||
return prefs()
|
||||
|
||||
def stop(self, force=False):
|
||||
if self.runner is not None and self.runner.is_running():
|
||||
try:
|
||||
# For Firefox we assume that stopping the runner prompts the
|
||||
# browser to shut down. This allows the leak log to be written
|
||||
for clean, stop_f in [(True, lambda: self.runner.wait(self.shutdown_timeout)),
|
||||
(False, lambda: self.runner.stop(signal.SIGTERM)),
|
||||
(False, lambda: self.runner.stop(signal.SIGKILL))]:
|
||||
if not force or not clean:
|
||||
retcode = stop_f()
|
||||
if retcode is not None:
|
||||
self.logger.info("Browser exited with return code %s" % retcode)
|
||||
break
|
||||
except OSError:
|
||||
# This can happen on Windows if the process is already dead
|
||||
pass
|
||||
self.process_leaks()
|
||||
self.logger.debug("stopped")
|
||||
def _set_required_prefs(self, profile):
|
||||
"""Set preferences required for wptrunner to function.
|
||||
|
||||
def process_leaks(self):
|
||||
self.logger.info("PROCESS LEAKS %s" % self.leak_report_file)
|
||||
if self.lsan_handler:
|
||||
self.lsan_handler.process()
|
||||
if self.leak_report_file is not None:
|
||||
# We have to ignore missing leaks in the tab because it can happen that the
|
||||
# content process crashed and in that case we don't want the test to fail.
|
||||
# Ideally we would record which content process crashed and just skip those.
|
||||
mozleak.process_leak_log(
|
||||
self.leak_report_file,
|
||||
leak_thresholds=self.mozleak_thresholds,
|
||||
ignore_missing_leaks=["tab", "gmplugin"],
|
||||
log=self.logger,
|
||||
stack_fixer=self.stack_fixer,
|
||||
scope=self.group_metadata.get("scope"),
|
||||
allowed=self.mozleak_allowed
|
||||
)
|
||||
Note that this doesn't set the marionette port, since we don't always
|
||||
know that at profile creation time. So the caller is responisble for
|
||||
setting that once it's available."""
|
||||
profile.set_preferences({
|
||||
"network.dns.localDomains": ",".join(self.config.domains_set),
|
||||
"dom.file.createInChild": True,
|
||||
# TODO: Remove preferences once Firefox 64 is stable (Bug 905404)
|
||||
"network.proxy.type": 0,
|
||||
"places.history.enabled": False,
|
||||
"network.preload": True,
|
||||
})
|
||||
if self.e10s:
|
||||
profile.set_preferences({"browser.tabs.remote.autostart": True})
|
||||
|
||||
def pid(self):
|
||||
if self.runner.process_handler is None:
|
||||
return None
|
||||
if self.test_type == "reftest":
|
||||
profile.set_preferences({"layout.interruptible-reflow.enabled": False})
|
||||
|
||||
try:
|
||||
return self.runner.process_handler.pid
|
||||
except AttributeError:
|
||||
return None
|
||||
# Bug 1262954: winxp + e10s, disable hwaccel
|
||||
if (self.e10s and platform.system() in ("Windows", "Microsoft") and
|
||||
"5.1" in platform.version()):
|
||||
self.profile.set_preferences({"layers.acceleration.disabled": True})
|
||||
|
||||
def on_output(self, line):
|
||||
"""Write a line of output from the firefox process to the log"""
|
||||
if "GLib-GObject-CRITICAL" in line:
|
||||
return
|
||||
if line:
|
||||
data = line.decode("utf8", "replace")
|
||||
if self.stack_fixer:
|
||||
data = self.stack_fixer(data)
|
||||
if self.lsan_handler:
|
||||
data = self.lsan_handler.log(data)
|
||||
if data is not None:
|
||||
self.logger.process_output(self.pid(),
|
||||
data,
|
||||
command=" ".join(self.runner.command))
|
||||
|
||||
def is_alive(self):
|
||||
if self.runner:
|
||||
return self.runner.is_running()
|
||||
return False
|
||||
|
||||
def cleanup(self, force=False):
|
||||
self.stop(force)
|
||||
|
||||
def executor_browser(self):
|
||||
assert self.marionette_port is not None
|
||||
return ExecutorBrowser, {"marionette_port": self.marionette_port}
|
||||
|
||||
def check_crash(self, process, test):
|
||||
dump_dir = os.path.join(self.profile.profile, "minidumps")
|
||||
|
||||
try:
|
||||
return bool(mozcrash.log_crashes(self.logger,
|
||||
dump_dir,
|
||||
symbols_path=self.symbols_path,
|
||||
stackwalk_binary=self.stackwalk_binary,
|
||||
process=process,
|
||||
test=test))
|
||||
except IOError:
|
||||
self.logger.warning("Looking for crash dump files failed")
|
||||
return False
|
||||
|
||||
def setup_ssl(self):
|
||||
def _setup_ssl(self, profile):
|
||||
"""Create a certificate database to use in the test profile. This is configured
|
||||
to trust the CA Certificate that has signed the web-platform.test server
|
||||
certificate."""
|
||||
@ -500,12 +621,12 @@ class FirefoxBrowser(Browser):
|
||||
stderr=subprocess.STDOUT),
|
||||
" ".join(cmd))
|
||||
|
||||
pw_path = os.path.join(self.profile.profile, ".crtdbpw")
|
||||
pw_path = os.path.join(profile.profile, ".crtdbpw")
|
||||
with open(pw_path, "w") as f:
|
||||
# Use empty password for certificate db
|
||||
f.write("\n")
|
||||
|
||||
cert_db_path = self.profile.profile
|
||||
cert_db_path = profile.profile
|
||||
|
||||
# Create a new certificate db
|
||||
certutil("-N", "-d", cert_db_path, "-f", pw_path)
|
||||
@ -516,3 +637,104 @@ class FirefoxBrowser(Browser):
|
||||
|
||||
# List all certs in the database
|
||||
certutil("-L", "-d", cert_db_path)
|
||||
|
||||
|
||||
|
||||
class FirefoxBrowser(Browser):
|
||||
init_timeout = 70
|
||||
|
||||
def __init__(self, logger, binary, prefs_root, test_type, extra_prefs=None, debug_info=None,
|
||||
symbols_path=None, stackwalk_binary=None, certutil_binary=None,
|
||||
ca_certificate_path=None, e10s=False, enable_webrender=False, stackfix_dir=None,
|
||||
binary_args=None, timeout_multiplier=None, leak_check=False, asan=False,
|
||||
stylo_threads=1, chaos_mode_flags=None, config=None, browser_channel="nightly",
|
||||
headless=None, preload_browser=False, **kwargs):
|
||||
Browser.__init__(self, logger)
|
||||
|
||||
self.logger = logger
|
||||
|
||||
if timeout_multiplier:
|
||||
self.init_timeout = self.init_timeout * timeout_multiplier
|
||||
|
||||
self.instance = None
|
||||
|
||||
self.stackfix_dir = stackfix_dir
|
||||
self.symbols_path = symbols_path
|
||||
self.stackwalk_binary = stackwalk_binary
|
||||
|
||||
self.asan = asan
|
||||
self.leak_check = leak_check
|
||||
|
||||
profile_creator = ProfileCreator(logger,
|
||||
prefs_root,
|
||||
config,
|
||||
test_type,
|
||||
extra_prefs,
|
||||
e10s,
|
||||
browser_channel,
|
||||
binary,
|
||||
certutil_binary,
|
||||
ca_certificate_path)
|
||||
|
||||
if preload_browser:
|
||||
instance_manager_cls = PreloadInstanceManager
|
||||
else:
|
||||
instance_manager_cls = SingleInstanceManager
|
||||
self.instance_manager = instance_manager_cls(logger,
|
||||
binary,
|
||||
binary_args,
|
||||
profile_creator,
|
||||
debug_info,
|
||||
chaos_mode_flags,
|
||||
headless,
|
||||
enable_webrender,
|
||||
stylo_threads,
|
||||
leak_check,
|
||||
stackfix_dir,
|
||||
symbols_path,
|
||||
asan)
|
||||
|
||||
|
||||
def settings(self, test):
|
||||
return {"check_leaks": self.leak_check and not test.leaks,
|
||||
"lsan_allowed": test.lsan_allowed,
|
||||
"lsan_max_stack_depth": test.lsan_max_stack_depth,
|
||||
"mozleak_allowed": self.leak_check and test.mozleak_allowed,
|
||||
"mozleak_thresholds": self.leak_check and test.mozleak_threshold}
|
||||
|
||||
def start(self, group_metadata=None, **kwargs):
|
||||
self.instance = self.instance_manager.get()
|
||||
self.instance.output_handler.setup(self.instance,
|
||||
group_metadata,
|
||||
**kwargs)
|
||||
|
||||
def stop(self, force=False):
|
||||
self.instance_manager.stop_current(force)
|
||||
self.logger.debug("stopped")
|
||||
|
||||
def pid(self):
|
||||
return self.instance.pid()
|
||||
|
||||
def is_alive(self):
|
||||
return self.instance and self.instance.is_alive()
|
||||
|
||||
def cleanup(self, force=False):
|
||||
self.instance_manager.teardown(force)
|
||||
|
||||
def executor_browser(self):
|
||||
assert self.instance is not None
|
||||
return ExecutorBrowser, {"marionette_port": self.instance.marionette_port}
|
||||
|
||||
def check_crash(self, process, test):
|
||||
dump_dir = os.path.join(self.instance.runner.profile.profile, "minidumps")
|
||||
|
||||
try:
|
||||
return bool(mozcrash.log_crashes(self.logger,
|
||||
dump_dir,
|
||||
symbols_path=self.symbols_path,
|
||||
stackwalk_binary=self.stackwalk_binary,
|
||||
process=process,
|
||||
test=test))
|
||||
except IOError:
|
||||
self.logger.warning("Looking for crash dump files failed")
|
||||
return False
|
||||
|
@ -1,7 +1,6 @@
|
||||
import os
|
||||
|
||||
import moznetwork
|
||||
from mozprofile import FirefoxProfile
|
||||
from mozrunner import FennecEmulatorRunner
|
||||
|
||||
from .base import (get_free_port,
|
||||
@ -11,11 +10,13 @@ from ..executors.executormarionette import (MarionetteTestharnessExecutor, # no
|
||||
MarionetteRefTestExecutor, # noqa: F401
|
||||
MarionetteCrashtestExecutor) # noqa: F401
|
||||
from ..process import cast_env
|
||||
from .base import (Browser,
|
||||
ExecutorBrowser)
|
||||
from .firefox import (get_timeout_multiplier, # noqa: F401
|
||||
run_info_extras as fx_run_info_extras,
|
||||
update_properties, # noqa: F401
|
||||
executor_kwargs, # noqa: F401
|
||||
FirefoxBrowser) # noqa: F401
|
||||
ProfileCreator as FirefoxProfileCreator)
|
||||
|
||||
|
||||
__wptrunner__ = {"product": "firefox_android",
|
||||
@ -84,40 +85,15 @@ def env_options():
|
||||
"supports_debugger": True}
|
||||
|
||||
|
||||
class FirefoxAndroidBrowser(FirefoxBrowser):
|
||||
init_timeout = 300
|
||||
shutdown_timeout = 60
|
||||
class ProfileCreator(FirefoxProfileCreator):
|
||||
def __init__(self, logger, prefs_root, config, test_type, extra_prefs, e10s,
|
||||
browser_channel, certutil_binary, ca_certificate_path):
|
||||
super(ProfileCreator, self).__init__(logger, prefs_root, config, test_type, extra_prefs,
|
||||
e10s, browser_channel, None, certutil_binary,
|
||||
ca_certificate_path)
|
||||
|
||||
def __init__(self, logger, prefs_root, test_type, package_name="org.mozilla.geckoview.test",
|
||||
device_serial="emulator-5444", **kwargs):
|
||||
FirefoxBrowser.__init__(self, logger, None, prefs_root, test_type, **kwargs)
|
||||
self.package_name = package_name
|
||||
self.device_serial = device_serial
|
||||
self.tests_root = kwargs["tests_root"]
|
||||
self.install_fonts = kwargs["install_fonts"]
|
||||
self.stackwalk_binary = kwargs["stackwalk_binary"]
|
||||
|
||||
def start(self, **kwargs):
|
||||
if self.marionette_port is None:
|
||||
self.marionette_port = get_free_port()
|
||||
|
||||
env = {}
|
||||
env["MOZ_CRASHREPORTER"] = "1"
|
||||
env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1"
|
||||
env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1"
|
||||
env["STYLO_THREADS"] = str(self.stylo_threads)
|
||||
if self.chaos_mode_flags is not None:
|
||||
env["MOZ_CHAOSMODE"] = str(self.chaos_mode_flags)
|
||||
if self.enable_webrender:
|
||||
env["MOZ_WEBRENDER"] = "1"
|
||||
else:
|
||||
env["MOZ_WEBRENDER"] = "0"
|
||||
|
||||
preferences = self.load_prefs()
|
||||
|
||||
self.profile = FirefoxProfile(preferences=preferences)
|
||||
self.profile.set_preferences({
|
||||
"marionette.port": self.marionette_port,
|
||||
def _set_required_prefs(self, profile):
|
||||
profile.set_preferences({
|
||||
"network.dns.localDomains": ",".join(self.config.domains_set),
|
||||
"dom.disable_open_during_load": False,
|
||||
"places.history.enabled": False,
|
||||
@ -126,11 +102,11 @@ class FirefoxAndroidBrowser(FirefoxBrowser):
|
||||
})
|
||||
|
||||
if self.e10s:
|
||||
self.profile.set_preferences({"browser.tabs.remote.autostart": True})
|
||||
profile.set_preferences({"browser.tabs.remote.autostart": True})
|
||||
|
||||
if self.test_type == "reftest":
|
||||
self.logger.info("Setting android reftest preferences")
|
||||
self.profile.set_preferences({
|
||||
profile.set_preferences({
|
||||
"browser.viewport.desktopWidth": 800,
|
||||
# Disable high DPI
|
||||
"layout.css.devPixelsPerPx": "1.0",
|
||||
@ -142,6 +118,72 @@ class FirefoxAndroidBrowser(FirefoxBrowser):
|
||||
"layout.testing.overlay-scrollbars.always-visible": True,
|
||||
})
|
||||
|
||||
|
||||
|
||||
class FirefoxAndroidBrowser(Browser):
|
||||
init_timeout = 300
|
||||
shutdown_timeout = 60
|
||||
|
||||
def __init__(self, logger, prefs_root, test_type, package_name="org.mozilla.geckoview.test",
|
||||
device_serial="emulator-5444", extra_prefs=None, debug_info=None,
|
||||
symbols_path=None, stackwalk_binary=None, certutil_binary=None,
|
||||
ca_certificate_path=None, e10s=False, enable_webrender=False, stackfix_dir=None,
|
||||
binary_args=None, timeout_multiplier=None, leak_check=False, asan=False,
|
||||
stylo_threads=1, chaos_mode_flags=None, config=None, browser_channel="nightly",
|
||||
install_fonts=False, tests_root=None, **kwargs):
|
||||
|
||||
super(FirefoxAndroidBrowser, self).__init__(logger)
|
||||
self.prefs_root = prefs_root
|
||||
self.test_type = test_type
|
||||
self.package_name = package_name
|
||||
self.device_serial = device_serial
|
||||
self.debug_info = debug_info
|
||||
self.symbols_path = symbols_path
|
||||
self.stackwalk_binary = stackwalk_binary
|
||||
self.certutil_binary = certutil_binary
|
||||
self.ca_certificate_path = ca_certificate_path
|
||||
self.e10s = e10s
|
||||
self.enable_webrender = enable_webrender
|
||||
self.stackfix_dir = stackfix_dir
|
||||
self.binary_args = binary_args
|
||||
self.timeout_multiplier = timeout_multiplier
|
||||
self.leak_check = leak_check
|
||||
self.asan = asan
|
||||
self.stylo_threads = stylo_threads
|
||||
self.chaos_mode_flags = chaos_mode_flags
|
||||
self.config = config
|
||||
self.browser_channel = browser_channel
|
||||
self.install_fonts = install_fonts
|
||||
self.tests_root = tests_root
|
||||
|
||||
self.profile_creator = ProfileCreator(logger,
|
||||
prefs_root,
|
||||
config,
|
||||
test_type,
|
||||
extra_prefs,
|
||||
e10s,
|
||||
browser_channel,
|
||||
certutil_binary,
|
||||
ca_certificate_path)
|
||||
|
||||
self.marionette_port = None
|
||||
self.profile = None
|
||||
self.runner = None
|
||||
|
||||
def settings(self, test):
|
||||
return {"check_leaks": self.leak_check and not test.leaks,
|
||||
"lsan_allowed": test.lsan_allowed,
|
||||
"lsan_max_stack_depth": test.lsan_max_stack_depth,
|
||||
"mozleak_allowed": self.leak_check and test.mozleak_allowed,
|
||||
"mozleak_thresholds": self.leak_check and test.mozleak_threshold}
|
||||
|
||||
def start(self, **kwargs):
|
||||
if self.marionette_port is None:
|
||||
self.marionette_port = get_free_port()
|
||||
|
||||
self.profile = self.profile_creator.create()
|
||||
self.profile.set_preferences({"marionette.port": self.marionette_port})
|
||||
|
||||
if self.install_fonts:
|
||||
self.logger.debug("Copying Ahem font to profile")
|
||||
font_dir = os.path.join(self.profile.profile, "fonts")
|
||||
@ -153,14 +195,23 @@ class FirefoxAndroidBrowser(FirefoxBrowser):
|
||||
|
||||
self.leak_report_file = None
|
||||
|
||||
if self.ca_certificate_path is not None:
|
||||
self.setup_ssl()
|
||||
|
||||
debug_args, cmd = browser_command(self.package_name,
|
||||
self.binary_args if self.binary_args else [] +
|
||||
[cmd_arg("marionette"), "about:blank"],
|
||||
self.debug_info)
|
||||
|
||||
env = {}
|
||||
env["MOZ_CRASHREPORTER"] = "1"
|
||||
env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1"
|
||||
env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1"
|
||||
env["STYLO_THREADS"] = str(self.stylo_threads)
|
||||
if self.chaos_mode_flags is not None:
|
||||
env["MOZ_CHAOSMODE"] = str(self.chaos_mode_flags)
|
||||
if self.enable_webrender:
|
||||
env["MOZ_WEBRENDER"] = "1"
|
||||
else:
|
||||
env["MOZ_WEBRENDER"] = "0"
|
||||
|
||||
self.runner = FennecEmulatorRunner(app=self.package_name,
|
||||
profile=self.profile,
|
||||
cmdargs=cmd[1:],
|
||||
@ -203,6 +254,26 @@ class FirefoxAndroidBrowser(FirefoxBrowser):
|
||||
self.runner.stop()
|
||||
self.logger.debug("stopped")
|
||||
|
||||
def pid(self):
|
||||
if self.runner.process_handler is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
return self.runner.process_handler.pid
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def is_alive(self):
|
||||
if self.runner:
|
||||
return self.runner.is_running()
|
||||
return False
|
||||
|
||||
def cleanup(self, force=False):
|
||||
self.stop(force)
|
||||
|
||||
def executor_browser(self):
|
||||
return ExecutorBrowser, {"marionette_port": self.marionette_port}
|
||||
|
||||
def check_crash(self, process, test):
|
||||
if not os.environ.get("MINIDUMP_STACKWALK", "") and self.stackwalk_binary:
|
||||
os.environ["MINIDUMP_STACKWALK"] = self.stackwalk_binary
|
||||
|
@ -263,6 +263,10 @@ scheme host and port.""")
|
||||
gecko_group = parser.add_argument_group("Gecko-specific")
|
||||
gecko_group.add_argument("--prefs-root", dest="prefs_root", action="store", type=abs_path,
|
||||
help="Path to the folder containing browser prefs")
|
||||
gecko_group.add_argument("--preload-browser", dest="preload_browser", action="store_true",
|
||||
default=None, help="Preload a gecko instance for faster restarts")
|
||||
gecko_group.add_argument("--no-preload-browser", dest="preload_browser", action="store_false",
|
||||
default=None, help="Don't preload a gecko instance for faster restarts")
|
||||
gecko_group.add_argument("--disable-e10s", dest="gecko_e10s", action="store_false", default=True,
|
||||
help="Run tests without electrolysis preferences")
|
||||
gecko_group.add_argument("--enable-webrender", dest="enable_webrender", action="store_true", default=None,
|
||||
@ -558,6 +562,10 @@ def check_args(kwargs):
|
||||
if kwargs["enable_webrender"] is None:
|
||||
kwargs["enable_webrender"] = False
|
||||
|
||||
if kwargs["preload_browser"] is None:
|
||||
# Default to preloading a gecko instance if we're only running a single process
|
||||
kwargs["preload_browser"] = kwargs["processes"] == 1
|
||||
|
||||
return kwargs
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user