1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this 3# file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5import os 6import platform 7import subprocess 8import sys 9 10import mozinfo 11from mozprocess import ProcessHandler 12from mozprofile import FirefoxProfile, Preferences 13from mozprofile.permissions import ServerLocations 14from mozrunner import FirefoxRunner 15from mozrunner.utils import get_stack_fixer_function 16from mozcrash import mozcrash 17 18from .base import (get_free_port, 19 Browser, 20 ExecutorBrowser, 21 require_arg, 22 cmd_arg, 23 browser_command) 24from ..executors import executor_kwargs as base_executor_kwargs 25from ..executors.executormarionette import (MarionetteTestharnessExecutor, 26 MarionetteRefTestExecutor, 27 MarionetteWdspecExecutor) 28from ..environment import hostnames 29 30 31here = os.path.join(os.path.split(__file__)[0]) 32 33__wptrunner__ = {"product": "firefox", 34 "check_args": "check_args", 35 "browser": "FirefoxBrowser", 36 "executor": {"testharness": "MarionetteTestharnessExecutor", 37 "reftest": "MarionetteRefTestExecutor", 38 "wdspec": "MarionetteWdspecExecutor"}, 39 "browser_kwargs": "browser_kwargs", 40 "executor_kwargs": "executor_kwargs", 41 "env_options": "env_options", 42 "run_info_extras": "run_info_extras", 43 "update_properties": "update_properties"} 44 45 46def check_args(**kwargs): 47 require_arg(kwargs, "binary") 48 if kwargs["ssl_type"] != "none": 49 require_arg(kwargs, "certutil_binary") 50 51 52def browser_kwargs(**kwargs): 53 return {"binary": kwargs["binary"], 54 "prefs_root": kwargs["prefs_root"], 55 "debug_info": kwargs["debug_info"], 56 "symbols_path": kwargs["symbols_path"], 57 "stackwalk_binary": kwargs["stackwalk_binary"], 58 "certutil_binary": kwargs["certutil_binary"], 59 "ca_certificate_path": kwargs["ssl_env"].ca_cert_path(), 60 "e10s": kwargs["gecko_e10s"], 61 "stackfix_dir": kwargs["stackfix_dir"]} 62 63 64def executor_kwargs(test_type, server_config, cache_manager, run_info_data, 65 **kwargs): 66 executor_kwargs = base_executor_kwargs(test_type, server_config, 67 cache_manager, **kwargs) 68 executor_kwargs["close_after_done"] = test_type != "reftest" 69 if kwargs["timeout_multiplier"] is None: 70 if test_type == "reftest": 71 if run_info_data["debug"] or run_info_data.get("asan"): 72 executor_kwargs["timeout_multiplier"] = 4 73 else: 74 executor_kwargs["timeout_multiplier"] = 2 75 elif run_info_data["debug"] or run_info_data.get("asan"): 76 executor_kwargs["timeout_multiplier"] = 3 77 if test_type == "wdspec": 78 executor_kwargs["webdriver_binary"] = kwargs.get("webdriver_binary") 79 return executor_kwargs 80 81 82def env_options(): 83 return {"host": "127.0.0.1", 84 "external_host": "web-platform.test", 85 "bind_hostname": "false", 86 "certificate_domain": "web-platform.test", 87 "supports_debugger": True} 88 89 90def run_info_extras(**kwargs): 91 return {"e10s": kwargs["gecko_e10s"]} 92 93 94def update_properties(): 95 return ["debug", "e10s", "os", "version", "processor", "bits"], {"debug", "e10s"} 96 97 98class FirefoxBrowser(Browser): 99 used_ports = set() 100 init_timeout = 60 101 102 def __init__(self, logger, binary, prefs_root, debug_info=None, 103 symbols_path=None, stackwalk_binary=None, certutil_binary=None, 104 ca_certificate_path=None, e10s=False, stackfix_dir=None): 105 Browser.__init__(self, logger) 106 self.binary = binary 107 self.prefs_root = prefs_root 108 self.marionette_port = None 109 self.runner = None 110 self.debug_info = debug_info 111 self.profile = None 112 self.symbols_path = symbols_path 113 self.stackwalk_binary = stackwalk_binary 114 self.ca_certificate_path = ca_certificate_path 115 self.certutil_binary = certutil_binary 116 self.e10s = e10s 117 if self.symbols_path and stackfix_dir: 118 self.stack_fixer = get_stack_fixer_function(stackfix_dir, 119 self.symbols_path) 120 else: 121 self.stack_fixer = None 122 123 def start(self): 124 self.marionette_port = get_free_port(2828, exclude=self.used_ports) 125 self.used_ports.add(self.marionette_port) 126 127 env = os.environ.copy() 128 env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1" 129 130 locations = ServerLocations(filename=os.path.join(here, "server-locations.txt")) 131 132 preferences = self.load_prefs() 133 134 self.profile = FirefoxProfile(locations=locations, 135 preferences=preferences) 136 self.profile.set_preferences({"marionette.defaultPrefs.enabled": True, 137 "marionette.defaultPrefs.port": self.marionette_port, 138 "dom.disable_open_during_load": False, 139 "network.dns.localDomains": ",".join(hostnames), 140 "network.proxy.type": 0, 141 "places.history.enabled": False}) 142 if self.e10s: 143 self.profile.set_preferences({"browser.tabs.remote.autostart": True}) 144 145 # Bug 1262954: winxp + e10s, disable hwaccel 146 if (self.e10s and platform.system() in ("Windows", "Microsoft") and 147 '5.1' in platform.version()): 148 self.profile.set_preferences({"layers.acceleration.disabled": True}) 149 150 if self.ca_certificate_path is not None: 151 self.setup_ssl() 152 153 debug_args, cmd = browser_command(self.binary, [cmd_arg("marionette"), "about:blank"], 154 self.debug_info) 155 156 self.runner = FirefoxRunner(profile=self.profile, 157 binary=cmd[0], 158 cmdargs=cmd[1:], 159 env=env, 160 process_class=ProcessHandler, 161 process_args={"processOutputLine": [self.on_output]}) 162 163 self.logger.debug("Starting Firefox") 164 165 self.runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive) 166 self.logger.debug("Firefox Started") 167 168 def load_prefs(self): 169 prefs_path = os.path.join(self.prefs_root, "prefs_general.js") 170 if os.path.exists(prefs_path): 171 preferences = Preferences.read_prefs(prefs_path) 172 else: 173 self.logger.warning("Failed to find base prefs file in %s" % prefs_path) 174 preferences = [] 175 176 return preferences 177 178 def stop(self): 179 self.logger.debug("Stopping browser") 180 if self.runner is not None: 181 try: 182 self.runner.stop() 183 except OSError: 184 # This can happen on Windows if the process is already dead 185 pass 186 187 def pid(self): 188 if self.runner.process_handler is None: 189 return None 190 191 try: 192 return self.runner.process_handler.pid 193 except AttributeError: 194 return None 195 196 def on_output(self, line): 197 """Write a line of output from the firefox process to the log""" 198 data = line.decode("utf8", "replace") 199 if self.stack_fixer: 200 data = self.stack_fixer(data) 201 self.logger.process_output(self.pid(), 202 data, 203 command=" ".join(self.runner.command)) 204 205 def is_alive(self): 206 if self.runner: 207 return self.runner.is_running() 208 return False 209 210 def cleanup(self): 211 self.stop() 212 213 def executor_browser(self): 214 assert self.marionette_port is not None 215 return ExecutorBrowser, {"marionette_port": self.marionette_port} 216 217 def log_crash(self, process, test): 218 dump_dir = os.path.join(self.profile.profile, "minidumps") 219 220 mozcrash.log_crashes(self.logger, 221 dump_dir, 222 symbols_path=self.symbols_path, 223 stackwalk_binary=self.stackwalk_binary, 224 process=process, 225 test=test) 226 227 def setup_ssl(self): 228 """Create a certificate database to use in the test profile. This is configured 229 to trust the CA Certificate that has signed the web-platform.test server 230 certificate.""" 231 232 self.logger.info("Setting up ssl") 233 234 # Make sure the certutil libraries from the source tree are loaded when using a 235 # local copy of certutil 236 # TODO: Maybe only set this if certutil won't launch? 237 env = os.environ.copy() 238 certutil_dir = os.path.dirname(self.binary) 239 if mozinfo.isMac: 240 env_var = "DYLD_LIBRARY_PATH" 241 elif mozinfo.isUnix: 242 env_var = "LD_LIBRARY_PATH" 243 else: 244 env_var = "PATH" 245 246 247 env[env_var] = (os.path.pathsep.join([certutil_dir, env[env_var]]) 248 if env_var in env else certutil_dir).encode( 249 sys.getfilesystemencoding() or 'utf-8', 'replace') 250 251 def certutil(*args): 252 cmd = [self.certutil_binary] + list(args) 253 self.logger.process_output("certutil", 254 subprocess.check_output(cmd, 255 env=env, 256 stderr=subprocess.STDOUT), 257 " ".join(cmd)) 258 259 pw_path = os.path.join(self.profile.profile, ".crtdbpw") 260 with open(pw_path, "w") as f: 261 # Use empty password for certificate db 262 f.write("\n") 263 264 cert_db_path = self.profile.profile 265 266 # Create a new certificate db 267 certutil("-N", "-d", cert_db_path, "-f", pw_path) 268 269 # Add the CA certificate to the database and mark as trusted to issue server certs 270 certutil("-A", "-d", cert_db_path, "-f", pw_path, "-t", "CT,,", 271 "-n", "web-platform-tests", "-i", self.ca_certificate_path) 272 273 # List all certs in the database 274 certutil("-L", "-d", cert_db_path) 275