1 # Copyright (C) 2011 Google Inc. All rights reserved.
2 # Copyright (C) 2012, 2013 Apple Inc. All rights reserved.
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
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
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.
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.
34 from webkitpy.common.system.crashlogs import CrashLogs
35 from webkitpy.common.system.executive import ScriptError
36 from webkitpy.port.apple import ApplePort
37 from webkitpy.port.leakdetector import LeakDetector
40 _log = logging.getLogger(__name__)
43 class MacPort(ApplePort):
46 VERSION_FALLBACK_ORDER = ['mac-snowleopard', 'mac-lion', 'mac-mountainlion']
48 ARCHITECTURES = ['x86_64', 'x86']
50 def __init__(self, host, port_name, **kwargs):
51 ApplePort.__init__(self, host, port_name, **kwargs)
52 self._architecture = self.get_option('architecture')
54 if not self._architecture:
55 self._architecture = 'x86_64'
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)
63 def default_timeout_ms(self):
64 if self.get_option('guard_malloc'):
66 return super(MacPort, self).default_timeout_ms()
68 def supports_per_test_timeout(self):
71 def _build_driver_flags(self):
72 return ['ARCHS=i386'] if self.architecture() == 'x86' else []
74 def should_retry_crashes(self):
75 # On Apple Mac, we retry crashes due to https://bugs.webkit.org/show_bug.cgi?id=82233
78 def default_baseline_search_path(self):
79 name = self._name.replace('-wk2', '')
80 if name.endswith(self.FUTURE_VERSION):
81 fallback_names = [self.port_name]
83 fallback_names = self.VERSION_FALLBACK_ORDER[self.VERSION_FALLBACK_ORDER.index(name):-1] + [self.port_name]
84 if self.get_option('webkit_test_runner'):
85 fallback_names = [self._wk2_port_name(), 'wk2'] + fallback_names
86 return map(self._webkit_baseline_path, fallback_names)
88 def _port_specific_expectations_files(self):
89 return list(reversed([self._filesystem.join(self._webkit_baseline_path(p), 'TestExpectations') for p in self.baseline_search_path()]))
91 def setup_environ_for_server(self, server_name=None):
92 env = super(MacPort, self).setup_environ_for_server(server_name)
93 if server_name == self.driver_name():
94 if self.get_option('leaks'):
95 env['MallocStackLogging'] = '1'
96 if self.get_option('guard_malloc'):
97 env['DYLD_INSERT_LIBRARIES'] = '/usr/lib/libgmalloc.dylib:' + self._build_path("libWebCoreTestShim.dylib")
99 env['DYLD_INSERT_LIBRARIES'] = self._build_path("libWebCoreTestShim.dylib")
100 env['XML_CATALOG_FILES'] = '' # work around missing /etc/catalog <rdar://problem/4292995>
103 def operating_system(self):
106 # Belongs on a Platform object.
107 def is_snowleopard(self):
108 return self._version == "snowleopard"
110 # Belongs on a Platform object.
112 return self._version == "lion"
114 def default_child_processes(self):
115 if self._version == "snowleopard":
116 _log.warning("Cannot run tests in parallel on Snow Leopard due to rdar://problem/10621525.")
119 default_count = super(MacPort, self).default_child_processes()
121 # FIXME: https://bugs.webkit.org/show_bug.cgi?id=95906 With too many WebProcess WK2 tests get stuck in resource contention.
122 # To alleviate the issue reduce the number of running processes
123 # 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.
124 should_throttle_for_wk2 = self.get_option('webkit_test_runner') and default_count > 4
125 # We also want to throttle for leaks bots.
126 if should_throttle_for_wk2 or self.get_option('leaks'):
127 default_count = int(.75 * default_count)
129 # Make sure we have enough ram to support that many instances:
130 total_memory = self.host.platform.total_bytes_memory()
132 bytes_per_drt = 256 * 1024 * 1024 # Assume each DRT needs 256MB to run.
133 overhead = 2048 * 1024 * 1024 # Assume we need 2GB free for the O/S
134 supportable_instances = max((total_memory - overhead) / bytes_per_drt, 1) # Always use one process, even if we don't have space for it.
135 if supportable_instances < default_count:
136 _log.warning("This machine could support %s child processes, but only has enough memory for %s." % (default_count, supportable_instances))
138 _log.warning("Cannot determine available memory for child processes, using default child process count of %s." % default_count)
139 supportable_instances = default_count
140 return min(supportable_instances, default_count)
142 def _build_java_test_support(self):
143 java_tests_path = self._filesystem.join(self.layout_tests_dir(), "java")
144 build_java = [self.make_command(), "-C", java_tests_path]
145 if self._executive.run_command(build_java, return_exit_code=True): # Paths are absolute, so we don't need to set a cwd.
146 _log.error("Failed to build Java support files: %s" % build_java)
150 def check_for_leaks(self, process_name, process_pid):
151 if not self.get_option('leaks'):
153 # We could use http://code.google.com/p/psutil/ to get the process_name from the pid.
154 self._leak_detector.check_for_leaks(process_name, process_pid)
156 def print_leaks_summary(self):
157 if not self.get_option('leaks'):
159 # We're in the manager process, so the leak detector will not have a valid list of leak files.
160 # FIXME: This is a hack, but we don't have a better way to get this information from the workers yet.
161 # FIXME: This will include too many leaks in subsequent runs until the results directory is cleared!
162 leaks_files = self._leak_detector.leaks_files_in_directory(self.results_directory())
165 total_bytes_string, unique_leaks = self._leak_detector.count_total_bytes_and_unique_leaks(leaks_files)
166 total_leaks = self._leak_detector.count_total_leaks(leaks_files)
167 _log.info("%s total leaks found for a total of %s!" % (total_leaks, total_bytes_string))
168 _log.info("%s unique leaks found!" % unique_leaks)
170 def _check_port_build(self):
171 return self._build_java_test_support()
173 def _path_to_webcore_library(self):
174 return self._build_path('WebCore.framework/Versions/A/WebCore')
176 def show_results_html_file(self, results_filename):
177 # We don't use self._run_script() because we don't want to wait for the script
178 # to exit and we want the output to show up on stdout in case there are errors
179 # launching the browser.
180 self._executive.popen([self.path_to_script('run-safari')] + self._arguments_for_configuration() + ['--no-saved-state', '-NSOpen', results_filename],
181 cwd=self.webkit_base(), stdout=file(os.devnull), stderr=file(os.devnull))
183 # FIXME: The next two routines turn off the http locking in order
184 # to work around failures on the bots caused when the slave restarts.
185 # See https://bugs.webkit.org/show_bug.cgi?id=64886 for more info.
186 # The proper fix is to make sure the slave is actually stopping NRWT
187 # properly on restart. Note that by removing the lock file and not waiting,
188 # the result should be that if there is a web server already running,
189 # it'll be killed and this one will be started in its place; this
190 # may lead to weird things happening in the other run. However, I don't
191 # think we're (intentionally) actually running multiple runs concurrently
194 def acquire_http_lock(self):
197 def release_http_lock(self):
200 def sample_file_path(self, name, pid):
201 return self._filesystem.join(self.results_directory(), "{0}-{1}-sample.txt".format(name, pid))
203 def _get_crash_log(self, name, pid, stdout, stderr, newer_than, time_fn=None, sleep_fn=None, wait_for_log=True):
204 # Note that we do slow-spin here and wait, since it appears the time
205 # ReportCrash takes to actually write and flush the file varies when there are
206 # lots of simultaneous crashes going on.
207 # FIXME: Should most of this be moved into CrashLogs()?
208 time_fn = time_fn or time.time
209 sleep_fn = sleep_fn or time.sleep
211 crash_logs = CrashLogs(self.host)
213 # FIXME: delete this after we're sure this code is working ...
214 _log.debug('looking for crash log for %s:%s' % (name, str(pid)))
215 deadline = now + 5 * int(self.get_option('child_processes', 1))
216 while not crash_log and now <= deadline:
217 crash_log = crash_logs.find_newest_log(name, pid, include_errors=True, newer_than=newer_than)
220 if not crash_log or not [line for line in crash_log.splitlines() if not line.startswith('ERROR')]:
225 return (stderr, None)
226 return (stderr, crash_log)
228 def look_for_new_crash_logs(self, crashed_processes, start_time):
229 """Since crash logs can take a long time to be written out if the system is
230 under stress do a second pass at the end of the test run.
232 crashes: test_name -> pid, process_name tuple of crashed process
233 start_time: time the tests started at. We're looking for crash
234 logs after that time.
237 for (test_name, process_name, pid) in crashed_processes:
238 # Passing None for output. This is a second pass after the test finished so
239 # if the output had any logging we would have already collected it.
240 crash_log = self._get_crash_log(process_name, pid, None, None, start_time, wait_for_log=False)[1]
243 crash_logs[test_name] = crash_log
246 def look_for_new_samples(self, unresponsive_processes, start_time):
248 for (test_name, process_name, pid) in unresponsive_processes:
249 sample_file = self.sample_file_path(process_name, pid)
250 if not self._filesystem.isfile(sample_file):
252 sample_files[test_name] = sample_file
255 def sample_process(self, name, pid):
257 hang_report = self.sample_file_path(name, pid)
258 self._executive.run_command([
266 except ScriptError as e:
267 _log.warning('Unable to sample process:' + str(e))
269 def _path_to_helper(self):
270 binary_name = 'LayoutTestHelper'
271 return self._build_path(binary_name)
273 def start_helper(self):
274 helper_path = self._path_to_helper()
276 _log.debug("Starting layout helper %s" % helper_path)
277 self._helper = self._executive.popen([helper_path],
278 stdin=self._executive.PIPE, stdout=self._executive.PIPE, stderr=None)
279 is_ready = self._helper.stdout.readline()
280 if not is_ready.startswith('ready'):
281 _log.error("LayoutTestHelper failed to be ready")
283 def stop_helper(self):
285 _log.debug("Stopping LayoutTestHelper")
287 self._helper.stdin.write("x\n")
288 self._helper.stdin.close()
291 _log.debug("IOError raised while stopping helper: %s" % str(e))
294 def make_command(self):
295 return self.xcrun_find('make', '/usr/bin/make')
297 def nm_command(self):
298 return self.xcrun_find('nm', 'nm')
300 def xcrun_find(self, command, fallback):
302 return self._executive.run_command(['xcrun', '-find', command]).rstrip()
304 _log.warn("xcrun failed; falling back to '%s'." % fallback)