[Win] run-webkit-tests is failing to run DRT and WTR without --architecture x86_64
[WebKit-https.git] / Tools / Scripts / webkitpy / port / win.py
1 # Copyright (C) 2010 Google Inc. All rights reserved.
2 # Copyright (C) 2013-2019 Apple Inc. All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
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
13 # distribution.
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.
17 #
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.
29
30 import atexit
31 import glob
32 import logging
33 import os
34 import re
35 import sys
36 import time
37
38 from webkitpy.common.system.crashlogs import CrashLogs
39 from webkitpy.common.system.systemhost import SystemHost
40 from webkitpy.common.system.executive import Executive
41 from webkitpy.common.system.path import abspath_to_uri, cygpath
42 from webkitpy.common.version import Version
43 from webkitpy.common.version_name_map import VersionNameMap
44 from webkitpy.port.apple import ApplePort
45 from webkitpy.port.config import apple_additions
46
47 _log = logging.getLogger(__name__)
48
49
50 try:
51     import _winreg
52     import win32com.client
53 except ImportError:
54     try:
55         import winreg as _winreg
56         import win32com.client
57     except ImportError:
58         _log.debug("Not running on native Windows.")
59
60
61 class WinPort(ApplePort):
62     port_name = "win"
63
64     VERSION_MIN = Version(5, 1)
65     VERSION_MAX = Version(10)
66
67     ARCHITECTURES = ['x86', 'x86_64']
68
69     DEFAULT_ARCHITECTURE = 'x86_64'
70
71     CRASH_LOG_PREFIX = "CrashLog"
72
73     if sys.platform.startswith('win'):
74         POST_MORTEM_DEBUGGER_KEY = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug'
75         WOW64_POST_MORTEM_DEBUGGER_KEY = r'SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\AeDebug'
76         WINDOWS_ERROR_REPORTING_KEY = r'SOFTWARE\Microsoft\Windows\Windows Error Reporting'
77         WOW64_WINDOWS_ERROR_REPORTING_KEY = r'SOFTWARE\Wow6432Node\Microsoft\Windows\Windows Error Reporting'
78         _HKLM = _winreg.HKEY_LOCAL_MACHINE
79         _HKCU = _winreg.HKEY_CURRENT_USER
80         _REG_DWORD = _winreg.REG_DWORD
81         _REG_SZ = _winreg.REG_SZ
82     else:
83         POST_MORTEM_DEBUGGER_KEY = "/%s/SOFTWARE/Microsoft/Windows NT/CurrentVersion/AeDebug/%s"
84         WOW64_POST_MORTEM_DEBUGGER_KEY = "/%s/SOFTWARE/Wow6432Node/Microsoft/Windows NT/CurrentVersion/AeDebug/%s"
85         WINDOWS_ERROR_REPORTING_KEY = "/%s/SOFTWARE/Microsoft/Windows/Windows Error Reporting/%s"
86         WOW64_WINDOWS_ERROR_REPORTING_KEY = "/%s/SOFTWARE/Wow6432Node/Microsoft/Windows/Windows Error Reporting/%s"
87         _HKLM = "HKLM"
88         _HKCU = "HKCU"
89         _REG_DWORD = "-d"
90         _REG_SZ = "-s"
91
92     previous_debugger_values = {}
93     previous_wow64_debugger_values = {}
94
95     previous_error_reporting_values = {}
96     previous_wow64_error_reporting_values = {}
97
98     def __init__(self, host, port_name, **kwargs):
99         ApplePort.__init__(self, host, port_name, **kwargs)
100         if len(port_name.split('-')) > 1:
101             self._os_version = VersionNameMap.map(host.platform).from_name(port_name.split('-')[1])[1]
102         else:
103             self._os_version = self.host.platform.os_version
104
105     def do_text_results_differ(self, expected_text, actual_text):
106         # Sanity was restored in WK2, so we don't need this hack there.
107         if self.get_option('webkit_test_runner'):
108             return ApplePort.do_text_results_differ(self, expected_text, actual_text)
109
110         # This is a hack (which dates back to ORWT).
111         # Windows does not have an EDITING DELEGATE, so we strip any EDITING DELEGATE
112         # messages to make more of the tests pass.
113         # It's possible more of the ports might want this and this could move down into WebKitPort.
114         delegate_regexp = re.compile("^EDITING DELEGATE: .*?\n", re.MULTILINE)
115         expected_text = delegate_regexp.sub("", expected_text)
116         actual_text = delegate_regexp.sub("", actual_text)
117         return expected_text != actual_text
118
119     def default_baseline_search_path(self, **kwargs):
120         version_name_map = VersionNameMap.map(self.host.platform)
121         if self._os_version < self.VERSION_MIN or self._os_version > self.VERSION_MAX:
122             fallback_versions = [self._os_version]
123         else:
124             sorted_versions = sorted(version_name_map.mapping_for_platform(platform=self.port_name).values())
125             fallback_versions = sorted_versions[sorted_versions.index(self._os_version):]
126         fallback_names = ['win-' + version_name_map.to_name(version, platform=self.port_name).lower().replace(' ', '') for version in fallback_versions]
127         fallback_names.append('win')
128
129         # FIXME: The AppleWin port falls back to AppleMac for some results.  Eventually we'll have a shared 'apple' port.
130         if self.get_option('webkit_test_runner'):
131             fallback_names.insert(0, 'win-wk2')
132             fallback_names.append('mac-wk2')
133             # Note we do not add 'wk2' here, even though it's included in _skipped_search_paths().
134         # FIXME: Perhaps we should get this list from MacPort?
135         fallback_names.append('mac')
136         result = list(map(self._webkit_baseline_path, fallback_names))
137         if apple_additions() and getattr(apple_additions(), "layout_tests_path", None):
138             result.insert(0, self._filesystem.join(apple_additions().layout_tests_path(), self.port_name))
139         return result
140
141     def setup_environ_for_server(self, server_name=None):
142         env = super(WinPort, self).setup_environ_for_server(server_name)
143         env['XML_CATALOG_FILES'] = ''  # work around missing /etc/catalog <rdar://problem/4292995>
144         return env
145
146     def environment_for_api_tests(self):
147         env = super(WinPort, self).environment_for_api_tests()
148         for variable in ['SYSTEMROOT', 'WEBKIT_LIBRARIES']:
149             self._copy_value_from_environ_if_set(env, variable)
150         return env
151
152     def operating_system(self):
153         return 'win'
154
155     def _port_flag_for_scripts(self):
156         if self.architecture() == 'x86_64':
157             return '--64-bit'
158         return None
159
160     def show_results_html_file(self, results_filename):
161         self._run_script('run-safari', [abspath_to_uri(SystemHost().platform, results_filename)])
162
163     def _build_path(self, *comps):
164         """Returns the full path to the test driver (DumpRenderTree)."""
165         root_directory = self.get_option('_cached_root') or self.get_option('root')
166         if not root_directory:
167             ApplePort._build_path(self, *comps)  # Sets option _cached_root
168             binary_directory = 'bin32'
169             if self.architecture() == 'x86_64':
170                 binary_directory = 'bin64'
171             root_directory = self._filesystem.join(self.get_option('_cached_root'), binary_directory)
172             self.set_option('_cached_root', root_directory)
173
174         return self._filesystem.join(root_directory, *comps)
175
176     def is_cygwin(self):
177         """Return whether current platform is Cygwin or not"""
178         return self.host.platform.is_cygwin()
179
180     # Note: These are based on the stock XAMPP locations for these files.
181     def _uses_apache(self):
182         return True
183
184     def _path_to_apache(self):
185         root = os.environ.get('XAMPP_ROOT', 'C:\\xampp')
186         path = self._filesystem.join(root, 'apache', 'bin', 'httpd.exe')
187         if self._filesystem.exists(path):
188             return path
189         _log.error('Could not find apache in the expected location. (path=%s)' % path)
190         return None
191
192     def _path_to_lighttpd(self):
193         return "/usr/sbin/lighttpd"
194
195     def _path_to_lighttpd_modules(self):
196         return "/usr/lib/lighttpd"
197
198     def _path_to_lighttpd_php(self):
199         return "/usr/bin/php-cgi"
200
201     def _path_to_default_image_diff(self):
202         return self._build_path('ImageDiff.exe')
203
204     API_TEST_BINARY_NAMES = ['TestWTF.exe', 'TestWebCore.exe', 'TestWebKitLegacy.exe']
205
206     def path_to_api_test_binaries(self):
207         return {binary.split('.')[0]: self._build_path(binary) for binary in self.API_TEST_BINARY_NAMES}
208
209     def test_search_path(self, **kwargs):
210         test_fallback_names = [path for path in self.baseline_search_path() if not path.startswith(self._webkit_baseline_path('mac'))]
211         return list(map(self._webkit_baseline_path, test_fallback_names))
212
213     def _ntsd_location(self):
214         if 'PROGRAMFILES' not in os.environ:
215             return None
216         possible_paths = [self._filesystem.join(os.environ['PROGRAMFILES'], "Windows Kits", "10", "Debuggers", "x64", "ntsd.exe"),
217             self._filesystem.join(os.environ['PROGRAMFILES'], "Windows Kits", "8.1", "Debuggers", "x64", "ntsd.exe"),
218             self._filesystem.join(os.environ['PROGRAMFILES'], "Windows Kits", "8.0", "Debuggers", "x64", "ntsd.exe")]
219         if self.architecture() == 'x86_64':
220             possible_paths.append(self._filesystem.join("{0} (x86)".format(os.environ['PROGRAMFILES']), "Windows Kits", "10", "Debuggers", "x64", "ntsd.exe"))
221             possible_paths.append(self._filesystem.join("{0} (x86)".format(os.environ['PROGRAMFILES']), "Windows Kits", "8.1", "Debuggers", "x64", "ntsd.exe"))
222             possible_paths.append(self._filesystem.join("{0} (x86)".format(os.environ['PROGRAMFILES']), "Windows Kits", "8.0", "Debuggers", "x64", "ntsd.exe"))
223             possible_paths.append(self._filesystem.join("{0} (x86)".format(os.environ['PROGRAMFILES']), "Debugging Tools for Windows (x64)", "ntsd.exe"))
224         else:
225             possible_paths.append(self._filesystem.join(os.environ['PROGRAMFILES'], "Debugging Tools for Windows (x86)", "ntsd.exe"))
226         possible_paths.append(self._filesystem.join(os.environ['SYSTEMROOT'], "system32", "ntsd.exe"))
227         if 'ProgramW6432' in os.environ:
228             possible_paths.append(self._filesystem.join(os.environ['ProgramW6432'], "Windows Kits", "10", "Debuggers", "x64", "ntsd.exe"))
229             possible_paths.append(self._filesystem.join(os.environ['ProgramW6432'], "Windows Kits", "8.1", "Debuggers", "x64", "ntsd.exe"))
230             possible_paths.append(self._filesystem.join(os.environ['ProgramW6432'], "Windows Kits", "8.0", "Debuggers", "x64", "ntsd.exe"))
231             possible_paths.append(self._filesystem.join(os.environ['ProgramW6432'], "Debugging Tools for Windows (x64)", "ntsd.exe"))
232         for path in possible_paths:
233             expanded_path = self._filesystem.expanduser(path)
234             _log.debug("Considering '%s'" % expanded_path)
235             if self._filesystem.exists(expanded_path):
236                 _log.debug("Using ntsd located in '%s'" % path)
237                 return expanded_path
238         return None
239
240     def create_debugger_command_file(self):
241         debugger_temp_directory = str(self._filesystem.mkdtemp())
242         command_file = self._filesystem.join(debugger_temp_directory, "debugger-commands.txt")
243         commands = ''.join(['.logopen /t "%s\\%s.txt"\n' % (cygpath(self.results_directory()), self.CRASH_LOG_PREFIX),
244             '.srcpath "%s"\n' % cygpath(self._webkit_finder.webkit_base()),
245             '!analyze -vv\n',
246             '~*kpn\n',
247             'q\n'])
248         self._filesystem.write_text_file(command_file, commands)
249         return command_file
250
251     def read_registry_value(self, reg_path, arch, root, key):
252         if sys.platform.startswith('win'):
253             _log.debug("Trying to read %s\\%s" % (reg_path, key))
254             try:
255                 registry_key = _winreg.OpenKey(root, reg_path)
256                 value = _winreg.QueryValueEx(registry_key, key)
257                 _winreg.CloseKey(registry_key)
258             except WindowsError as ex:
259                 _log.debug("Unable to read %s\\%s: %s" % (reg_path, key, str(ex)))
260                 return ['', self._REG_SZ]
261         else:
262             registry_key = reg_path % (root, key)
263             _log.debug("Reading %s" % (registry_key))
264             read_registry_command = ["regtool", arch, "get", registry_key]
265             int_value = self._executive.run_command(read_registry_command, ignore_errors=True)
266             # regtool doesn't return the type of the entry, so need this ugly hack:
267             if reg_path in (self.WINDOWS_ERROR_REPORTING_KEY, self.WOW64_WINDOWS_ERROR_REPORTING_KEY):
268                 _log.debug("I got {0}".format(int_value))
269                 try:
270                     value = [int(int_value), self._REG_DWORD]
271                 except:
272                     value = [0, self._REG_DWORD]
273             else:
274                 value = [int_value.rstrip(), self._REG_SZ]
275
276         _log.debug("I got back ({0}) of type ({1})".format(value[0], value[1]))
277         return value
278
279     def write_registry_value(self, reg_path, arch, root, key, regType, value):
280         if sys.platform.startswith('win'):
281             _log.debug("Trying to write %s\\%s = %s" % (reg_path, key, value))
282             try:
283                 registry_key = _winreg.OpenKey(root, reg_path, 0, _winreg.KEY_WRITE)
284             except WindowsError:
285                 try:
286                     _log.debug("Key doesn't exist -- must create it.")
287                     registry_key = _winreg.CreateKeyEx(root, reg_path, 0, _winreg.KEY_WRITE)
288                 except WindowsError as ex:
289                     _log.error("Error setting (%s) %s\key: %s to value: %s.  Error=%s." % (arch, root, key, value, str(ex)))
290                     _log.error("You many need to adjust permissions on the %s\\%s key." % (reg_path, key))
291                     return False
292
293             _log.debug("Writing {0} of type {1} to {2}\\{3}".format(value, regType, registry_key, key))
294             _winreg.SetValueEx(registry_key, key, 0, regType, value)
295             _winreg.CloseKey(registry_key)
296         else:
297             registry_key = reg_path % (root, key)
298             _log.debug("Writing to %s" % registry_key)
299
300             set_reg_value_command = ["regtool", arch, "set", regType, str(registry_key), str(value)]
301             rc = self._executive.run_command(set_reg_value_command, return_exit_code=True)
302             if rc == 2:
303                 add_reg_value_command = ["regtool", arch, "add", regType, str(registry_key)]
304                 rc = self._executive.run_command(add_reg_value_command, return_exit_code=True)
305                 if rc == 0:
306                     rc = self._executive.run_command(set_reg_value_command, return_exit_code=True)
307             if rc:
308                 _log.warn("Error setting (%s) %s\key: %s to value: %s.  Error=%s." % (arch, root, key, value, str(rc)))
309                 _log.warn("You many need to adjust permissions on the %s key." % registry_key)
310                 return False
311
312         # On Windows Vista/7 with UAC enabled, regtool will fail to modify the registry, but will still
313         # return a successful exit code. So we double-check here that the value we tried to write to the
314         # registry was really written.
315         check_value = self.read_registry_value(reg_path, arch, root, key)
316         if check_value[0] != value or check_value[1] != regType:
317             _log.warn("Reg update reported success, but value of key %s did not change." % key)
318             _log.warn("Wanted to set it to ({0}, {1}), but got {2})".format(value, regType, check_value))
319             _log.warn("You many need to adjust permissions on the %s\\%s key." % (reg_path, key))
320             return False
321
322         return True
323
324     def setup_crash_log_saving(self):
325         if '_NT_SYMBOL_PATH' not in os.environ:
326             _log.warning("The _NT_SYMBOL_PATH environment variable is not set. Using Microsoft Symbol Server.")
327             os.environ['_NT_SYMBOL_PATH'] = 'SRV*http://msdl.microsoft.com/download/symbols'
328
329         # Add build path to symbol path
330         os.environ['_NT_SYMBOL_PATH'] += ";" + self._build_path()
331
332         ntsd_path = self._ntsd_location()
333         if not ntsd_path:
334             _log.warning("Can't find ntsd.exe. Crash logs will not be saved.")
335             return None
336         # If we used -c (instead of -cf) we could pass the commands directly on the command line. But
337         # when the commands include multiple quoted paths (e.g., for .logopen and .srcpath), Windows
338         # fails to invoke the post-mortem debugger at all (perhaps due to a bug in Windows's command
339         # line parsing). So we save the commands to a file instead and tell the debugger to execute them
340         # using -cf.
341         command_file = self.create_debugger_command_file()
342         if not command_file:
343             return None
344         debugger_options = '"{0}" -p %ld -e %ld -g -noio -lines -cf "{1}"'.format(cygpath(ntsd_path), cygpath(command_file))
345         registry_settings = {'Debugger': [debugger_options, self._REG_SZ], 'Auto': ["1", self._REG_SZ]}
346         for key, value in registry_settings.items():
347             for arch in ["--wow32", "--wow64"]:
348                 self.previous_debugger_values[(arch, self._HKLM, key)] = self.read_registry_value(self.POST_MORTEM_DEBUGGER_KEY, arch, self._HKLM, key)
349                 self.previous_wow64_debugger_values[(arch, self._HKLM, key)] = self.read_registry_value(self.WOW64_POST_MORTEM_DEBUGGER_KEY, arch, self._HKLM, key)
350                 self.write_registry_value(self.POST_MORTEM_DEBUGGER_KEY, arch, self._HKLM, key, value[1], value[0])
351                 self.write_registry_value(self.WOW64_POST_MORTEM_DEBUGGER_KEY, arch, self._HKLM, key, value[1], value[0])
352
353     def restore_crash_log_saving(self):
354         for key, value in self.previous_debugger_values.items():
355             self.write_registry_value(self.POST_MORTEM_DEBUGGER_KEY, key[0], key[1], key[2], value[1], value[0])
356         for key, value in self.previous_wow64_debugger_values.items():
357             self.write_registry_value(self.WOW64_POST_MORTEM_DEBUGGER_KEY, key[0], key[1], key[2], value[1], value[0])
358
359     def prevent_error_dialogs(self):
360         registry_settings = {'DontShowUI': [1, self._REG_DWORD], 'Disabled': [1, self._REG_DWORD]}
361         for key, value in registry_settings.items():
362             for root in [self._HKLM, self._HKCU]:
363                 for arch in ["--wow32", "--wow64"]:
364                     self.previous_error_reporting_values[(arch, root, key)] = self.read_registry_value(self.WINDOWS_ERROR_REPORTING_KEY, arch, root, key)
365                     self.previous_wow64_error_reporting_values[(arch, root, key)] = self.read_registry_value(self.WOW64_WINDOWS_ERROR_REPORTING_KEY, arch, root, key)
366                     self.write_registry_value(self.WINDOWS_ERROR_REPORTING_KEY, arch, root, key, value[1], value[0])
367                     self.write_registry_value(self.WOW64_WINDOWS_ERROR_REPORTING_KEY, arch, root, key, value[1], value[0])
368
369     def allow_error_dialogs(self):
370         for key, value in self.previous_error_reporting_values.items():
371             self.write_registry_value(self.WINDOWS_ERROR_REPORTING_KEY, key[0], key[1], key[2], value[1], value[0])
372         for key, value in self.previous_wow64_error_reporting_values.items():
373             self.write_registry_value(self.WOW64_WINDOWS_ERROR_REPORTING_KEY, key[0], key[1], key[2], value[1], value[0])
374
375     def delete_sem_locks(self):
376         os.system("rm -rf /dev/shm/sem.*")
377
378     def delete_preference_files(self):
379         try:
380             preferences_files = self._filesystem.join(os.environ['APPDATA'], "Apple Computer/Preferences", "com.apple.DumpRenderTree*")
381             filelist = glob.glob(preferences_files)
382             for file in filelist:
383                 self._filesystem.remove(file)
384         except:
385             _log.warn("Failed to delete preference files.")
386
387     def setup_test_run(self, device_type=None):
388         atexit.register(self.restore_crash_log_saving)
389         self.setup_crash_log_saving()
390         self.prevent_error_dialogs()
391         self.delete_sem_locks()
392         self.delete_preference_files()
393         super(WinPort, self).setup_test_run(device_type)
394
395     def clean_up_test_run(self):
396         self.allow_error_dialogs()
397         self.restore_crash_log_saving()
398         super(WinPort, self).clean_up_test_run()
399
400     def path_to_crash_logs(self):
401         return self.results_directory()
402
403     def _get_crash_log(self, name, pid, stdout, stderr, newer_than, time_fn=None, sleep_fn=None, wait_for_log=True, target_host=None):
404         # Note that we do slow-spin here and wait, since it appears the time
405         # ReportCrash takes to actually write and flush the file varies when there are
406         # lots of simultaneous crashes going on.
407         # FIXME: Should most of this be moved into CrashLogs()?
408         time_fn = time_fn or time.time
409         sleep_fn = sleep_fn or time.sleep
410         crash_log = ''
411         crash_logs = CrashLogs(target_host or self.host, self.path_to_crash_logs(), crash_logs_to_skip=self._crash_logs_to_skip_for_host.get(target_host or self.host, []))
412         now = time_fn()
413         # FIXME: delete this after we're sure this code is working ...
414         _log.debug('looking for crash log for %s:%s' % (name, str(pid)))
415         deadline = now + 5 * int(self.get_option('child_processes', 1))
416         while not crash_log and now <= deadline:
417             crash_log = crash_logs.find_newest_log(name, pid, include_errors=True, newer_than=newer_than)
418             if not wait_for_log:
419                 break
420             if not crash_log or not [line for line in crash_log.splitlines() if line.startswith('quit:')]:
421                 sleep_fn(0.1)
422                 now = time_fn()
423
424         if not crash_log:
425             return (stderr, None)
426         return (stderr, crash_log)
427
428     def look_for_new_crash_logs(self, crashed_processes, start_time):
429         """Since crash logs can take a long time to be written out if the system is
430            under stress do a second pass at the end of the test run.
431
432            crashes: test_name -> pid, process_name tuple of crashed process
433            start_time: time the tests started at.  We're looking for crash
434                logs after that time.
435         """
436         crash_logs = {}
437         for (test_name, process_name, pid) in crashed_processes:
438             # Passing None for output.  This is a second pass after the test finished so
439             # if the output had any logging we would have already collected it.
440             crash_log = self._get_crash_log(process_name, pid, None, None, start_time, wait_for_log=False)[1]
441             if crash_log:
442                 crash_logs[test_name] = crash_log
443         return crash_logs
444
445     def check_httpd(self):
446         if not super(WinPort, self).check_httpd():
447             return False
448
449         path = self._path_to_apache()
450         if not path:
451             return False
452
453         # To launch Apache as a daemon, service installation is required.
454         exit_code = self._executive.run_command([path, '-k', 'install', '-T'], return_exit_code=True)
455         # 0=success, 2=already installed, 720005=permission error, etc.
456         if exit_code not in (0, 2):
457             _log.error('Could not install httpd as a service. Perhaps you forgot to run as adminstrator? (exit code={})'.format(exit_code))
458             return False
459
460         return True
461
462
463 class WinCairoPort(WinPort):
464     port_name = "wincairo"
465
466     DEFAULT_ARCHITECTURE = 'x86_64'
467
468     def default_baseline_search_path(self, **kwargs):
469         return list(map(self._webkit_baseline_path, self._search_paths()))
470
471     def _port_specific_expectations_files(self, **kwargs):
472         return list(map(lambda x: self._filesystem.join(self._webkit_baseline_path(x), 'TestExpectations'), reversed(self._search_paths())))
473
474     def _search_paths(self):
475         paths = []
476         version_name_map = VersionNameMap.map(self.host.platform)
477         if self._os_version < self.VERSION_MIN or self._os_version > self.VERSION_MAX:
478             versions = [self._os_version]
479         else:
480             sorted_versions = sorted(version_name_map.mapping_for_platform(platform=self.port_name).values())
481             versions = sorted_versions[sorted_versions.index(self._os_version):]
482
483         normalize = lambda version: version.lower().replace(' ', '')
484         to_name = lambda version: version_name_map.to_name(version, platform=self.port_name)
485
486         wk_version = 'wk2' if self.get_option('webkit_test_runner') else 'wk1'
487
488         for version in versions:
489             name = self.port_name + '-' + normalize(to_name(version))
490             paths.append(name + '-' + wk_version)
491             paths.append(name)
492
493         paths.append(self.port_name + '-' + wk_version)
494         paths.append(self.port_name)
495         if self.get_option('webkit_test_runner'):
496             paths.append('wk2')
497         paths.extend(self.get_option("additional_platform_directory", []))
498
499         return paths
500
501     def configuration_for_upload(self, host=None):
502         configuration = super(WinCairoPort, self).configuration_for_upload(host=host)
503         configuration['platform'] = self.port_name
504         return configuration
505
506
507 class FTWPort(WinPort):
508     port_name = "ftw"
509
510     DEFAULT_ARCHITECTURE = 'x86_64'
511
512     def default_baseline_search_path(self, **kwargs):
513         return list(map(self._webkit_baseline_path, self._search_paths()))
514
515     def _port_specific_expectations_files(self, **kwargs):
516         return list(map(lambda x: self._filesystem.join(self._webkit_baseline_path(x), 'TestExpectations'), reversed(self._search_paths())))
517
518     def _search_paths(self):
519         paths = []
520         version_name_map = VersionNameMap.map(self.host.platform)
521         if self._os_version < self.VERSION_MIN or self._os_version > self.VERSION_MAX:
522             versions = [self._os_version]
523         else:
524             sorted_versions = sorted(version_name_map.mapping_for_platform(platform=self.port_name).values())
525             versions = sorted_versions[sorted_versions.index(self._os_version):]
526
527         normalize = lambda version: version.lower().replace(' ', '')
528         to_name = lambda version: version_name_map.to_name(version, platform=self.port_name)
529
530         wk_version = 'wk2' if self.get_option('webkit_test_runner') else 'wk1'
531
532         for version in versions:
533             name = self.port_name + '-' + normalize(to_name(version))
534             paths.append(name + '-' + wk_version)
535             paths.append(name)
536
537         paths.append(self.port_name + '-' + wk_version)
538         paths.append(self.port_name)
539         if self.get_option('webkit_test_runner'):
540             paths.append('wk2')
541         paths.extend(self.get_option("additional_platform_directory", []))
542
543         return paths