summaryrefslogtreecommitdiff
path: root/Tools/Scripts/webkitpy/port/win.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/Scripts/webkitpy/port/win.py')
-rw-r--r--Tools/Scripts/webkitpy/port/win.py284
1 files changed, 284 insertions, 0 deletions
diff --git a/Tools/Scripts/webkitpy/port/win.py b/Tools/Scripts/webkitpy/port/win.py
new file mode 100644
index 000000000..18175d60b
--- /dev/null
+++ b/Tools/Scripts/webkitpy/port/win.py
@@ -0,0 +1,284 @@
+# Copyright (C) 2010 Google Inc. All rights reserved.
+# Copyright (C) 2013 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the Google name nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import atexit
+import os
+import logging
+import re
+import sys
+import time
+
+from webkitpy.common.system.crashlogs import CrashLogs
+from webkitpy.common.system.systemhost import SystemHost
+from webkitpy.common.system.executive import ScriptError, Executive
+from webkitpy.common.system.path import abspath_to_uri, cygpath
+from webkitpy.port.apple import ApplePort
+
+
+_log = logging.getLogger(__name__)
+
+
+class WinPort(ApplePort):
+ port_name = "win"
+
+ VERSION_FALLBACK_ORDER = ["win-xp", "win-vista", "win-7sp0", "win"]
+
+ ARCHITECTURES = ['x86']
+
+ CRASH_LOG_PREFIX = "CrashLog"
+
+ POST_MORTEM_DEBUGGER_KEY = "/HKLM/SOFTWARE/Microsoft/Windows NT/CurrentVersion/AeDebug/%s"
+
+ previous_debugger_values = {}
+
+ def do_text_results_differ(self, expected_text, actual_text):
+ # Sanity was restored in WK2, so we don't need this hack there.
+ if self.get_option('webkit_test_runner'):
+ return ApplePort.do_text_results_differ(self, expected_text, actual_text)
+
+ # This is a hack (which dates back to ORWT).
+ # Windows does not have an EDITING DELEGATE, so we strip any EDITING DELEGATE
+ # messages to make more of the tests pass.
+ # It's possible more of the ports might want this and this could move down into WebKitPort.
+ delegate_regexp = re.compile("^EDITING DELEGATE: .*?\n", re.MULTILINE)
+ expected_text = delegate_regexp.sub("", expected_text)
+ actual_text = delegate_regexp.sub("", actual_text)
+ return expected_text != actual_text
+
+ def default_baseline_search_path(self):
+ name = self._name.replace('-wk2', '')
+ if name.endswith(self.FUTURE_VERSION):
+ fallback_names = [self.port_name]
+ else:
+ fallback_names = self.VERSION_FALLBACK_ORDER[self.VERSION_FALLBACK_ORDER.index(name):-1] + [self.port_name]
+ # FIXME: The AppleWin port falls back to AppleMac for some results. Eventually we'll have a shared 'apple' port.
+ if self.get_option('webkit_test_runner'):
+ fallback_names.insert(0, 'win-wk2')
+ fallback_names.append('mac-wk2')
+ # Note we do not add 'wk2' here, even though it's included in _skipped_search_paths().
+ # FIXME: Perhaps we should get this list from MacPort?
+ fallback_names.extend(['mac-lion', 'mac'])
+ return map(self._webkit_baseline_path, fallback_names)
+
+ def operating_system(self):
+ return 'win'
+
+ def show_results_html_file(self, results_filename):
+ self._run_script('run-safari', [abspath_to_uri(SystemHost().platform, results_filename)])
+
+ # FIXME: webkitperl/httpd.pm installs /usr/lib/apache/libphp4.dll on cycwin automatically
+ # as part of running old-run-webkit-tests. That's bad design, but we may need some similar hack.
+ # We might use setup_environ_for_server for such a hack (or modify apache_http_server.py).
+
+ def _runtime_feature_list(self):
+ supported_features_command = [self._path_to_driver(), '--print-supported-features']
+ try:
+ output = self._executive.run_command(supported_features_command, error_handler=Executive.ignore_error)
+ except OSError, e:
+ _log.warn("Exception running driver: %s, %s. Driver must be built before calling WebKitPort.test_expectations()." % (supported_features_command, e))
+ return None
+
+ # Note: win/DumpRenderTree.cpp does not print a leading space before the features_string.
+ match_object = re.match("SupportedFeatures:\s*(?P<features_string>.*)\s*", output)
+ if not match_object:
+ return None
+ return match_object.group('features_string').split(' ')
+
+ # Note: These are based on the stock Cygwin locations for these files.
+ def _uses_apache(self):
+ return False
+
+ def _path_to_lighttpd(self):
+ return "/usr/sbin/lighttpd"
+
+ def _path_to_lighttpd_modules(self):
+ return "/usr/lib/lighttpd"
+
+ def _path_to_lighttpd_php(self):
+ return "/usr/bin/php-cgi"
+
+ def _driver_tempdir_for_environment(self):
+ return cygpath(self._driver_tempdir())
+
+ def test_search_path(self):
+ test_fallback_names = [path for path in self.baseline_search_path() if not path.startswith(self._webkit_baseline_path('mac'))]
+ return map(self._webkit_baseline_path, test_fallback_names)
+
+ def _ntsd_location(self):
+ possible_paths = [self._filesystem.join(os.environ['PROGRAMFILES'], "Windows Kits", "8.0", "Debuggers", "x86", "ntsd.exe"),
+ self._filesystem.join(os.environ['PROGRAMFILES'], "Windows Kits", "8.0", "Debuggers", "x64", "ntsd.exe"),
+ self._filesystem.join(os.environ['PROGRAMFILES'], "Debugging Tools for Windows (x86)", "ntsd.exe"),
+ self._filesystem.join(os.environ['ProgramW6432'], "Debugging Tools for Windows (x64)", "ntsd.exe"),
+ self._filesystem.join(os.environ['SYSTEMROOT'], "system32", "ntsd.exe")]
+ for path in possible_paths:
+ expanded_path = self._filesystem.expanduser(path)
+ if self._filesystem.exists(expanded_path):
+ _log.debug("Using ntsd located in '%s'" % path)
+ return expanded_path
+ return None
+
+ def create_debugger_command_file(self):
+ debugger_temp_directory = str(self._filesystem.mkdtemp())
+ command_file = self._filesystem.join(debugger_temp_directory, "debugger-commands.txt")
+ commands = ''.join(['.logopen /t "%s\\%s.txt"\n' % (cygpath(self.results_directory()), self.CRASH_LOG_PREFIX),
+ '.srcpath "%s"\n' % cygpath(self._webkit_finder.webkit_base()),
+ '!analyze -vv\n',
+ '~*kpn\n',
+ 'q\n'])
+ self._filesystem.write_text_file(command_file, commands)
+ return command_file
+
+ def read_registry_string(self, key):
+ registry_key = self.POST_MORTEM_DEBUGGER_KEY % key
+ read_registry_command = ["regtool", "--wow32", "get", registry_key]
+ value = self._executive.run_command(read_registry_command, error_handler=Executive.ignore_error)
+ return value.rstrip()
+
+ def write_registry_string(self, key, value):
+ registry_key = self.POST_MORTEM_DEBUGGER_KEY % key
+ set_reg_value_command = ["regtool", "--wow32", "set", "-s", str(registry_key), str(value)]
+ rc = self._executive.run_command(set_reg_value_command, return_exit_code=True)
+ if rc == 2:
+ add_reg_value_command = ["regtool", "--wow32", "add", "-s", str(registry_key)]
+ rc = self._executive.run_command(add_reg_value_command, return_exit_code=True)
+ if rc == 0:
+ rc = self._executive.run_command(set_reg_value_command, return_exit_code=True)
+ if rc:
+ _log.warn("Error setting key: %s to value %s. Error=%ld." % (key, value, rc))
+ return False
+
+ # On Windows Vista/7 with UAC enabled, regtool will fail to modify the registry, but will still
+ # return a successful exit code. So we double-check here that the value we tried to write to the
+ # registry was really written.
+ if self.read_registry_string(key) != value:
+ _log.warn("Regtool reported success, but value of key %s did not change." % key)
+ return False
+
+ return True
+
+ def setup_crash_log_saving(self):
+ if '_NT_SYMBOL_PATH' not in os.environ:
+ _log.warning("The _NT_SYMBOL_PATH environment variable is not set. Crash logs will not be saved.")
+ return None
+ ntsd_path = self._ntsd_location()
+ if not ntsd_path:
+ _log.warning("Can't find ntsd.exe. Crash logs will not be saved.")
+ return None
+ # If we used -c (instead of -cf) we could pass the commands directly on the command line. But
+ # when the commands include multiple quoted paths (e.g., for .logopen and .srcpath), Windows
+ # fails to invoke the post-mortem debugger at all (perhaps due to a bug in Windows's command
+ # line parsing). So we save the commands to a file instead and tell the debugger to execute them
+ # using -cf.
+ command_file = self.create_debugger_command_file()
+ if not command_file:
+ return None
+ debugger_options = '"{0}" -p %ld -e %ld -g -noio -lines -cf "{1}"'.format(cygpath(ntsd_path), cygpath(command_file))
+ registry_settings = {'Debugger': debugger_options, 'Auto': "1"}
+ for key in registry_settings:
+ self.previous_debugger_values[key] = self.read_registry_string(key)
+ self.write_registry_string(key, registry_settings[key])
+
+ def restore_crash_log_saving(self):
+ for key in self.previous_debugger_values:
+ self.write_registry_string(key, self.previous_debugger_values[key])
+
+ def setup_test_run(self):
+ atexit.register(self.restore_crash_log_saving)
+ self.setup_crash_log_saving()
+ super(WinPort, self).setup_test_run()
+
+ def clean_up_test_run(self):
+ self.restore_crash_log_saving()
+ super(WinPort, self).clean_up_test_run()
+
+ def _get_crash_log(self, name, pid, stdout, stderr, newer_than, time_fn=None, sleep_fn=None, wait_for_log=True):
+ # Note that we do slow-spin here and wait, since it appears the time
+ # ReportCrash takes to actually write and flush the file varies when there are
+ # lots of simultaneous crashes going on.
+ # FIXME: Should most of this be moved into CrashLogs()?
+ time_fn = time_fn or time.time
+ sleep_fn = sleep_fn or time.sleep
+ crash_log = ''
+ crash_logs = CrashLogs(self.host, self.results_directory())
+ now = time_fn()
+ # FIXME: delete this after we're sure this code is working ...
+ _log.debug('looking for crash log for %s:%s' % (name, str(pid)))
+ deadline = now + 5 * int(self.get_option('child_processes', 1))
+ while not crash_log and now <= deadline:
+ # If the system_pid hasn't been determined yet, just try with the passed in pid. We'll be checking again later
+ system_pid = self._executive.pid_to_system_pid.get(pid)
+ if system_pid == None:
+ break # We haven't mapped cygwin pid->win pid yet
+ crash_log = crash_logs.find_newest_log(name, system_pid, include_errors=True, newer_than=newer_than)
+ if not wait_for_log:
+ break
+ if not crash_log or not [line for line in crash_log.splitlines() if line.startswith('quit:')]:
+ sleep_fn(0.1)
+ now = time_fn()
+
+ if not crash_log:
+ return (stderr, None)
+ return (stderr, crash_log)
+
+ def look_for_new_crash_logs(self, crashed_processes, start_time):
+ """Since crash logs can take a long time to be written out if the system is
+ under stress do a second pass at the end of the test run.
+
+ crashes: test_name -> pid, process_name tuple of crashed process
+ start_time: time the tests started at. We're looking for crash
+ logs after that time.
+ """
+ crash_logs = {}
+ for (test_name, process_name, pid) in crashed_processes:
+ # Passing None for output. This is a second pass after the test finished so
+ # if the output had any logging we would have already collected it.
+ crash_log = self._get_crash_log(process_name, pid, None, None, start_time, wait_for_log=False)[1]
+ if crash_log:
+ crash_logs[test_name] = crash_log
+ return crash_logs
+
+ def find_system_pid(self, name, pid):
+ system_pid = int(pid)
+ # Windows and Cygwin PIDs are not the same. We need to find the Windows
+ # PID for our Cygwin process so we can match it later to any crash
+ # files we end up creating (which will be tagged with the Windows PID)
+ ps_process = self._executive.run_command(['ps', '-e'], error_handler=Executive.ignore_error)
+ for line in ps_process.splitlines():
+ tokens = line.strip().split()
+ try:
+ cpid, ppid, pgid, winpid, tty, uid, stime, process_name = tokens
+ if process_name.endswith(name):
+ self._executive.pid_to_system_pid[int(cpid)] = int(winpid)
+ if int(pid) == int(cpid):
+ system_pid = int(winpid)
+ break
+ except ValueError, e:
+ pass
+
+ return system_pid