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.layout_tests.port.apple import ApplePort
37 from webkitpy.layout_tests.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 _build_driver_flags(self):
69 return ['ARCHS=i386'] if self.architecture() == 'x86' else []
71 def should_retry_crashes(self):
72 # On Apple Mac, we retry crashes due to https://bugs.webkit.org/show_bug.cgi?id=82233
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]
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)
85 def expectations_files(self):
86 return reversed([self._filesystem.join(self._webkit_baseline_path(d), 'TestExpectations') for d in self.baseline_search_path()])
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>
98 def operating_system(self):
101 # Belongs on a Platform object.
102 def is_snowleopard(self):
103 return self._version == "snowleopard"
105 # Belongs on a Platform object.
107 return self._version == "lion"
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.")
114 default_count = super(MacPort, self).default_child_processes()
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)
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)
131 def _build_java_test_support(self):
132 java_tests_path = self._filesystem.join(self.layout_tests_dir(), "java")
133 build_java = [self.make_command(), "-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)
139 def check_for_leaks(self, process_name, process_pid):
140 if not self.get_option('leaks'):
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)
145 def print_leaks_summary(self):
146 if not self.get_option('leaks'):
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())
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)
159 def _check_port_build(self):
160 return self._build_java_test_support()
162 def _path_to_webcore_library(self):
163 return self._build_path('WebCore.framework/Versions/A/WebCore')
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))
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
183 def acquire_http_lock(self):
186 def release_http_lock(self):
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
197 crash_logs = CrashLogs(self.host)
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)
206 if not crash_log or not [line for line in crash_log.splitlines() if not line.startswith('ERROR')]:
211 return (stderr, None)
212 return (stderr, crash_log)
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.
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.
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]
229 crash_logs[test_name] = crash_log
232 def sample_process(self, name, pid):
234 hang_report = self._filesystem.join(self.results_directory(), "%s-%s.sample.txt" % (name, pid))
235 self._executive.run_command([
243 except ScriptError as e:
244 _log.warning('Unable to sample process:' + str(e))
246 def _path_to_helper(self):
247 binary_name = 'LayoutTestHelper'
248 return self._build_path(binary_name)
250 def start_helper(self):
251 helper_path = self._path_to_helper()
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")
260 def stop_helper(self):
262 _log.debug("Stopping LayoutTestHelper")
264 self._helper.stdin.write("x\n")
265 self._helper.stdin.close()
268 _log.debug("IOError raised while stopping helper: %s" % str(e))
271 def make_command(self):
272 return self.xcrun_find('make', '/usr/bin/make')
274 def nm_command(self):
275 return self.xcrun_find('nm', 'nm')
277 def xcrun_find(self, command, fallback):
279 return self._executive.run_command(['xcrun', '-find', command]).rstrip()
281 _log.warn("xcrun failed; falling back to '%s'." % fallback)