43da63c7ca4ee05a0c20bb3d01ddd2722d862090
[WebKit-https.git] / WebDriverTests / imported / w3c / tools / wptrunner / wptrunner / browsers / firefox.py
1 import os
2 import platform
3 import signal
4 import subprocess
5 import sys
6
7 import mozinfo
8 import mozleak
9 from mozprocess import ProcessHandler
10 from mozprofile import FirefoxProfile, Preferences
11 from mozprofile.permissions import ServerLocations
12 from mozrunner import FirefoxRunner
13 from mozrunner.utils import get_stack_fixer_function
14 from mozcrash import mozcrash
15
16 from .base import (get_free_port,
17                    Browser,
18                    ExecutorBrowser,
19                    require_arg,
20                    cmd_arg,
21                    browser_command)
22 from ..executors import executor_kwargs as base_executor_kwargs
23 from ..executors.executormarionette import (MarionetteTestharnessExecutor,
24                                             MarionetteRefTestExecutor,
25                                             MarionetteWdspecExecutor)
26 from ..environment import hostnames
27
28
29 here = os.path.join(os.path.split(__file__)[0])
30
31 __wptrunner__ = {"product": "firefox",
32                  "check_args": "check_args",
33                  "browser": "FirefoxBrowser",
34                  "executor": {"testharness": "MarionetteTestharnessExecutor",
35                               "reftest": "MarionetteRefTestExecutor",
36                               "wdspec": "MarionetteWdspecExecutor"},
37                  "browser_kwargs": "browser_kwargs",
38                  "executor_kwargs": "executor_kwargs",
39                  "env_extras": "env_extras",
40                  "env_options": "env_options",
41                  "run_info_extras": "run_info_extras",
42                  "update_properties": "update_properties"}
43
44
45 def get_timeout_multiplier(test_type, run_info_data, **kwargs):
46     if kwargs["timeout_multiplier"] is not None:
47         return kwargs["timeout_multiplier"]
48     if test_type == "reftest":
49         if run_info_data["debug"] or run_info_data.get("asan"):
50             return 4
51         else:
52             return 2
53     elif run_info_data["debug"] or run_info_data.get("asan"):
54         return 3
55     return 1
56
57
58 def check_args(**kwargs):
59     require_arg(kwargs, "binary")
60     if kwargs["ssl_type"] != "none":
61         require_arg(kwargs, "certutil_binary")
62
63
64 def browser_kwargs(test_type, run_info_data, **kwargs):
65     return {"binary": kwargs["binary"],
66             "prefs_root": kwargs["prefs_root"],
67             "extra_prefs": kwargs["extra_prefs"],
68             "test_type": test_type,
69             "debug_info": kwargs["debug_info"],
70             "symbols_path": kwargs["symbols_path"],
71             "stackwalk_binary": kwargs["stackwalk_binary"],
72             "certutil_binary": kwargs["certutil_binary"],
73             "ca_certificate_path": kwargs["ssl_env"].ca_cert_path(),
74             "e10s": kwargs["gecko_e10s"],
75             "stackfix_dir": kwargs["stackfix_dir"],
76             "binary_args": kwargs["binary_args"],
77             "timeout_multiplier": get_timeout_multiplier(test_type,
78                                                          run_info_data,
79                                                          **kwargs),
80             "leak_check": kwargs["leak_check"],
81             "stylo_threads": kwargs["stylo_threads"]}
82
83
84 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
85                     **kwargs):
86     executor_kwargs = base_executor_kwargs(test_type, server_config,
87                                            cache_manager, **kwargs)
88     executor_kwargs["close_after_done"] = test_type != "reftest"
89     executor_kwargs["timeout_multiplier"] = get_timeout_multiplier(test_type,
90                                                                    run_info_data,
91                                                                    **kwargs)
92     if test_type == "reftest":
93         executor_kwargs["reftest_internal"] = kwargs["reftest_internal"]
94         executor_kwargs["reftest_screenshot"] = kwargs["reftest_screenshot"]
95     if test_type == "wdspec":
96         fxOptions = {}
97         if kwargs["binary"]:
98             fxOptions["binary"] = kwargs["binary"]
99         if kwargs["binary_args"]:
100             fxOptions["args"] = kwargs["binary_args"]
101         fxOptions["prefs"] = {
102             "network.dns.localDomains": ",".join(hostnames)
103         }
104         capabilities = {"moz:firefoxOptions": fxOptions}
105         executor_kwargs["capabilities"] = capabilities
106     return executor_kwargs
107
108
109 def env_extras(**kwargs):
110     return []
111
112
113 def env_options():
114     return {"host": "127.0.0.1",
115             "external_host": "web-platform.test",
116             "bind_hostname": "false",
117             "certificate_domain": "web-platform.test",
118             "supports_debugger": True}
119
120
121 def run_info_extras(**kwargs):
122     return {"e10s": kwargs["gecko_e10s"],
123             "headless": "MOZ_HEADLESS" in os.environ}
124
125
126 def update_properties():
127     return (["debug", "stylo", "e10s", "os", "version", "processor", "bits"],
128             {"debug", "e10s", "stylo"})
129
130
131 class FirefoxBrowser(Browser):
132     used_ports = set()
133     init_timeout = 60
134     shutdown_timeout = 60
135
136     def __init__(self, logger, binary, prefs_root, test_type, extra_prefs=None, debug_info=None,
137                  symbols_path=None, stackwalk_binary=None, certutil_binary=None,
138                  ca_certificate_path=None, e10s=False, stackfix_dir=None,
139                  binary_args=None, timeout_multiplier=None, leak_check=False, stylo_threads=1):
140         Browser.__init__(self, logger)
141         self.binary = binary
142         self.prefs_root = prefs_root
143         self.test_type = test_type
144         self.extra_prefs = extra_prefs
145         self.marionette_port = None
146         self.runner = None
147         self.debug_info = debug_info
148         self.profile = None
149         self.symbols_path = symbols_path
150         self.stackwalk_binary = stackwalk_binary
151         self.ca_certificate_path = ca_certificate_path
152         self.certutil_binary = certutil_binary
153         self.e10s = e10s
154         self.binary_args = binary_args
155         if stackfix_dir:
156             self.stack_fixer = get_stack_fixer_function(stackfix_dir,
157                                                         self.symbols_path)
158         else:
159             self.stack_fixer = None
160
161         if timeout_multiplier:
162             self.init_timeout = self.init_timeout * timeout_multiplier
163
164         self.leak_report_file = None
165         self.leak_check = leak_check
166         self.stylo_threads = stylo_threads
167
168     def settings(self, test):
169         return {"check_leaks": self.leak_check and not test.leaks}
170
171     def start(self, **kwargs):
172         if self.marionette_port is None:
173             self.marionette_port = get_free_port(2828, exclude=self.used_ports)
174             self.used_ports.add(self.marionette_port)
175
176         env = os.environ.copy()
177         env["MOZ_CRASHREPORTER"] = "1"
178         env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1"
179         env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1"
180         env["STYLO_THREADS"] = str(self.stylo_threads)
181
182         locations = ServerLocations(filename=os.path.join(here, "server-locations.txt"))
183
184         preferences = self.load_prefs()
185
186         self.profile = FirefoxProfile(locations=locations,
187                                       preferences=preferences)
188         self.profile.set_preferences({"marionette.port": self.marionette_port,
189                                       "dom.disable_open_during_load": False,
190                                       "network.dns.localDomains": ",".join(hostnames),
191                                       "network.proxy.type": 0,
192                                       "places.history.enabled": False,
193                                       "dom.send_after_paint_to_content": True,
194                                       "network.preload": True})
195         if self.e10s:
196             self.profile.set_preferences({"browser.tabs.remote.autostart": True})
197
198         if self.test_type == "reftest":
199             self.profile.set_preferences({"layout.interruptible-reflow.enabled": False})
200
201         if self.leak_check and kwargs.get("check_leaks", True):
202             self.leak_report_file = os.path.join(self.profile.profile, "runtests_leaks.log")
203             if os.path.exists(self.leak_report_file):
204                 os.remove(self.leak_report_file)
205             env["XPCOM_MEM_BLOAT_LOG"] = self.leak_report_file
206         else:
207             self.leak_report_file = None
208
209         # Bug 1262954: winxp + e10s, disable hwaccel
210         if (self.e10s and platform.system() in ("Windows", "Microsoft") and
211             '5.1' in platform.version()):
212             self.profile.set_preferences({"layers.acceleration.disabled": True})
213
214         if self.ca_certificate_path is not None:
215             self.setup_ssl()
216
217         debug_args, cmd = browser_command(self.binary,
218                                           self.binary_args if self.binary_args else [] +
219                                           [cmd_arg("marionette"), "about:blank"],
220                                           self.debug_info)
221
222         self.runner = FirefoxRunner(profile=self.profile,
223                                     binary=cmd[0],
224                                     cmdargs=cmd[1:],
225                                     env=env,
226                                     process_class=ProcessHandler,
227                                     process_args={"processOutputLine": [self.on_output]})
228
229         self.logger.debug("Starting Firefox")
230
231         self.runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive)
232         self.logger.debug("Firefox Started")
233
234     def load_prefs(self):
235         prefs = Preferences()
236
237         prefs_path = os.path.join(self.prefs_root, "prefs_general.js")
238         if os.path.exists(prefs_path):
239             prefs.add(Preferences.read_prefs(prefs_path))
240         else:
241             self.logger.warning("Failed to find base prefs file in %s" % prefs_path)
242
243         # Add any custom preferences
244         prefs.add(self.extra_prefs, cast=True)
245
246         return prefs()
247
248     def stop(self, force=False):
249         if self.runner is not None and self.runner.is_running():
250             try:
251                 # For Firefox we assume that stopping the runner prompts the
252                 # browser to shut down. This allows the leak log to be written
253                 for clean, stop_f in [(True, lambda: self.runner.wait(self.shutdown_timeout)),
254                                       (False, lambda: self.runner.stop(signal.SIGTERM)),
255                                       (False, lambda: self.runner.stop(signal.SIGKILL))]:
256                     if not force or not clean:
257                         retcode = stop_f()
258                         if retcode is not None:
259                             self.logger.info("Browser exited with return code %s" % retcode)
260                             break
261             except OSError:
262                 # This can happen on Windows if the process is already dead
263                 pass
264         self.logger.debug("stopped")
265
266     def process_leaks(self):
267         self.logger.debug("PROCESS LEAKS %s" % self.leak_report_file)
268         if self.leak_report_file is None:
269             return
270         mozleak.process_leak_log(
271             self.leak_report_file,
272             leak_thresholds={
273                 "default": 0,
274                 "tab": 10000,  # See dependencies of bug 1051230.
275                 # GMP rarely gets a log, but when it does, it leaks a little.
276                 "geckomediaplugin": 20000,
277             },
278             ignore_missing_leaks=["geckomediaplugin"],
279             log=self.logger,
280             stack_fixer=self.stack_fixer
281         )
282
283     def pid(self):
284         if self.runner.process_handler is None:
285             return None
286
287         try:
288             return self.runner.process_handler.pid
289         except AttributeError:
290             return None
291
292     def on_output(self, line):
293         """Write a line of output from the firefox process to the log"""
294         data = line.decode("utf8", "replace")
295         if self.stack_fixer:
296             data = self.stack_fixer(data)
297         self.logger.process_output(self.pid(),
298                                    data,
299                                    command=" ".join(self.runner.command))
300
301     def is_alive(self):
302         if self.runner:
303             return self.runner.is_running()
304         return False
305
306     def cleanup(self):
307         self.stop()
308         self.process_leaks()
309
310     def executor_browser(self):
311         assert self.marionette_port is not None
312         return ExecutorBrowser, {"marionette_port": self.marionette_port}
313
314     def check_for_crashes(self):
315         dump_dir = os.path.join(self.profile.profile, "minidumps")
316
317         return bool(mozcrash.check_for_crashes(dump_dir,
318                                                symbols_path=self.symbols_path,
319                                                stackwalk_binary=self.stackwalk_binary,
320                                                quiet=True))
321
322     def log_crash(self, process, test):
323         dump_dir = os.path.join(self.profile.profile, "minidumps")
324
325         mozcrash.log_crashes(self.logger,
326                              dump_dir,
327                              symbols_path=self.symbols_path,
328                              stackwalk_binary=self.stackwalk_binary,
329                              process=process,
330                              test=test)
331
332     def setup_ssl(self):
333         """Create a certificate database to use in the test profile. This is configured
334         to trust the CA Certificate that has signed the web-platform.test server
335         certificate."""
336
337         self.logger.info("Setting up ssl")
338
339         # Make sure the certutil libraries from the source tree are loaded when using a
340         # local copy of certutil
341         # TODO: Maybe only set this if certutil won't launch?
342         env = os.environ.copy()
343         certutil_dir = os.path.dirname(self.binary)
344         if mozinfo.isMac:
345             env_var = "DYLD_LIBRARY_PATH"
346         elif mozinfo.isUnix:
347             env_var = "LD_LIBRARY_PATH"
348         else:
349             env_var = "PATH"
350
351
352         env[env_var] = (os.path.pathsep.join([certutil_dir, env[env_var]])
353                         if env_var in env else certutil_dir).encode(
354                                 sys.getfilesystemencoding() or 'utf-8', 'replace')
355
356         def certutil(*args):
357             cmd = [self.certutil_binary] + list(args)
358             self.logger.process_output("certutil",
359                                        subprocess.check_output(cmd,
360                                                                env=env,
361                                                                stderr=subprocess.STDOUT),
362                                        " ".join(cmd))
363
364         pw_path = os.path.join(self.profile.profile, ".crtdbpw")
365         with open(pw_path, "w") as f:
366             # Use empty password for certificate db
367             f.write("\n")
368
369         cert_db_path = self.profile.profile
370
371         # Create a new certificate db
372         certutil("-N", "-d", cert_db_path, "-f", pw_path)
373
374         # Add the CA certificate to the database and mark as trusted to issue server certs
375         certutil("-A", "-d", cert_db_path, "-f", pw_path, "-t", "CT,,",
376                  "-n", "web-platform-tests", "-i", self.ca_certificate_path)
377
378         # List all certs in the database
379         certutil("-L", "-d", cert_db_path)