| import os |
| import signal |
| import sys |
| import tempfile |
| import traceback |
| |
| import moznetwork |
| from mozprocess import ProcessHandler |
| from mozprofile import FirefoxProfile |
| from mozrunner import FennecEmulatorRunner |
| |
| from serve.serve import make_hosts_file |
| |
| from .base import (get_free_port, |
| cmd_arg, |
| browser_command) |
| from ..executors.executormarionette import (MarionetteTestharnessExecutor, # noqa: F401 |
| MarionetteRefTestExecutor) # noqa: F401 |
| from .firefox import (get_timeout_multiplier, update_properties, executor_kwargs, FirefoxBrowser) # noqa: F401 |
| |
| |
| __wptrunner__ = {"product": "fennec", |
| "check_args": "check_args", |
| "browser": "FennecBrowser", |
| "executor": {"testharness": "MarionetteTestharnessExecutor", |
| "reftest": "MarionetteRefTestExecutor"}, |
| "browser_kwargs": "browser_kwargs", |
| "executor_kwargs": "executor_kwargs", |
| "env_extras": "env_extras", |
| "env_options": "env_options", |
| "run_info_extras": "run_info_extras", |
| "update_properties": "update_properties"} |
| |
| class FennecProfile(FirefoxProfile): |
| # WPT-specific prefs are set in FennecBrowser.start() |
| FirefoxProfile.preferences.update({ |
| # Make sure Shield doesn't hit the network. |
| "app.normandy.api_url": "", |
| # Increase the APZ content response timeout in tests to 1 minute. |
| "apz.content_response_timeout": 60000, |
| # Enable output for dump() and chrome console API |
| "browser.dom.window.dump.enabled": True, |
| "devtools.console.stdout.chrome": True, |
| # Disable safebrowsing components |
| "browser.safebrowsing.blockedURIs.enabled": False, |
| "browser.safebrowsing.downloads.enabled": False, |
| "browser.safebrowsing.passwords.enabled": False, |
| "browser.safebrowsing.malware.enabled": False, |
| "browser.safebrowsing.phishing.enabled": False, |
| # Do not restore the last open set of tabs if the browser has crashed |
| "browser.sessionstore.resume_from_crash": False, |
| # Disable Android snippets |
| "browser.snippets.enabled": False, |
| "browser.snippets.syncPromo.enabled": False, |
| "browser.snippets.firstrunHomepage.enabled": False, |
| # Do not allow background tabs to be zombified, otherwise for tests that |
| # open additional tabs, the test harness tab itself might get unloaded |
| "browser.tabs.disableBackgroundZombification": True, |
| # Disable e10s by default |
| "browser.tabs.remote.autostart": False, |
| # Don't warn when exiting the browser |
| "browser.warnOnQuit": False, |
| # Don't send Firefox health reports to the production server |
| "datareporting.healthreport.about.reportUrl": "http://%(server)s/dummy/abouthealthreport/", |
| # Automatically unload beforeunload alerts |
| "dom.disable_beforeunload": True, |
| # Disable the ProcessHangMonitor |
| "dom.ipc.reportProcessHangs": False, |
| # No slow script dialogs |
| "dom.max_chrome_script_run_time": 0, |
| "dom.max_script_run_time": 0, |
| # Make sure opening about:addons won"t hit the network |
| "extensions.webservice.discoverURL": "http://%(server)s/dummy/discoveryURL", |
| # No hang monitor |
| "hangmonitor.timeout": 0, |
| |
| "javascript.options.showInConsole": True, |
| # Ensure blocklist updates don't hit the network |
| "services.settings.server": "http://%(server)s/dummy/blocklist/", |
| # Disable password capture, so that tests that include forms aren"t |
| # influenced by the presence of the persistent doorhanger notification |
| "signon.rememberSignons": False, |
| }) |
| |
| |
| def check_args(**kwargs): |
| pass |
| |
| def browser_kwargs(test_type, run_info_data, config, **kwargs): |
| return {"package_name": kwargs["package_name"], |
| "device_serial": kwargs["device_serial"], |
| "prefs_root": kwargs["prefs_root"], |
| "extra_prefs": kwargs["extra_prefs"], |
| "test_type": test_type, |
| "debug_info": kwargs["debug_info"], |
| "symbols_path": kwargs["symbols_path"], |
| "stackwalk_binary": kwargs["stackwalk_binary"], |
| "certutil_binary": kwargs["certutil_binary"], |
| "ca_certificate_path": config.ssl_config["ca_cert_path"], |
| "stackfix_dir": kwargs["stackfix_dir"], |
| "binary_args": kwargs["binary_args"], |
| "timeout_multiplier": get_timeout_multiplier(test_type, |
| run_info_data, |
| **kwargs), |
| "leak_check": kwargs["leak_check"], |
| "stylo_threads": kwargs["stylo_threads"], |
| "chaos_mode_flags": kwargs["chaos_mode_flags"], |
| "config": config, |
| "install_fonts": kwargs["install_fonts"], |
| "tests_root": config.doc_root} |
| |
| |
| def env_extras(**kwargs): |
| return [] |
| |
| |
| def run_info_extras(**kwargs): |
| return {"e10s": False, |
| "headless": False, |
| "sw-e10s": False} |
| |
| |
| def env_options(): |
| # The server host is set to public localhost IP so that resources can be accessed |
| # from Android emulator |
| return {"server_host": moznetwork.get_ip(), |
| "bind_address": False, |
| "supports_debugger": True} |
| |
| |
| def write_hosts_file(config, device): |
| new_hosts = make_hosts_file(config, moznetwork.get_ip()) |
| current_hosts = device.get_file("/etc/hosts") |
| if new_hosts == current_hosts: |
| return |
| hosts_fd, hosts_path = tempfile.mkstemp() |
| try: |
| with os.fdopen(hosts_fd, "w") as f: |
| f.write(new_hosts) |
| device.remount() |
| device.push(hosts_path, "/etc/hosts") |
| finally: |
| os.remove(hosts_path) |
| |
| |
| class FennecBrowser(FirefoxBrowser): |
| used_ports = set() |
| init_timeout = 300 |
| shutdown_timeout = 60 |
| |
| def __init__(self, logger, prefs_root, test_type, package_name=None, |
| 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"] |
| |
| @property |
| def package_name(self): |
| """ |
| Name of app to run on emulator. |
| """ |
| if self._package_name is None: |
| self._package_name = "org.mozilla.fennec" |
| user = os.getenv("USER") |
| if user: |
| self._package_name += "_" + user |
| return self._package_name |
| |
| def start(self, **kwargs): |
| if self.marionette_port is None: |
| self.marionette_port = get_free_port(2828, exclude=self.used_ports) |
| self.used_ports.add(self.marionette_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) |
| |
| preferences = self.load_prefs() |
| |
| self.profile = FennecProfile(preferences=preferences) |
| self.profile.set_preferences({"marionette.port": self.marionette_port, |
| "dom.disable_open_during_load": False, |
| "places.history.enabled": False, |
| "dom.send_after_paint_to_content": True, |
| "network.preload": True}) |
| |
| if self.install_fonts: |
| self.logger.debug("Copying Ahem font to profile") |
| font_dir = os.path.join(self.profile.profile, "fonts") |
| if not os.path.exists(font_dir): |
| os.makedirs(font_dir) |
| with open(os.path.join(self.tests_root, "fonts", "Ahem.ttf"), "rb") as src: |
| with open(os.path.join(font_dir, "Ahem.ttf"), "wb") as dest: |
| dest.write(src.read()) |
| |
| if self.leak_check and kwargs.get("check_leaks", True): |
| self.leak_report_file = os.path.join(self.profile.profile, "runtests_leaks.log") |
| 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 |
| |
| 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) |
| |
| self.runner = FennecEmulatorRunner(app=self.package_name, |
| profile=self.profile, |
| cmdargs=cmd[1:], |
| env=env, |
| symbols_path=self.symbols_path, |
| serial=self.device_serial, |
| # TODO - choose appropriate log dir |
| logdir=os.getcwd(), |
| process_class=ProcessHandler, |
| process_args={"processOutputLine": [self.on_output]}) |
| |
| self.logger.debug("Starting %s" % self.package_name) |
| # connect to a running emulator |
| self.runner.device.connect() |
| |
| write_hosts_file(self.config, self.runner.device.device) |
| |
| self.runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive) |
| |
| self.runner.device.device.forward( |
| local="tcp:{}".format(self.marionette_port), |
| remote="tcp:{}".format(self.marionette_port)) |
| |
| self.logger.debug("%s Started" % self.package_name) |
| |
| def stop(self, force=False): |
| if self.runner is not None: |
| try: |
| if self.runner.device.connected: |
| if len(self.runner.device.device.list_forwards()) > 0: |
| self.runner.device.device.remove_forwards( |
| "tcp:{}".format(self.marionette_port)) |
| except Exception: |
| traceback.print_exception(*sys.exc_info()) |
| # We assume that stopping the runner prompts the |
| # browser to shut down. This allows the leak log to be written |
| self.runner.stop() |
| 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 |
| self.logger.debug("stopped") |