Use simctl instead of LayoutTestRelay
[WebKit.git] / Tools / Scripts / webkitpy / port / darwin.py
1 # Copyright (C) 2014-2016 Apple Inc. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions
5 # are met:
6 # 1.  Redistributions of source code must retain the above copyright
7 #     notice, this list of conditions and the following disclaimer.
8 # 2.  Redistributions in binary form must reproduce the above copyright
9 #     notice, this list of conditions and the following disclaimer in the
10 #     documentation and/or other materials provided with the distribution.
11 #
12 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
13 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15 # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
16 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
18 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
19 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
20 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
21 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
23 import logging
24 import os
25 import time
26
27 from webkitpy.common.system.crashlogs import CrashLogs
28 from webkitpy.common.system.executive import ScriptError
29 from webkitpy.port.apple import ApplePort
30 from webkitpy.port.leakdetector import LeakDetector
31
32
33 _log = logging.getLogger(__name__)
34
35
36 class DarwinPort(ApplePort):
37
38     SDK = None
39
40     def __init__(self, host, port_name, **kwargs):
41         ApplePort.__init__(self, host, port_name, **kwargs)
42
43         self._leak_detector = LeakDetector(self)
44         if self.get_option("leaks"):
45             # DumpRenderTree slows down noticably if we run more than about 1000 tests in a batch
46             # with MallocStackLogging enabled.
47             self.set_option_default("batch_size", 1000)
48
49     def default_timeout_ms(self):
50         if self.get_option('guard_malloc'):
51             return 350 * 1000
52         return super(DarwinPort, self).default_timeout_ms()
53
54     def _port_specific_expectations_files(self):
55         return list(reversed([self._filesystem.join(self._webkit_baseline_path(p), 'TestExpectations') for p in self.baseline_search_path()]))
56
57     def check_for_leaks(self, process_name, process_pid):
58         if not self.get_option('leaks'):
59             return
60         # We could use http://code.google.com/p/psutil/ to get the process_name from the pid.
61         self._leak_detector.check_for_leaks(process_name, process_pid)
62
63     def print_leaks_summary(self):
64         if not self.get_option('leaks'):
65             return
66         # We're in the manager process, so the leak detector will not have a valid list of leak files.
67         # FIXME: This is a hack, but we don't have a better way to get this information from the workers yet.
68         # FIXME: This will include too many leaks in subsequent runs until the results directory is cleared!
69         leaks_files = self._leak_detector.leaks_files_in_directory(self.results_directory())
70         if not leaks_files:
71             return
72         total_bytes_string, unique_leaks = self._leak_detector.count_total_bytes_and_unique_leaks(leaks_files)
73         total_leaks = self._leak_detector.count_total_leaks(leaks_files)
74         _log.info("%s total leaks found for a total of %s." % (total_leaks, total_bytes_string))
75         _log.info("%s unique leaks found." % unique_leaks)
76
77     def _path_to_webcore_library(self):
78         return self._build_path('WebCore.framework/Versions/A/WebCore')
79
80     def show_results_html_file(self, results_filename):
81         # We don't use self._run_script() because we don't want to wait for the script
82         # to exit and we want the output to show up on stdout in case there are errors
83         # launching the browser.
84         self._executive.popen([self.path_to_script('run-safari')] + self._arguments_for_configuration() + ['--no-saved-state', '-NSOpen', results_filename],
85             cwd=self.webkit_base(), stdout=file(os.devnull), stderr=file(os.devnull))
86
87     def _merge_crash_logs(self, logs, new_logs, crashed_processes):
88         for test, crash_log in new_logs.iteritems():
89             try:
90                 process_name = test.split("-")[0]
91                 pid = int(test.split("-")[1])
92             except IndexError:
93                 continue
94             if not any(entry[1] == process_name and entry[2] == pid for entry in crashed_processes):
95                 # if this is a new crash, then append the logs
96                 logs[test] = crash_log
97         return logs
98
99     def _look_for_all_crash_logs_in_log_dir(self, newer_than):
100         crash_log = CrashLogs(self.host)
101         return crash_log.find_all_logs(include_errors=True, newer_than=newer_than)
102
103     def _get_crash_log(self, name, pid, stdout, stderr, newer_than, time_fn=None, sleep_fn=None, wait_for_log=True):
104         return None
105
106     def look_for_new_crash_logs(self, crashed_processes, start_time):
107         """Since crash logs can take a long time to be written out if the system is
108            under stress do a second pass at the end of the test run.
109
110            crashes: test_name -> pid, process_name tuple of crashed process
111            start_time: time the tests started at.  We're looking for crash
112                logs after that time.
113         """
114         crash_logs = {}
115         for (test_name, process_name, pid) in crashed_processes:
116             # Passing None for output.  This is a second pass after the test finished so
117             # if the output had any logging we would have already collected it.
118             crash_log = self._get_crash_log(process_name, pid, None, None, start_time, wait_for_log=False)[1]
119             if not crash_log:
120                 continue
121             crash_logs[test_name] = crash_log
122         all_crash_log = self._look_for_all_crash_logs_in_log_dir(start_time)
123         return self._merge_crash_logs(crash_logs, all_crash_log, crashed_processes)
124
125     def sample_process(self, name, pid):
126         exit_status = self._executive.run_command([
127             "/usr/bin/sudo",
128             "-n",
129             "/usr/sbin/spindump",
130             pid,
131             10,
132             10,
133             "-file",
134             self.spindump_file_path(name, pid),
135         ], return_exit_code=True)
136         if exit_status:
137             try:
138                 self._executive.run_command([
139                     "/usr/bin/sample",
140                     pid,
141                     10,
142                     10,
143                     "-file",
144                     self.sample_file_path(name, pid),
145                 ])
146             except ScriptError as e:
147                 _log.warning('Unable to sample process:' + str(e))
148
149     def sample_file_path(self, name, pid):
150         return self._filesystem.join(self.results_directory(), "{0}-{1}-sample.txt".format(name, pid))
151
152     def spindump_file_path(self, name, pid):
153         return self._filesystem.join(self.results_directory(), "{0}-{1}-spindump.txt".format(name, pid))
154
155     def look_for_new_samples(self, unresponsive_processes, start_time):
156         sample_files = {}
157         for (test_name, process_name, pid) in unresponsive_processes:
158             sample_file = self.sample_file_path(process_name, pid)
159             if not self._filesystem.isfile(sample_file):
160                 continue
161             sample_files[test_name] = sample_file
162         return sample_files
163
164     def make_command(self):
165         return self.xcrun_find('make', '/usr/bin/make')
166
167     def nm_command(self):
168         return self.xcrun_find('nm', 'nm')
169
170     def xcrun_find(self, command, fallback=None):
171         fallback = fallback or command
172         try:
173             return self._executive.run_command(['xcrun', '--sdk', self.SDK, '-find', command]).rstrip()
174         except ScriptError:
175             _log.warn("xcrun failed; falling back to '%s'." % fallback)
176             return fallback
177
178     def app_identifier_from_bundle(self, app_bundle):
179         plist_path = self._filesystem.join(app_bundle, 'Info.plist')
180         if not self._filesystem.exists(plist_path):
181             plist_path = self._filesystem.join(app_bundle, 'Contents', 'Info.plist')
182         if not self._filesystem.exists(plist_path):
183             return None
184         return self._executive.run_command(['/usr/libexec/PlistBuddy', '-c', 'Print CFBundleIdentifier', plist_path]).rstrip()