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 31 import errno 32 import os 33 import logging 34 35 try: 36 import _winreg 37 except ImportError as e: 38 _winreg = None 39 WindowsError = Exception # this shuts up pylint. 40 41 from webkitpy.layout_tests.breakpad.dump_reader_win import DumpReaderWin 42 from webkitpy.layout_tests.models import test_run_results 43 from webkitpy.layout_tests.port import base 44 from webkitpy.layout_tests.servers import crash_service 45 46 47 _log = logging.getLogger(__name__) 48 49 50 class 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.get_option('use_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: lighttpd depends on some environment variable we're not whitelisting. 158 # We should add the variable to an explicit whitelist in base.Port. 159 # FIXME: This is a temporary hack to get the cr-win bot online until 160 # someone from the cr-win port can take a look. 161 use_apache = self.get_option('use_apache') 162 apache_envvars = ['SYSTEMDRIVE', 'SYSTEMROOT', 'TEMP', 'TMP'] 163 for key, value in os.environ.items(): 164 if key not in env and (not use_apache or key in apache_envvars): 165 env[key] = value 166 167 if use_apache: 168 return env 169 170 # Put the cygwin directory first in the path to find cygwin1.dll. 171 env["PATH"] = "%s;%s" % (self.path_from_chromium_base("third_party", "cygwin", "bin"), env["PATH"]) 172 # Configure the cygwin directory so that pywebsocket finds proper 173 # python executable to run cgi program. 174 env["CYGWIN_PATH"] = self.path_from_chromium_base("third_party", "cygwin", "bin") 175 if self.get_option('register_cygwin'): 176 setup_mount = self.path_from_chromium_base("third_party", "cygwin", "setup_mount.bat") 177 self._executive.run_command([setup_mount]) # Paths are all absolute, so this does not require a cwd. 178 return env 179 180 def _modules_to_search_for_symbols(self): 181 # FIXME: we should return the path to the ffmpeg equivalents to detect if we have the mp3 and aac codecs installed. 182 # See https://bugs.webkit.org/show_bug.cgi?id=89706. 183 return [] 184 185 def check_build(self, needs_http, printer): 186 result = super(WinPort, self).check_build(needs_http, printer) 187 188 self._crash_service_available = self._check_crash_service_available() 189 if not self._crash_service_available: 190 result = test_run_results.UNEXPECTED_ERROR_EXIT_STATUS 191 192 if result: 193 _log.error('For complete Windows build requirements, please see:') 194 _log.error('') 195 _log.error(' http://dev.chromium.org/developers/how-tos/build-instructions-windows') 196 return result 197 198 def operating_system(self): 199 return 'win' 200 201 def relative_test_filename(self, filename): 202 path = filename[len(self.layout_tests_dir()) + 1:] 203 return path.replace('\\', '/') 204 205 def uses_apache(self): 206 return self.get_option('use_apache') 207 208 def path_to_apache(self): 209 return self.path_from_chromium_base('third_party', 'apache-win32', 'bin', 'httpd.exe') 210 211 def path_to_apache_config_file(self): 212 return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', 'win-httpd.conf') 213 214 def _lighttpd_path(self, *comps): 215 return self.path_from_chromium_base('third_party', 'lighttpd', 'win', *comps) 216 217 def path_to_lighttpd(self): 218 return self._lighttpd_path('LightTPD.exe') 219 220 def path_to_lighttpd_modules(self): 221 return self._lighttpd_path('lib') 222 223 def path_to_lighttpd_php(self): 224 return self._lighttpd_path('php5', 'php-cgi.exe') 225 226 # 227 # PROTECTED ROUTINES 228 # 229 230 def _path_to_driver(self, configuration=None): 231 binary_name = '%s.exe' % self.driver_name() 232 return self._build_path_with_configuration(configuration, binary_name) 233 234 def _path_to_crash_service(self): 235 binary_name = 'content_shell_crash_service.exe' 236 return self._build_path(binary_name) 237 238 def _path_to_image_diff(self): 239 binary_name = 'image_diff.exe' 240 return self._build_path(binary_name) 241 242 def _path_to_wdiff(self): 243 return self.path_from_chromium_base('third_party', 'cygwin', 'bin', 'wdiff.exe') 244 245 def _check_crash_service_available(self): 246 """Checks whether the crash service binary is present.""" 247 result = self._check_file_exists(self._path_to_crash_service(), "content_shell_crash_service.exe") 248 if not result: 249 _log.error(" Could not find crash service, unexpected crashes won't be symbolized.") 250 _log.error(' Did you build the target blink_tests?') 251 _log.error('') 252 return result 253 254 def look_for_new_crash_logs(self, crashed_processes, start_time): 255 if self.get_option('disable_breakpad'): 256 return None 257 return self._dump_reader.look_for_new_crash_logs(crashed_processes, start_time) 258 259 def clobber_old_port_specific_results(self): 260 if not self.get_option('disable_breakpad'): 261 self._dump_reader.clobber_old_results() 262