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