Spurious output on Windows tests: AQMEIOManager::FindIOUnit: error -1
[WebKit-https.git] / Tools / Scripts / webkitpy / port / mac.py
1 # Copyright (C) 2011 Google Inc. All rights reserved.
2 # Copyright (C) 2012, 2013 Apple Inc. All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 #     * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 #     * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 #     * Neither the Google name nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 import logging
31 import os
32 import time
33 import re
34
35 from webkitpy.common.system.crashlogs import CrashLogs
36 from webkitpy.common.system.executive import ScriptError
37 from webkitpy.port.apple import ApplePort
38 from webkitpy.port.leakdetector import LeakDetector
39
40
41 _log = logging.getLogger(__name__)
42
43
44 class MacPort(ApplePort):
45     port_name = "mac"
46
47     VERSION_FALLBACK_ORDER = ['mac-snowleopard', 'mac-lion', 'mac-mountainlion', 'mac-mavericks', 'mac-yosemite', 'mac-elcapitan']
48
49     ARCHITECTURES = ['x86_64', 'x86']
50
51     DEFAULT_ARCHITECTURE = 'x86_64'
52
53     def __init__(self, host, port_name, **kwargs):
54         ApplePort.__init__(self, host, port_name, **kwargs)
55
56         self._leak_detector = LeakDetector(self)
57         if self.get_option("leaks"):
58             # DumpRenderTree slows down noticably if we run more than about 1000 tests in a batch
59             # with MallocStackLogging enabled.
60             self.set_option_default("batch_size", 1000)
61
62     def default_timeout_ms(self):
63         if self.get_option('guard_malloc'):
64             return 350 * 1000
65         return super(MacPort, self).default_timeout_ms()
66
67     def supports_per_test_timeout(self):
68         return True
69
70     def _build_driver_flags(self):
71         return ['ARCHS=i386'] if self.architecture() == 'x86' else []
72
73     def should_retry_crashes(self):
74         # On Apple Mac, we retry crashes due to https://bugs.webkit.org/show_bug.cgi?id=82233
75         return True
76
77     def default_baseline_search_path(self):
78         name = self._name.replace('-wk2', '')
79         wk_version = [] if self.get_option('webkit_test_runner') else ['mac-wk1']
80         if name.endswith(self.FUTURE_VERSION):
81             fallback_names = wk_version + [self.port_name]
82         else:
83             fallback_names = self.VERSION_FALLBACK_ORDER[self.VERSION_FALLBACK_ORDER.index(name):-1] + wk_version + [self.port_name]
84         # FIXME: mac-wk2 should appear at the same place as mac-wk1.
85         if self.get_option('webkit_test_runner'):
86             fallback_names = [self._wk2_port_name(), 'wk2'] + fallback_names
87         return map(self._webkit_baseline_path, fallback_names)
88
89     def _port_specific_expectations_files(self):
90         return list(reversed([self._filesystem.join(self._webkit_baseline_path(p), 'TestExpectations') for p in self.baseline_search_path()]))
91
92     def configuration_specifier_macros(self):
93         return {
94             "elcapitan+": ["elcapitan", "future"],
95             "mavericks+": ["mavericks", "yosemite", "elcapitan", "future"],
96             "yosemite+": ["yosemite", "elcapitan", "future"],
97         }
98
99     def setup_environ_for_server(self, server_name=None):
100         env = super(MacPort, self).setup_environ_for_server(server_name)
101         if server_name == self.driver_name():
102             if self.get_option('leaks'):
103                 env['MallocStackLogging'] = '1'
104             if self.get_option('guard_malloc'):
105                 self._append_value_colon_separated(env, 'DYLD_INSERT_LIBRARIES', '/usr/lib/libgmalloc.dylib')
106             self._append_value_colon_separated(env, 'DYLD_INSERT_LIBRARIES', self._build_path("libWebCoreTestShim.dylib"))
107         env['XML_CATALOG_FILES'] = ''  # work around missing /etc/catalog <rdar://problem/4292995>
108         return env
109
110     def operating_system(self):
111         return 'mac'
112
113     # Belongs on a Platform object.
114     def is_mavericks(self):
115         return self._version == 'mavericks'
116
117     def default_child_processes(self):
118         if self._version == "snowleopard":
119             _log.warning("Cannot run tests in parallel on Snow Leopard due to rdar://problem/10621525.")
120             return 1
121
122         default_count = super(MacPort, self).default_child_processes()
123
124         # FIXME: https://bugs.webkit.org/show_bug.cgi?id=95906  With too many WebProcess WK2 tests get stuck in resource contention.
125         # To alleviate the issue reduce the number of running processes
126         # Anecdotal evidence suggests that a 4 core/8 core logical machine may run into this, but that a 2 core/4 core logical machine does not.
127         should_throttle_for_wk2 = self.get_option('webkit_test_runner') and default_count > 4
128         # We also want to throttle for leaks bots.
129         if should_throttle_for_wk2 or self.get_option('leaks'):
130             default_count = int(.75 * default_count)
131
132         # Make sure we have enough ram to support that many instances:
133         total_memory = self.host.platform.total_bytes_memory()
134         if total_memory:
135             bytes_per_drt = 256 * 1024 * 1024  # Assume each DRT needs 256MB to run.
136             overhead = 2048 * 1024 * 1024  # Assume we need 2GB free for the O/S
137             supportable_instances = max((total_memory - overhead) / bytes_per_drt, 1)  # Always use one process, even if we don't have space for it.
138             if supportable_instances < default_count:
139                 _log.warning("This machine could support %s child processes, but only has enough memory for %s." % (default_count, supportable_instances))
140         else:
141             _log.warning("Cannot determine available memory for child processes, using default child process count of %s." % default_count)
142             supportable_instances = default_count
143         return min(supportable_instances, default_count)
144
145     def _build_java_test_support(self):
146         java_tests_path = self._filesystem.join(self.layout_tests_dir(), "java")
147         build_java = [self.make_command(), "-C", java_tests_path]
148         if self._executive.run_command(build_java, return_exit_code=True):  # Paths are absolute, so we don't need to set a cwd.
149             _log.error("Failed to build Java support files: %s" % build_java)
150             return False
151         return True
152
153     def check_for_leaks(self, process_name, process_pid):
154         if not self.get_option('leaks'):
155             return
156         # We could use http://code.google.com/p/psutil/ to get the process_name from the pid.
157         self._leak_detector.check_for_leaks(process_name, process_pid)
158
159     def print_leaks_summary(self):
160         if not self.get_option('leaks'):
161             return
162         # We're in the manager process, so the leak detector will not have a valid list of leak files.
163         # FIXME: This is a hack, but we don't have a better way to get this information from the workers yet.
164         # FIXME: This will include too many leaks in subsequent runs until the results directory is cleared!
165         leaks_files = self._leak_detector.leaks_files_in_directory(self.results_directory())
166         if not leaks_files:
167             return
168         total_bytes_string, unique_leaks = self._leak_detector.count_total_bytes_and_unique_leaks(leaks_files)
169         total_leaks = self._leak_detector.count_total_leaks(leaks_files)
170         _log.info("%s total leaks found for a total of %s." % (total_leaks, total_bytes_string))
171         _log.info("%s unique leaks found." % unique_leaks)
172
173     def _check_port_build(self):
174         return not self.get_option('java') or self._build_java_test_support()
175
176     def _path_to_webcore_library(self):
177         return self._build_path('WebCore.framework/Versions/A/WebCore')
178
179     def show_results_html_file(self, results_filename):
180         # We don't use self._run_script() because we don't want to wait for the script
181         # to exit and we want the output to show up on stdout in case there are errors
182         # launching the browser.
183         self._executive.popen([self.path_to_script('run-safari')] + self._arguments_for_configuration() + ['--no-saved-state', '-NSOpen', results_filename],
184             cwd=self.webkit_base(), stdout=file(os.devnull), stderr=file(os.devnull))
185
186     def sample_file_path(self, name, pid):
187         return self._filesystem.join(self.results_directory(), "{0}-{1}-sample.txt".format(name, pid))
188
189     def _get_crash_log(self, name, pid, stdout, stderr, newer_than, time_fn=None, sleep_fn=None, wait_for_log=True):
190         # Note that we do slow-spin here and wait, since it appears the time
191         # ReportCrash takes to actually write and flush the file varies when there are
192         # lots of simultaneous crashes going on.
193         # FIXME: Should most of this be moved into CrashLogs()?
194         time_fn = time_fn or time.time
195         sleep_fn = sleep_fn or time.sleep
196         crash_log = ''
197         crash_logs = CrashLogs(self.host)
198         now = time_fn()
199         # FIXME: delete this after we're sure this code is working ...
200         _log.debug('looking for crash log for %s:%s' % (name, str(pid)))
201         deadline = now + 5 * int(self.get_option('child_processes', 1))
202         while not crash_log and now <= deadline:
203             crash_log = crash_logs.find_newest_log(name, pid, include_errors=True, newer_than=newer_than)
204             if not wait_for_log:
205                 break
206             if not crash_log or not [line for line in crash_log.splitlines() if not line.startswith('ERROR')]:
207                 sleep_fn(0.1)
208                 now = time_fn()
209
210         if not crash_log:
211             return (stderr, None)
212         return (stderr, crash_log)
213
214     def look_for_new_crash_logs(self, crashed_processes, start_time):
215         """Since crash logs can take a long time to be written out if the system is
216            under stress do a second pass at the end of the test run.
217
218            crashes: test_name -> pid, process_name tuple of crashed process
219            start_time: time the tests started at.  We're looking for crash
220                logs after that time.
221         """
222         crash_logs = {}
223         for (test_name, process_name, pid) in crashed_processes:
224             # Passing None for output.  This is a second pass after the test finished so
225             # if the output had any logging we would have already collected it.
226             crash_log = self._get_crash_log(process_name, pid, None, None, start_time, wait_for_log=False)[1]
227             if not crash_log:
228                 continue
229             crash_logs[test_name] = crash_log
230         return crash_logs
231
232     def look_for_new_samples(self, unresponsive_processes, start_time):
233         sample_files = {}
234         for (test_name, process_name, pid) in unresponsive_processes:
235             sample_file = self.sample_file_path(process_name, pid)
236             if not self._filesystem.isfile(sample_file):
237                 continue
238             sample_files[test_name] = sample_file
239         return sample_files
240
241     def sample_process(self, name, pid):
242         try:
243             hang_report = self.sample_file_path(name, pid)
244             self._executive.run_command([
245                 "/usr/bin/sample",
246                 pid,
247                 10,
248                 10,
249                 "-file",
250                 hang_report,
251             ])
252         except ScriptError as e:
253             _log.warning('Unable to sample process:' + str(e))
254
255     def _path_to_helper(self):
256         binary_name = 'LayoutTestHelper'
257         return self._build_path(binary_name)
258
259     def start_helper(self, pixel_tests=False):
260         helper_path = self._path_to_helper()
261         if not helper_path:
262             _log.error("No path to LayoutTestHelper binary")
263             return False
264         _log.debug("Starting layout helper %s" % helper_path)
265         arguments = [helper_path, '--install-color-profile']
266         self._helper = self._executive.popen(arguments,
267             stdin=self._executive.PIPE, stdout=self._executive.PIPE, stderr=None)
268         is_ready = self._helper.stdout.readline()
269         if not is_ready.startswith('ready'):
270             _log.error("LayoutTestHelper could not start")
271             return False
272         return True
273
274     def reset_preferences(self):
275         _log.debug("Resetting persistent preferences")
276
277         for domain in ["DumpRenderTree", "WebKitTestRunner"]:
278             try:
279                 self._executive.run_command(["defaults", "delete", domain])
280             except ScriptError, e:
281                 # 'defaults' returns 1 if the domain did not exist
282                 if e.exit_code != 1:
283                     raise e
284
285     def stop_helper(self):
286         if self._helper:
287             _log.debug("Stopping LayoutTestHelper")
288             try:
289                 self._helper.stdin.write("x\n")
290                 self._helper.stdin.close()
291                 self._helper.wait()
292             except IOError, e:
293                 _log.debug("IOError raised while stopping helper: %s" % str(e))
294             self._helper = None
295
296     def make_command(self):
297         return self.xcrun_find('make', '/usr/bin/make')
298
299     def nm_command(self):
300         return self.xcrun_find('nm', 'nm')
301
302     def xcrun_find(self, command, fallback):
303         try:
304             return self._executive.run_command(['xcrun', '-find', command]).rstrip()
305         except ScriptError:
306             _log.warn("xcrun failed; falling back to '%s'." % fallback)
307             return fallback
308
309     def logging_patterns_to_strip(self):
310         # FIXME: Remove this after <rdar://problem/15605007> is fixed
311         return [(re.compile('(AVF|GVA) info:.*\n'), '')]
312
313     def stderr_patterns_to_strip(self):
314         worthless_patterns = []
315         # FIXME: We still get CoreMedia stderr logging that doesn't match the below lines. 'defaults write com.apple.coremedia fig_notes 0' may work better for silencing them all, however running tests shouldn't change user's defaults.
316         worthless_patterns.append((re.compile('.*(Fig|fig|itemasync|vt|mv_|PullParamSetSPS|ccrp_|client).* signalled err=.*\n'), ''))
317         worthless_patterns.append((re.compile('.*<<<< FigFilePlayer >>>>.*\n'), ''))
318         worthless_patterns.append((re.compile('.*<<<< FigFile >>>>.*\n'), ''))
319         worthless_patterns.append((re.compile('.*<<<< FAQ >>>>.*\n'), ''))
320         worthless_patterns.append((re.compile('.*<<<< MediaValidator >>>>.*\n'), ''))
321         worthless_patterns.append((re.compile('.*<<<< VMC >>>>.*\n'), ''))
322         worthless_patterns.append((re.compile('.*<<< FFR_Common >>>.*\n'), ''))
323         return worthless_patterns