File: win.py

package info (click to toggle)
qtwebkit-opensource-src 5.7.1%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 291,692 kB
  • ctags: 268,122
  • sloc: cpp: 1,360,420; python: 70,286; ansic: 42,986; perl: 35,476; ruby: 12,236; objc: 9,465; xml: 8,396; asm: 3,873; yacc: 2,397; sh: 1,647; makefile: 650; lex: 644; java: 110
file content (284 lines) | stat: -rw-r--r-- 14,027 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# Copyright (C) 2010 Google Inc. All rights reserved.
# Copyright (C) 2013 Apple Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#     * Neither the Google name nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import atexit
import os
import logging
import re
import sys
import time

from webkitpy.common.system.crashlogs import CrashLogs
from webkitpy.common.system.systemhost import SystemHost
from webkitpy.common.system.executive import ScriptError, Executive
from webkitpy.common.system.path import abspath_to_uri, cygpath
from webkitpy.port.apple import ApplePort


_log = logging.getLogger(__name__)


class WinPort(ApplePort):
    port_name = "win"

    VERSION_FALLBACK_ORDER = ["win-xp", "win-vista", "win-7sp0", "win"]

    ARCHITECTURES = ['x86']

    CRASH_LOG_PREFIX = "CrashLog"

    POST_MORTEM_DEBUGGER_KEY = "/HKLM/SOFTWARE/Microsoft/Windows NT/CurrentVersion/AeDebug/%s"

    previous_debugger_values = {}

    def do_text_results_differ(self, expected_text, actual_text):
        # Sanity was restored in WK2, so we don't need this hack there.
        if self.get_option('webkit_test_runner'):
            return ApplePort.do_text_results_differ(self, expected_text, actual_text)

        # This is a hack (which dates back to ORWT).
        # Windows does not have an EDITING DELEGATE, so we strip any EDITING DELEGATE
        # messages to make more of the tests pass.
        # It's possible more of the ports might want this and this could move down into WebKitPort.
        delegate_regexp = re.compile("^EDITING DELEGATE: .*?\n", re.MULTILINE)
        expected_text = delegate_regexp.sub("", expected_text)
        actual_text = delegate_regexp.sub("", actual_text)
        return expected_text != actual_text

    def default_baseline_search_path(self):
        name = self._name.replace('-wk2', '')
        if name.endswith(self.FUTURE_VERSION):
            fallback_names = [self.port_name]
        else:
            fallback_names = self.VERSION_FALLBACK_ORDER[self.VERSION_FALLBACK_ORDER.index(name):-1] + [self.port_name]
        # FIXME: The AppleWin port falls back to AppleMac for some results.  Eventually we'll have a shared 'apple' port.
        if self.get_option('webkit_test_runner'):
            fallback_names.insert(0, 'win-wk2')
            fallback_names.append('mac-wk2')
            # Note we do not add 'wk2' here, even though it's included in _skipped_search_paths().
        # FIXME: Perhaps we should get this list from MacPort?
        fallback_names.extend(['mac-lion', 'mac'])
        return map(self._webkit_baseline_path, fallback_names)

    def operating_system(self):
        return 'win'

    def show_results_html_file(self, results_filename):
        self._run_script('run-safari', [abspath_to_uri(SystemHost().platform, results_filename)])

    # FIXME: webkitperl/httpd.pm installs /usr/lib/apache/libphp4.dll on cycwin automatically
    # as part of running old-run-webkit-tests.  That's bad design, but we may need some similar hack.
    # We might use setup_environ_for_server for such a hack (or modify apache_http_server.py).

    def _runtime_feature_list(self):
        supported_features_command = [self._path_to_driver(), '--print-supported-features']
        try:
            output = self._executive.run_command(supported_features_command, error_handler=Executive.ignore_error)
        except OSError, e:
            _log.warn("Exception running driver: %s, %s.  Driver must be built before calling WebKitPort.test_expectations()." % (supported_features_command, e))
            return None

        # Note: win/DumpRenderTree.cpp does not print a leading space before the features_string.
        match_object = re.match("SupportedFeatures:\s*(?P<features_string>.*)\s*", output)
        if not match_object:
            return None
        return match_object.group('features_string').split(' ')

    # Note: These are based on the stock Cygwin locations for these files.
    def _uses_apache(self):
        return False

    def _path_to_lighttpd(self):
        return "/usr/sbin/lighttpd"

    def _path_to_lighttpd_modules(self):
        return "/usr/lib/lighttpd"

    def _path_to_lighttpd_php(self):
        return "/usr/bin/php-cgi"

    def _driver_tempdir_for_environment(self):
        return cygpath(self._driver_tempdir())

    def test_search_path(self):
        test_fallback_names = [path for path in self.baseline_search_path() if not path.startswith(self._webkit_baseline_path('mac'))]
        return map(self._webkit_baseline_path, test_fallback_names)

    def _ntsd_location(self):
        possible_paths = [self._filesystem.join(os.environ['PROGRAMFILES'], "Windows Kits", "8.0", "Debuggers", "x86", "ntsd.exe"),
            self._filesystem.join(os.environ['PROGRAMFILES'], "Windows Kits", "8.0", "Debuggers", "x64", "ntsd.exe"),
            self._filesystem.join(os.environ['PROGRAMFILES'], "Debugging Tools for Windows (x86)", "ntsd.exe"),
            self._filesystem.join(os.environ['ProgramW6432'], "Debugging Tools for Windows (x64)", "ntsd.exe"),
            self._filesystem.join(os.environ['SYSTEMROOT'], "system32", "ntsd.exe")]
        for path in possible_paths:
            expanded_path = self._filesystem.expanduser(path)
            if self._filesystem.exists(expanded_path):
                _log.debug("Using ntsd located in '%s'" % path)
                return expanded_path
        return None

    def create_debugger_command_file(self):
        debugger_temp_directory = str(self._filesystem.mkdtemp())
        command_file = self._filesystem.join(debugger_temp_directory, "debugger-commands.txt")
        commands = ''.join(['.logopen /t "%s\\%s.txt"\n' % (cygpath(self.results_directory()), self.CRASH_LOG_PREFIX),
            '.srcpath "%s"\n' % cygpath(self._webkit_finder.webkit_base()),
            '!analyze -vv\n',
            '~*kpn\n',
            'q\n'])
        self._filesystem.write_text_file(command_file, commands)
        return command_file

    def read_registry_string(self, key):
        registry_key = self.POST_MORTEM_DEBUGGER_KEY % key
        read_registry_command = ["regtool", "--wow32", "get", registry_key]
        value = self._executive.run_command(read_registry_command, error_handler=Executive.ignore_error)
        return value.rstrip()

    def write_registry_string(self, key, value):
        registry_key = self.POST_MORTEM_DEBUGGER_KEY % key
        set_reg_value_command = ["regtool", "--wow32", "set", "-s", str(registry_key), str(value)]
        rc = self._executive.run_command(set_reg_value_command, return_exit_code=True)
        if rc == 2:
            add_reg_value_command = ["regtool", "--wow32", "add", "-s", str(registry_key)]
            rc = self._executive.run_command(add_reg_value_command, return_exit_code=True)
            if rc == 0:
                rc = self._executive.run_command(set_reg_value_command, return_exit_code=True)
        if rc:
            _log.warn("Error setting key: %s to value %s.  Error=%ld." % (key, value, rc))
            return False

        # On Windows Vista/7 with UAC enabled, regtool will fail to modify the registry, but will still
        # return a successful exit code. So we double-check here that the value we tried to write to the
        # registry was really written.
        if self.read_registry_string(key) != value:
            _log.warn("Regtool reported success, but value of key %s did not change." % key)
            return False

        return True

    def setup_crash_log_saving(self):
        if '_NT_SYMBOL_PATH' not in os.environ:
            _log.warning("The _NT_SYMBOL_PATH environment variable is not set. Crash logs will not be saved.")
            return None
        ntsd_path = self._ntsd_location()
        if not ntsd_path:
            _log.warning("Can't find ntsd.exe. Crash logs will not be saved.")
            return None
        # If we used -c (instead of -cf) we could pass the commands directly on the command line. But
        # when the commands include multiple quoted paths (e.g., for .logopen and .srcpath), Windows
        # fails to invoke the post-mortem debugger at all (perhaps due to a bug in Windows's command
        # line parsing). So we save the commands to a file instead and tell the debugger to execute them
        # using -cf.
        command_file = self.create_debugger_command_file()
        if not command_file:
            return None
        debugger_options = '"{0}" -p %ld -e %ld -g -noio -lines -cf "{1}"'.format(cygpath(ntsd_path), cygpath(command_file))
        registry_settings = {'Debugger': debugger_options, 'Auto': "1"}
        for key in registry_settings:
            self.previous_debugger_values[key] = self.read_registry_string(key)
            self.write_registry_string(key, registry_settings[key])

    def restore_crash_log_saving(self):
        for key in self.previous_debugger_values:
            self.write_registry_string(key, self.previous_debugger_values[key])

    def setup_test_run(self):
        atexit.register(self.restore_crash_log_saving)
        self.setup_crash_log_saving()
        super(WinPort, self).setup_test_run()

    def clean_up_test_run(self):
        self.restore_crash_log_saving()
        super(WinPort, self).clean_up_test_run()

    def _get_crash_log(self, name, pid, stdout, stderr, newer_than, time_fn=None, sleep_fn=None, wait_for_log=True):
        # Note that we do slow-spin here and wait, since it appears the time
        # ReportCrash takes to actually write and flush the file varies when there are
        # lots of simultaneous crashes going on.
        # FIXME: Should most of this be moved into CrashLogs()?
        time_fn = time_fn or time.time
        sleep_fn = sleep_fn or time.sleep
        crash_log = ''
        crash_logs = CrashLogs(self.host, self.results_directory())
        now = time_fn()
        # FIXME: delete this after we're sure this code is working ...
        _log.debug('looking for crash log for %s:%s' % (name, str(pid)))
        deadline = now + 5 * int(self.get_option('child_processes', 1))
        while not crash_log and now <= deadline:
            # If the system_pid hasn't been determined yet, just try with the passed in pid.  We'll be checking again later
            system_pid = self._executive.pid_to_system_pid.get(pid)
            if system_pid == None:
                break  # We haven't mapped cygwin pid->win pid yet
            crash_log = crash_logs.find_newest_log(name, system_pid, include_errors=True, newer_than=newer_than)
            if not wait_for_log:
                break
            if not crash_log or not [line for line in crash_log.splitlines() if line.startswith('quit:')]:
                sleep_fn(0.1)
                now = time_fn()

        if not crash_log:
            return (stderr, None)
        return (stderr, crash_log)

    def look_for_new_crash_logs(self, crashed_processes, start_time):
        """Since crash logs can take a long time to be written out if the system is
           under stress do a second pass at the end of the test run.

           crashes: test_name -> pid, process_name tuple of crashed process
           start_time: time the tests started at.  We're looking for crash
               logs after that time.
        """
        crash_logs = {}
        for (test_name, process_name, pid) in crashed_processes:
            # Passing None for output.  This is a second pass after the test finished so
            # if the output had any logging we would have already collected it.
            crash_log = self._get_crash_log(process_name, pid, None, None, start_time, wait_for_log=False)[1]
            if crash_log:
                crash_logs[test_name] = crash_log
        return crash_logs

    def find_system_pid(self, name, pid):
        system_pid = int(pid)
        # Windows and Cygwin PIDs are not the same.  We need to find the Windows
        # PID for our Cygwin process so we can match it later to any crash
        # files we end up creating (which will be tagged with the Windows PID)
        ps_process = self._executive.run_command(['ps', '-e'], error_handler=Executive.ignore_error)
        for line in ps_process.splitlines():
            tokens = line.strip().split()
            try:
                cpid, ppid, pgid, winpid, tty, uid, stime, process_name = tokens
                if process_name.endswith(name):
                    self._executive.pid_to_system_pid[int(cpid)] = int(winpid)
                    if int(pid) == int(cpid):
                        system_pid = int(winpid)
                    break
            except ValueError, e:
                pass

        return system_pid