summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/tools/blinkpy/web_tests/servers/wptserve.py
blob: 20ef3d819a3ec5ce25d99b6d14a195aa5bdf52fa (plain)
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
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Start and stop the WPTserve servers as they're used by the web tests."""

import datetime
import json
import logging

from blinkpy.common.path_finder import PathFinder
from blinkpy.web_tests.servers import server_base

_log = logging.getLogger(__name__)


class WPTServe(server_base.ServerBase):
    def __init__(self, port_obj, output_dir):
        super(WPTServe, self).__init__(port_obj, output_dir)

        # These ports must match wpt_tools/wpt.config.json
        http_port = 8001
        http_alt_port = 8081
        http_private_port = 8082
        http_public_port = 8083
        https_port = 8444
        https_alt_port = 8445
        https_private_port = 8446
        https_public_port = 8447
        h2_port = 9000
        ws_port = 9001
        wss_port = 9444

        self._name = 'wptserve'
        self._log_prefixes = ('wptserve_stderr', )
        self._mappings = [{
            'port': http_port,
            'scheme': 'http'
        }, {
            'port': http_alt_port,
            'scheme': 'http'
        }, {
            'port': http_private_port,
            'scheme': 'http'
        }, {
            'port': http_public_port,
            'scheme': 'http'
        }, {
            'port': https_port,
            'scheme': 'https',
            'sslcert': True
        }, {
            'port': https_alt_port,
            'scheme': 'https',
            'sslcert': True
        }, {
            'port': https_private_port,
            'scheme': 'https'
        }, {
            'port': https_public_port,
            'scheme': 'https'
        }, {
            'port': h2_port,
            'scheme': 'https',
            'sslcert': True
        }, {
            'port': ws_port,
            'scheme': 'ws'
        }, {
            'port': wss_port,
            'scheme': 'wss',
            'sslcert': True
        }]

        # TODO(burnik): We can probably avoid PID files for WPT in the future.
        fs = self._filesystem
        self._pid_file = fs.join(self._runtime_path, '%s.pid' % self._name)
        self._config_file = fs.join(self._runtime_path, 'wpt.config.json')

        finder = PathFinder(fs)
        path_to_pywebsocket = finder.path_from_chromium_base(
            'third_party', 'pywebsocket3', 'src')
        self.path_to_wpt_support = finder.path_from_chromium_base(
            'third_party', 'wpt_tools')
        path_to_wpt_root = fs.join(self.path_to_wpt_support, 'wpt')
        path_to_wpt_tests = fs.abspath(
            fs.join(self._port_obj.web_tests_dir(), 'external', 'wpt'))
        path_to_ws_handlers = fs.join(path_to_wpt_tests, 'websockets',
                                      'handlers')
        wpt_script = fs.join(path_to_wpt_root, 'wpt')
        start_cmd = [
            self._port_obj.python3_command(),
            '-u',
            wpt_script,
            'serve',
            '--config',
            self._config_file,
            '--doc_root',
            path_to_wpt_tests,
        ]

        # Some users (e.g. run_webdriver_tests.py) do not need WebSocket
        # handlers, so we only add the flag if the directory exists.
        if self._port_obj.host.filesystem.exists(path_to_ws_handlers):
            start_cmd += ['--ws_doc_root', path_to_ws_handlers]

        # TODO(burnik): We should stop setting the CWD once WPT can be run without it.
        self._cwd = path_to_wpt_root
        self._env = port_obj.host.environ.copy()
        self._env.update({'PYTHONPATH': path_to_pywebsocket})
        self._start_cmd = start_cmd

        self._error_log_path = self._filesystem.join(output_dir,
                                                     'wptserve_stderr.txt')
        self._output_log_path = self._filesystem.join(output_dir,
                                                      'wptserve_stdout.txt')

        expiration_date = datetime.date(2025, 1, 4)
        if datetime.date.today() > expiration_date - datetime.timedelta(30):
            _log.error(
                'Pre-generated keys and certificates are going to be expired at %s.'
                ' Please re-generate them by following steps in %s/README.chromium.',
                expiration_date.strftime('%b %d %Y'), self.path_to_wpt_support)

    def _prepare_config(self):
        fs = self._filesystem
        template_path = fs.join(self.path_to_wpt_support, 'wpt.config.json')
        config = json.loads(fs.read_text_file(template_path))
        config['aliases'].append({
            'url-path':
            '/gen/',
            'local-dir':
            self._port_obj.generated_sources_directory()
        })

        with fs.open_text_file_for_writing(self._config_file) as f:
            json.dump(config, f)

        # wptserve is spammy on stderr even at the INFO log level and will block
        # the pipe, so we need to redirect it.
        # The file is opened here instead in __init__ because _remove_stale_logs
        # will try to delete the log file, which causes deadlocks on Windows.
        self._stderr = fs.open_text_file_for_writing(self._error_log_path)

        # The pywebsocket process started by wptserve logs to stdout. This can
        # also cause deadlock, and so should also be redirected to a file.
        self._stdout = fs.open_text_file_for_writing(self._output_log_path)

    def _stop_running_server(self):
        if not self._wait_for_action(self._check_and_kill):
            # This is mostly for POSIX systems. We send SIGINT in
            # _check_and_kill() and here we use SIGKILL.
            self._executive.kill_process(self._pid)

        if self._filesystem.exists(self._pid_file):
            self._filesystem.remove(self._pid_file)
        if self._filesystem.exists(self._config_file):
            self._filesystem.remove(self._config_file)

    def _check_and_kill(self):
        """Tries to kill wptserve.

        Returns True if it appears to be not running. Or, if it appears to be
        running, tries to kill the process and returns False.
        """
        if not self._pid:
            _log.warning('No PID; wptserve has not started.')
            return True

        # Polls the process in case it has died; otherwise, the process might be
        # defunct and check_running_pid can still succeed.
        if (self._process and self._process.poll()) or \
                (not self._executive.check_running_pid(self._pid)):
            _log.debug('pid %d is not running', self._pid)
            return True

        _log.debug('pid %d is running, killing it', self._pid)
        self._executive.kill_process(self._pid)

        return False