NoEventDispatchAssertion in ContainerNode::removeChildren is too strict
[WebKit-https.git] / Tools / Scripts / webkitpy / layout_tests / port / mac.py
1 # Copyright (C) 2011 Google Inc. All rights reserved.
2 # Copyright (C) 2012 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
34 from webkitpy.common.system.crashlogs import CrashLogs
35 from webkitpy.common.system.executive import ScriptError
36 from webkitpy.layout_tests.port.apple import ApplePort
37 from webkitpy.layout_tests.port.leakdetector import LeakDetector
38
39
40 _log = logging.getLogger(__name__)
41
42
43 class MacPort(ApplePort):
44     port_name = "mac"
45
46     VERSION_FALLBACK_ORDER = ['mac-snowleopard', 'mac-lion', 'mac-mountainlion']
47
48     ARCHITECTURES = ['x86_64', 'x86']
49
50     def __init__(self, host, port_name, **kwargs):
51         ApplePort.__init__(self, host, port_name, **kwargs)
52         self._architecture = self.get_option('architecture')
53
54         if not self._architecture:
55             self._architecture = 'x86_64'
56
57         self._leak_detector = LeakDetector(self)
58         if self.get_option("leaks"):
59             # DumpRenderTree slows down noticably if we run more than about 1000 tests in a batch
60             # with MallocStackLogging enabled.
61             self.set_option_default("batch_size", 1000)
62
63     def default_timeout_ms(self):
64         if self.get_option('guard_malloc'):
65             return 350 * 1000
66         return super(MacPort, self).default_timeout_ms()
67
68     def _build_driver_flags(self):
69         return ['ARCHS=i386'] if self.architecture() == 'x86' else []
70
71     def should_retry_crashes(self):
72         # On Apple Mac, we retry crashes due to https://bugs.webkit.org/show_bug.cgi?id=82233
73         return True
74
75     def default_baseline_search_path(self):
76         name = self._name.replace('-wk2', '')
77         if name.endswith(self.FUTURE_VERSION):
78             fallback_names = [self.port_name]
79         else:
80             fallback_names = self.VERSION_FALLBACK_ORDER[self.VERSION_FALLBACK_ORDER.index(name):-1] + [self.port_name]
81         if self.get_option('webkit_test_runner'):
82             fallback_names = [self._wk2_port_name(), 'wk2'] + fallback_names
83         return map(self._webkit_baseline_path, fallback_names)
84
85     def expectations_files(self):
86         return reversed([self._filesystem.join(self._webkit_baseline_path(d), 'TestExpectations') for d in self.baseline_search_path()])
87
88     def setup_environ_for_server(self, server_name=None):
89         env = super(MacPort, self).setup_environ_for_server(server_name)
90         if server_name == self.driver_name():
91             if self.get_option('leaks'):
92                 env['MallocStackLogging'] = '1'
93             if self.get_option('guard_malloc'):
94                 env['DYLD_INSERT_LIBRARIES'] = '/usr/lib/libgmalloc.dylib'
95         env['XML_CATALOG_FILES'] = ''  # work around missing /etc/catalog <rdar://problem/4292995>
96         return env
97
98     def operating_system(self):
99         return 'mac'
100
101     # Belongs on a Platform object.
102     def is_snowleopard(self):
103         return self._version == "snowleopard"
104
105     # Belongs on a Platform object.
106     def is_lion(self):
107         return self._version == "lion"
108
109     def default_child_processes(self):
110         if self._version == "snowleopard":
111             _log.warning("Cannot run tests in parallel on Snow Leopard due to rdar://problem/10621525.")
112             return 1
113
114         default_count = super(MacPort, self).default_child_processes()
115
116         # FIXME: https://bugs.webkit.org/show_bug.cgi?id=95906  With too many WebProcess WK2 tests get stuck in resource contention.
117         # To alleviate the issue reduce the number of running processes
118         # 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.
119         if self.get_option('webkit_test_runner') and default_count > 4:
120             default_count = int(.75 * default_count)
121
122         # Make sure we have enough ram to support that many instances:
123         total_memory = self.host.platform.total_bytes_memory()
124         bytes_per_drt = 256 * 1024 * 1024  # Assume each DRT needs 256MB to run.
125         overhead = 2048 * 1024 * 1024  # Assume we need 2GB free for the O/S
126         supportable_instances = max((total_memory - overhead) / bytes_per_drt, 1)  # Always use one process, even if we don't have space for it.
127         if supportable_instances < default_count:
128             _log.warning("This machine could support %s child processes, but only has enough memory for %s." % (default_count, supportable_instances))
129         return min(supportable_instances, default_count)
130
131     def _build_java_test_support(self):
132         java_tests_path = self._filesystem.join(self.layout_tests_dir(), "java")
133         build_java = ["/usr/bin/make", "-C", java_tests_path]
134         if self._executive.run_command(build_java, return_exit_code=True):  # Paths are absolute, so we don't need to set a cwd.
135             _log.error("Failed to build Java support files: %s" % build_java)
136             return False
137         return True
138
139     def check_for_leaks(self, process_name, process_pid):
140         if not self.get_option('leaks'):
141             return
142         # We could use http://code.google.com/p/psutil/ to get the process_name from the pid.
143         self._leak_detector.check_for_leaks(process_name, process_pid)
144
145     def print_leaks_summary(self):
146         if not self.get_option('leaks'):
147             return
148         # We're in the manager process, so the leak detector will not have a valid list of leak files.
149         # FIXME: This is a hack, but we don't have a better way to get this information from the workers yet.
150         # FIXME: This will include too many leaks in subsequent runs until the results directory is cleared!
151         leaks_files = self._leak_detector.leaks_files_in_directory(self.results_directory())
152         if not leaks_files:
153             return
154         total_bytes_string, unique_leaks = self._leak_detector.count_total_bytes_and_unique_leaks(leaks_files)
155         total_leaks = self._leak_detector.count_total_leaks(leaks_files)
156         _log.info("%s total leaks found for a total of %s!" % (total_leaks, total_bytes_string))
157         _log.info("%s unique leaks found!" % unique_leaks)
158
159     def _check_port_build(self):
160         return self._build_java_test_support()
161
162     def _path_to_webcore_library(self):
163         return self._build_path('WebCore.framework/Versions/A/WebCore')
164
165     def show_results_html_file(self, results_filename):
166         # We don't use self._run_script() because we don't want to wait for the script
167         # to exit and we want the output to show up on stdout in case there are errors
168         # launching the browser.
169         self._executive.popen([self.path_to_script('run-safari')] + self._arguments_for_configuration() + ['--no-saved-state', '-NSOpen', results_filename],
170             cwd=self.webkit_base(), stdout=file(os.devnull), stderr=file(os.devnull))
171
172     # FIXME: The next two routines turn off the http locking in order
173     # to work around failures on the bots caused when the slave restarts.
174     # See https://bugs.webkit.org/show_bug.cgi?id=64886 for more info.
175     # The proper fix is to make sure the slave is actually stopping NRWT
176     # properly on restart. Note that by removing the lock file and not waiting,
177     # the result should be that if there is a web server already running,
178     # it'll be killed and this one will be started in its place; this
179     # may lead to weird things happening in the other run. However, I don't
180     # think we're (intentionally) actually running multiple runs concurrently
181     # on any Mac bots.
182
183     def acquire_http_lock(self):
184         pass
185
186     def release_http_lock(self):
187         pass
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 loggine 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 sample_process(self, name, pid):
233         try:
234             hang_report = self._filesystem.join(self.results_directory(), "%s-%s.sample.txt" % (name, pid))
235             self._executive.run_command([
236                 "/usr/bin/sample",
237                 pid,
238                 10,
239                 10,
240                 "-file",
241                 hang_report,
242             ])
243         except ScriptError as e:
244             _log.warning('Unable to sample process:' + str(e))
245
246     def _path_to_helper(self):
247         binary_name = 'LayoutTestHelper'
248         return self._build_path(binary_name)
249
250     def start_helper(self):
251         helper_path = self._path_to_helper()
252         if helper_path:
253             _log.debug("Starting layout helper %s" % helper_path)
254             self._helper = self._executive.popen([helper_path],
255                 stdin=self._executive.PIPE, stdout=self._executive.PIPE, stderr=None)
256             is_ready = self._helper.stdout.readline()
257             if not is_ready.startswith('ready'):
258                 _log.error("LayoutTestHelper failed to be ready")
259
260     def stop_helper(self):
261         if self._helper:
262             _log.debug("Stopping LayoutTestHelper")
263             try:
264                 self._helper.stdin.write("x\n")
265                 self._helper.stdin.close()
266                 self._helper.wait()
267             except IOError, e:
268                 _log.debug("IOError raised while stopping helper: %s" % str(e))
269             self._helper = None
270
271     def nm_command(self):
272         try:
273             return self._executive.run_command(['xcrun', '-find', 'nm']).rstrip()
274         except ScriptError:
275             _log.warn("xcrun failed; falling back to 'nm'.")
276             return 'nm'