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