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