1 # Copyright (C) 2010 Google Inc. All rights reserved.
2 # Copyright (C) 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.
37 from webkitpy.common.system.crashlogs import CrashLogs
38 from webkitpy.common.system.systemhost import SystemHost
39 from webkitpy.common.system.executive import ScriptError, Executive
40 from webkitpy.common.system.path import abspath_to_uri, cygpath
41 from webkitpy.port.apple import ApplePort
44 _log = logging.getLogger(__name__)
47 class WinPort(ApplePort):
50 VERSION_FALLBACK_ORDER = ["win-xp", "win-vista", "win-7sp0", "win"]
52 ARCHITECTURES = ['x86', 'x86_64']
54 CRASH_LOG_PREFIX = "CrashLog"
56 POST_MORTEM_DEBUGGER_KEY = "/%s/SOFTWARE/Microsoft/Windows NT/CurrentVersion/AeDebug/%s"
58 WINDOWS_ERROR_REPORTING_KEY = "/%s/SOFTWARE/Microsoft/Windows/Windows Error Reporting/%s"
60 previous_debugger_values = {}
62 previous_error_reporting_values = {}
64 def do_text_results_differ(self, expected_text, actual_text):
65 # Sanity was restored in WK2, so we don't need this hack there.
66 if self.get_option('webkit_test_runner'):
67 return ApplePort.do_text_results_differ(self, expected_text, actual_text)
69 # This is a hack (which dates back to ORWT).
70 # Windows does not have an EDITING DELEGATE, so we strip any EDITING DELEGATE
71 # messages to make more of the tests pass.
72 # It's possible more of the ports might want this and this could move down into WebKitPort.
73 delegate_regexp = re.compile("^EDITING DELEGATE: .*?\n", re.MULTILINE)
74 expected_text = delegate_regexp.sub("", expected_text)
75 actual_text = delegate_regexp.sub("", actual_text)
76 return expected_text != actual_text
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 # FIXME: The AppleWin port falls back to AppleMac for some results. Eventually we'll have a shared 'apple' port.
85 if self.get_option('webkit_test_runner'):
86 fallback_names.insert(0, 'win-wk2')
87 fallback_names.append('mac-wk2')
88 # Note we do not add 'wk2' here, even though it's included in _skipped_search_paths().
89 # FIXME: Perhaps we should get this list from MacPort?
90 fallback_names.extend(['mac-mountainlion', 'mac'])
91 return map(self._webkit_baseline_path, fallback_names)
93 def operating_system(self):
96 def default_child_processes(self):
99 def show_results_html_file(self, results_filename):
100 self._run_script('run-safari', [abspath_to_uri(SystemHost().platform, results_filename)])
102 def _runtime_feature_list(self):
103 supported_features_command = [self._path_to_driver(), '--print-supported-features']
105 output = self._executive.run_command(supported_features_command, error_handler=Executive.ignore_error)
107 _log.warn("Exception running driver: %s, %s. Driver must be built before calling WebKitPort.test_expectations()." % (supported_features_command, e))
110 # Note: win/DumpRenderTree.cpp does not print a leading space before the features_string.
111 match_object = re.match("SupportedFeatures:\s*(?P<features_string>.*)\s*", output)
114 return match_object.group('features_string').split(' ')
116 # Note: These are based on the stock XAMPP locations for these files.
117 def _uses_apache(self):
120 def _path_to_apache(self):
121 httpdPath = "C:/xampp/apache/bin/httpd.exe"
122 if self._filesystem.exists(httpdPath):
124 _log.error("Could not find apache. Not installed or unknown path.")
127 def _path_to_lighttpd(self):
128 return "/usr/sbin/lighttpd"
130 def _path_to_lighttpd_modules(self):
131 return "/usr/lib/lighttpd"
133 def _path_to_lighttpd_php(self):
134 return "/usr/bin/php-cgi"
136 def _driver_tempdir_for_environment(self):
137 return cygpath(self._driver_tempdir())
139 def test_search_path(self):
140 test_fallback_names = [path for path in self.baseline_search_path() if not path.startswith(self._webkit_baseline_path('mac'))]
141 return map(self._webkit_baseline_path, test_fallback_names)
143 def _ntsd_location(self):
144 if 'PROGRAMFILES' not in os.environ:
146 possible_paths = [self._filesystem.join(os.environ['PROGRAMFILES'], "Windows Kits", "8.1", "Debuggers", "x86", "ntsd.exe"),
147 self._filesystem.join(os.environ['PROGRAMFILES'], "Windows Kits", "8.1", "Debuggers", "x64", "ntsd.exe"),
148 self._filesystem.join(os.environ['PROGRAMFILES'], "Windows Kits", "8.0", "Debuggers", "x86", "ntsd.exe"),
149 self._filesystem.join(os.environ['PROGRAMFILES'], "Windows Kits", "8.0", "Debuggers", "x64", "ntsd.exe"),
150 self._filesystem.join(os.environ['PROGRAMFILES'], "Debugging Tools for Windows (x86)", "ntsd.exe"),
151 self._filesystem.join(os.environ['ProgramW6432'], "Debugging Tools for Windows (x64)", "ntsd.exe"),
152 self._filesystem.join(os.environ['SYSTEMROOT'], "system32", "ntsd.exe")]
153 for path in possible_paths:
154 expanded_path = self._filesystem.expanduser(path)
155 if self._filesystem.exists(expanded_path):
156 _log.debug("Using ntsd located in '%s'" % path)
160 def create_debugger_command_file(self):
161 debugger_temp_directory = str(self._filesystem.mkdtemp())
162 command_file = self._filesystem.join(debugger_temp_directory, "debugger-commands.txt")
163 commands = ''.join(['.logopen /t "%s\\%s.txt"\n' % (cygpath(self.results_directory()), self.CRASH_LOG_PREFIX),
164 '.srcpath "%s"\n' % cygpath(self._webkit_finder.webkit_base()),
168 self._filesystem.write_text_file(command_file, commands)
171 def read_registry_string(self, reg_path, arch, root, key):
172 registry_key = reg_path % (root, key)
173 read_registry_command = ["regtool", arch, "get", registry_key]
174 value = self._executive.run_command(read_registry_command, error_handler=Executive.ignore_error)
175 return value.rstrip()
177 def write_registry_value(self, reg_path, arch, root, key, regType, value):
178 registry_key = reg_path % (root, key)
180 _log.debug("Writing to %s" % registry_key)
182 set_reg_value_command = ["regtool", arch, "set", regType, str(registry_key), str(value)]
183 rc = self._executive.run_command(set_reg_value_command, return_exit_code=True)
185 add_reg_value_command = ["regtool", arch, "add", regType, str(registry_key)]
186 rc = self._executive.run_command(add_reg_value_command, return_exit_code=True)
188 rc = self._executive.run_command(set_reg_value_command, return_exit_code=True)
190 _log.warn("Error setting (%s) %s\key: %s to value: %s. Error=%s." % (arch, root, key, value, str(rc)))
191 _log.warn("You many need to adjust permissions on the %s key." % registry_key)
194 # On Windows Vista/7 with UAC enabled, regtool will fail to modify the registry, but will still
195 # return a successful exit code. So we double-check here that the value we tried to write to the
196 # registry was really written.
197 if self.read_registry_string(reg_path, arch, root, key) != str(value):
198 _log.warn("Regtool reported success, but value of key %s did not change." % key)
199 _log.warn("You many need to adjust permissions on the %s key." % registry_key)
204 def write_registry_string(self, reg_path, arch, root, key, value):
205 return self.write_registry_value(reg_path, arch, root, key, "-s", value)
207 def setup_crash_log_saving(self):
208 if '_NT_SYMBOL_PATH' not in os.environ:
209 _log.warning("The _NT_SYMBOL_PATH environment variable is not set. Using Microsoft Symbol Server.")
210 os.environ['_NT_SYMBOL_PATH'] = 'SRV*http://msdl.microsoft.com/download/symbols'
211 ntsd_path = self._ntsd_location()
213 _log.warning("Can't find ntsd.exe. Crash logs will not be saved.")
215 # If we used -c (instead of -cf) we could pass the commands directly on the command line. But
216 # when the commands include multiple quoted paths (e.g., for .logopen and .srcpath), Windows
217 # fails to invoke the post-mortem debugger at all (perhaps due to a bug in Windows's command
218 # line parsing). So we save the commands to a file instead and tell the debugger to execute them
220 command_file = self.create_debugger_command_file()
223 debugger_options = '"{0}" -p %ld -e %ld -g -noio -lines -cf "{1}"'.format(cygpath(ntsd_path), cygpath(command_file))
224 registry_settings = {'Debugger': debugger_options, 'Auto': "1"}
225 for key in registry_settings:
226 for arch in ["--wow32", "--wow64"]:
227 self.previous_debugger_values[(arch, "HKLM", key)] = self.read_registry_string(self.POST_MORTEM_DEBUGGER_KEY, arch, "HKLM", key)
228 self.write_registry_string(self.POST_MORTEM_DEBUGGER_KEY, arch, "HKLM", key, registry_settings[key])
230 def restore_crash_log_saving(self):
231 for key in self.previous_debugger_values:
232 self.write_registry_string(self.POST_MORTEM_DEBUGGER_KEY, key[0], key[1], key[2], self.previous_debugger_values[key])
234 def prevent_error_dialogs(self):
235 registry_settings = {'DontShowUI': 1, 'Disabled': 1}
236 for key in registry_settings:
237 for root in ["HKLM", "HKCU"]:
238 for arch in ["--wow32", "--wow64"]:
239 self.previous_error_reporting_values[(arch, root, key)] = self.read_registry_string(self.WINDOWS_ERROR_REPORTING_KEY, arch, root, key)
240 self.write_registry_value(self.WINDOWS_ERROR_REPORTING_KEY, arch, root, key, "-d", registry_settings[key])
242 def allow_error_dialogs(self):
243 for key in self.previous_error_reporting_values:
244 self.write_registry_value(self.WINDOWS_ERROR_REPORTING_KEY, key[0], key[1], key[2], "-d", self.previous_error_reporting_values[key])
246 def delete_sem_locks(self):
247 os.system("rm -rf /dev/shm/sem.*")
249 def setup_test_run(self):
250 atexit.register(self.restore_crash_log_saving)
251 self.setup_crash_log_saving()
252 self.prevent_error_dialogs()
253 self.delete_sem_locks()
254 super(WinPort, self).setup_test_run()
256 def clean_up_test_run(self):
257 self.allow_error_dialogs()
258 self.restore_crash_log_saving()
259 super(WinPort, self).clean_up_test_run()
261 def _get_crash_log(self, name, pid, stdout, stderr, newer_than, time_fn=None, sleep_fn=None, wait_for_log=True):
262 # Note that we do slow-spin here and wait, since it appears the time
263 # ReportCrash takes to actually write and flush the file varies when there are
264 # lots of simultaneous crashes going on.
265 # FIXME: Should most of this be moved into CrashLogs()?
266 time_fn = time_fn or time.time
267 sleep_fn = sleep_fn or time.sleep
269 crash_logs = CrashLogs(self.host, self.results_directory())
271 # FIXME: delete this after we're sure this code is working ...
272 _log.debug('looking for crash log for %s:%s' % (name, str(pid)))
273 deadline = now + 5 * int(self.get_option('child_processes', 1))
274 while not crash_log and now <= deadline:
275 # If the system_pid hasn't been determined yet, just try with the passed in pid. We'll be checking again later
276 system_pid = self._executive.pid_to_system_pid.get(pid)
277 if system_pid == None:
278 break # We haven't mapped cygwin pid->win pid yet
279 crash_log = crash_logs.find_newest_log(name, system_pid, include_errors=True, newer_than=newer_than)
282 if not crash_log or not [line for line in crash_log.splitlines() if line.startswith('quit:')]:
287 return (stderr, None)
288 return (stderr, crash_log)
290 def look_for_new_crash_logs(self, crashed_processes, start_time):
291 """Since crash logs can take a long time to be written out if the system is
292 under stress do a second pass at the end of the test run.
294 crashes: test_name -> pid, process_name tuple of crashed process
295 start_time: time the tests started at. We're looking for crash
296 logs after that time.
299 for (test_name, process_name, pid) in crashed_processes:
300 # Passing None for output. This is a second pass after the test finished so
301 # if the output had any logging we would have already collected it.
302 crash_log = self._get_crash_log(process_name, pid, None, None, start_time, wait_for_log=False)[1]
304 crash_logs[test_name] = crash_log
307 def find_system_pid(self, name, pid):
308 system_pid = int(pid)
309 # Windows and Cygwin PIDs are not the same. We need to find the Windows
310 # PID for our Cygwin process so we can match it later to any crash
311 # files we end up creating (which will be tagged with the Windows PID)
312 ps_process = self._executive.run_command(['ps', '-e'], error_handler=Executive.ignore_error)
313 for line in ps_process.splitlines():
314 tokens = line.strip().split()
316 cpid, ppid, pgid, winpid, tty, uid, stime, process_name = tokens
317 if process_name.endswith(name):
318 self._executive.pid_to_system_pid[int(cpid)] = int(winpid)
319 if int(pid) == int(cpid):
320 system_pid = int(winpid)
322 except ValueError, e: