1# Copyright (C) 2010 Google 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 are 5# met: 6# 7# * Redistributions of source code must retain the above copyright 8# notice, this list of conditions and the following disclaimer. 9# * Redistributions in binary form must reproduce the above 10# copyright notice, this list of conditions and the following disclaimer 11# in the documentation and/or other materials provided with the 12# distribution. 13# * Neither the name of Google Inc. nor the names of its 14# contributors may be used to endorse or promote products derived from 15# this software without specific prior written permission. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29"""Windows implementation of the Port interface.""" 30 31import errno 32import os 33import logging 34 35try: 36 import _winreg 37except ImportError as e: 38 _winreg = None 39 WindowsError = Exception # this shuts up pylint. 40 41from webkitpy.layout_tests.breakpad.dump_reader_win import DumpReaderWin 42from webkitpy.layout_tests.models import test_run_results 43from webkitpy.layout_tests.port import base 44from webkitpy.layout_tests.servers import crash_service 45 46 47_log = logging.getLogger(__name__) 48 49 50class WinPort(base.Port): 51 port_name = 'win' 52 53 # FIXME: Figure out how to unify this with base.TestConfiguration.all_systems()? 54 SUPPORTED_VERSIONS = ('xp', 'win7') 55 56 FALLBACK_PATHS = { 'win7': [ 'win' ]} 57 FALLBACK_PATHS['xp'] = ['win-xp'] + FALLBACK_PATHS['win7'] 58 59 DEFAULT_BUILD_DIRECTORIES = ('build', 'out') 60 61 BUILD_REQUIREMENTS_URL = 'http://www.chromium.org/developers/how-tos/build-instructions-windows' 62 63 @classmethod 64 def determine_full_port_name(cls, host, options, port_name): 65 if port_name.endswith('win'): 66 assert host.platform.is_win() 67 # We don't maintain separate baselines for vista, so we pretend it is win7. 68 if host.platform.os_version in ('vista', '7sp0', '7sp1', 'future'): 69 version = 'win7' 70 else: 71 version = host.platform.os_version 72 port_name = port_name + '-' + version 73 return port_name 74 75 def __init__(self, host, port_name, **kwargs): 76 super(WinPort, self).__init__(host, port_name, **kwargs) 77 self._version = port_name[port_name.index('win-') + len('win-'):] 78 assert self._version in self.SUPPORTED_VERSIONS, "%s is not in %s" % (self._version, self.SUPPORTED_VERSIONS) 79 if not self.get_option('disable_breakpad'): 80 self._dump_reader = DumpReaderWin(host, self._build_path()) 81 self._crash_service = None 82 self._crash_service_available = None 83 84 def additional_drt_flag(self): 85 flags = super(WinPort, self).additional_drt_flag() 86 flags += ['--enable-direct-write'] 87 if not self.get_option('disable_breakpad'): 88 flags += ['--enable-crash-reporter', '--crash-dumps-dir=%s' % self._dump_reader.crash_dumps_directory()] 89 return flags 90 91 def check_httpd(self): 92 res = super(WinPort, self).check_httpd() 93 if self.uses_apache(): 94 # In order to run CGI scripts on Win32 that use unix shebang lines, we need to 95 # create entries in the registry that remap the extensions (.pl and .cgi) to the 96 # appropriate Win32 paths. The command line arguments must match the command 97 # line arguments in the shebang line exactly. 98 if _winreg: 99 res = self._check_reg(r'.cgi\Shell\ExecCGI\Command') and res 100 res = self._check_reg(r'.pl\Shell\ExecCGI\Command') and res 101 else: 102 _log.warning("Could not check the registry; http may not work correctly.") 103 104 return res 105 106 def _check_reg(self, sub_key): 107 # see comments in check_httpd(), above, for why this routine exists and what it's doing. 108 try: 109 # Note that we HKCR is a union of HKLM and HKCR (with the latter 110 # overridding the former), so reading from HKCR ensures that we get 111 # the value if it is set in either place. See als comments below. 112 hkey = _winreg.OpenKey(_winreg.HKEY_CLASSES_ROOT, sub_key) 113 args = _winreg.QueryValue(hkey, '').split() 114 _winreg.CloseKey(hkey) 115 116 # In order to keep multiple checkouts from stepping on each other, we simply check that an 117 # existing entry points to a valid path and has the right command line. 118 if len(args) == 2 and self._filesystem.exists(args[0]) and args[0].endswith('perl.exe') and args[1] == '-wT': 119 return True 120 except WindowsError, e: 121 if e.errno != errno.ENOENT: 122 raise e 123 # The key simply probably doesn't exist. 124 pass 125 126 # Note that we write to HKCU so that we don't need privileged access 127 # to the registry, and that will get reflected in HKCR when it is read, above. 128 cmdline = self.path_from_chromium_base('third_party', 'perl', 'perl', 'bin', 'perl.exe') + ' -wT' 129 hkey = _winreg.CreateKeyEx(_winreg.HKEY_CURRENT_USER, 'Software\\Classes\\' + sub_key, 0, _winreg.KEY_WRITE) 130 _winreg.SetValue(hkey, '', _winreg.REG_SZ, cmdline) 131 _winreg.CloseKey(hkey) 132 return True 133 134 def setup_test_run(self): 135 super(WinPort, self).setup_test_run() 136 137 if not self.get_option('disable_breakpad'): 138 assert not self._crash_service, 'Already running a crash service' 139 if self._crash_service_available == None: 140 self._crash_service_available = self._check_crash_service_available() 141 if not self._crash_service_available: 142 return 143 service = crash_service.CrashService(self, self._dump_reader.crash_dumps_directory()) 144 service.start() 145 self._crash_service = service 146 147 def clean_up_test_run(self): 148 super(WinPort, self).clean_up_test_run() 149 150 if self._crash_service: 151 self._crash_service.stop() 152 self._crash_service = None 153 154 def setup_environ_for_server(self, server_name=None): 155 env = super(WinPort, self).setup_environ_for_server(server_name) 156 157 # FIXME: This is a temporary hack to get the cr-win bot online until 158 # someone from the cr-win port can take a look. 159 apache_envvars = ['SYSTEMDRIVE', 'SYSTEMROOT', 'TEMP', 'TMP'] 160 for key, value in os.environ.items(): 161 if key not in env and key in apache_envvars: 162 env[key] = value 163 164 # Put the cygwin directory first in the path to find cygwin1.dll. 165 env["PATH"] = "%s;%s" % (self.path_from_chromium_base("third_party", "cygwin", "bin"), env["PATH"]) 166 # Configure the cygwin directory so that pywebsocket finds proper 167 # python executable to run cgi program. 168 env["CYGWIN_PATH"] = self.path_from_chromium_base("third_party", "cygwin", "bin") 169 if self.get_option('register_cygwin'): 170 setup_mount = self.path_from_chromium_base("third_party", "cygwin", "setup_mount.bat") 171 self._executive.run_command([setup_mount]) # Paths are all absolute, so this does not require a cwd. 172 return env 173 174 def _modules_to_search_for_symbols(self): 175 # FIXME: we should return the path to the ffmpeg equivalents to detect if we have the mp3 and aac codecs installed. 176 # See https://bugs.webkit.org/show_bug.cgi?id=89706. 177 return [] 178 179 def check_build(self, needs_http, printer): 180 result = super(WinPort, self).check_build(needs_http, printer) 181 182 self._crash_service_available = self._check_crash_service_available() 183 if not self._crash_service_available: 184 result = test_run_results.UNEXPECTED_ERROR_EXIT_STATUS 185 186 if result: 187 _log.error('For complete Windows build requirements, please see:') 188 _log.error('') 189 _log.error(' http://dev.chromium.org/developers/how-tos/build-instructions-windows') 190 return result 191 192 def operating_system(self): 193 return 'win' 194 195 def relative_test_filename(self, filename): 196 path = filename[len(self.layout_tests_dir()) + 1:] 197 return path.replace('\\', '/') 198 199 def uses_apache(self): 200 val = self.get_option('use_apache') 201 if val is None: 202 return True 203 return val 204 205 def path_to_apache(self): 206 return self.path_from_chromium_base('third_party', 'apache-win32', 'bin', 'httpd.exe') 207 208 def path_to_apache_config_file(self): 209 return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', 'win-httpd.conf') 210 211 # 212 # PROTECTED ROUTINES 213 # 214 215 def _path_to_driver(self, configuration=None): 216 binary_name = '%s.exe' % self.driver_name() 217 return self._build_path_with_configuration(configuration, binary_name) 218 219 def _path_to_crash_service(self): 220 binary_name = 'content_shell_crash_service.exe' 221 return self._build_path(binary_name) 222 223 def _path_to_image_diff(self): 224 binary_name = 'image_diff.exe' 225 return self._build_path(binary_name) 226 227 def _path_to_wdiff(self): 228 return self.path_from_chromium_base('third_party', 'cygwin', 'bin', 'wdiff.exe') 229 230 def _check_crash_service_available(self): 231 """Checks whether the crash service binary is present.""" 232 result = self._check_file_exists(self._path_to_crash_service(), "content_shell_crash_service.exe") 233 if not result: 234 _log.error(" Could not find crash service, unexpected crashes won't be symbolized.") 235 _log.error(' Did you build the target blink_tests?') 236 _log.error('') 237 return result 238 239 def look_for_new_crash_logs(self, crashed_processes, start_time): 240 if self.get_option('disable_breakpad'): 241 return None 242 return self._dump_reader.look_for_new_crash_logs(crashed_processes, start_time) 243 244 def clobber_old_port_specific_results(self): 245 if not self.get_option('disable_breakpad'): 246 self._dump_reader.clobber_old_results() 247