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