Clean up ChunkedUpdateDrawingAreaProxy
[WebKit-https.git] / WebKitTools / Scripts / webkitpy / layout_tests / port / websocket_server.py
1 #!/usr/bin/env python
2 # Copyright (C) 2010 Google 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 name of Google Inc. 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 """A class to help start/stop the PyWebSocket server used by layout tests."""
31
32
33 from __future__ import with_statement
34
35 import codecs
36 import logging
37 import optparse
38 import os
39 import subprocess
40 import sys
41 import tempfile
42 import time
43 import urllib
44
45 import factory
46 import http_server
47
48 from webkitpy.common.system.executive import Executive
49 from webkitpy.thirdparty.autoinstalled.pywebsocket import mod_pywebsocket
50
51
52 _log = logging.getLogger("webkitpy.layout_tests.port.websocket_server")
53
54 _WS_LOG_PREFIX = 'pywebsocket.ws.log-'
55 _WSS_LOG_PREFIX = 'pywebsocket.wss.log-'
56
57 _DEFAULT_WS_PORT = 8880
58 _DEFAULT_WSS_PORT = 9323
59
60
61 def url_is_alive(url):
62     """Checks to see if we get an http response from |url|.
63     We poll the url 20 times with a 0.5 second delay.  If we don't
64     get a reply in that time, we give up and assume the httpd
65     didn't start properly.
66
67     Args:
68       url: The URL to check.
69     Return:
70       True if the url is alive.
71     """
72     sleep_time = 0.5
73     wait_time = 10
74     while wait_time > 0:
75         try:
76             response = urllib.urlopen(url)
77             # Server is up and responding.
78             return True
79         except IOError:
80             pass
81         # Wait for sleep_time before trying again.
82         wait_time -= sleep_time
83         time.sleep(sleep_time)
84
85     return False
86
87
88 class PyWebSocketNotStarted(Exception):
89     pass
90
91
92 class PyWebSocketNotFound(Exception):
93     pass
94
95
96 class PyWebSocket(http_server.Lighttpd):
97
98     def __init__(self, port_obj, output_dir, port=_DEFAULT_WS_PORT,
99                  root=None, use_tls=False,
100                  pidfile=None):
101         """Args:
102           output_dir: the absolute path to the layout test result directory
103         """
104         http_server.Lighttpd.__init__(self, port_obj, output_dir,
105                                       port=_DEFAULT_WS_PORT,
106                                       root=root)
107         self._output_dir = output_dir
108         self._process = None
109         self._port = port
110         self._root = root
111         self._use_tls = use_tls
112         self._private_key = self._pem_file
113         self._certificate = self._pem_file
114         if self._port:
115             self._port = int(self._port)
116         if self._use_tls:
117             self._server_name = 'PyWebSocket(Secure)'
118         else:
119             self._server_name = 'PyWebSocket'
120         self._pidfile = pidfile
121         self._wsout = None
122
123         # Webkit tests
124         if self._root:
125             self._layout_tests = os.path.abspath(self._root)
126             self._web_socket_tests = os.path.abspath(
127                 os.path.join(self._root, 'http', 'tests',
128                              'websocket', 'tests'))
129         else:
130             try:
131                 self._layout_tests = self._port_obj.layout_tests_dir()
132                 self._web_socket_tests = os.path.join(self._layout_tests,
133                      'http', 'tests', 'websocket', 'tests')
134             except:
135                 self._web_socket_tests = None
136
137     def start(self):
138         if not self._web_socket_tests:
139             _log.info('No need to start %s server.' % self._server_name)
140             return
141         if self.is_running():
142             raise PyWebSocketNotStarted('%s is already running.' %
143                                         self._server_name)
144
145         time_str = time.strftime('%d%b%Y-%H%M%S')
146         if self._use_tls:
147             log_prefix = _WSS_LOG_PREFIX
148         else:
149             log_prefix = _WS_LOG_PREFIX
150         log_file_name = log_prefix + time_str
151
152         # Remove old log files. We only need to keep the last ones.
153         self.remove_log_files(self._output_dir, log_prefix)
154
155         error_log = os.path.join(self._output_dir, log_file_name + "-err.txt")
156
157         output_log = os.path.join(self._output_dir, log_file_name + "-out.txt")
158         self._wsout = codecs.open(output_log, "w", "utf-8")
159
160         python_interp = sys.executable
161         pywebsocket_base = os.path.join(
162             os.path.dirname(os.path.dirname(os.path.dirname(
163             os.path.abspath(__file__)))), 'thirdparty',
164             'autoinstalled', 'pywebsocket')
165         pywebsocket_script = os.path.join(pywebsocket_base, 'mod_pywebsocket',
166             'standalone.py')
167         start_cmd = [
168             python_interp, '-u', pywebsocket_script,
169             '--server-host', '127.0.0.1',
170             '--port', str(self._port),
171             '--document-root', os.path.join(self._layout_tests, 'http', 'tests'),
172             '--scan-dir', self._web_socket_tests,
173             '--cgi-paths', '/websocket/tests',
174             '--log-file', error_log,
175         ]
176
177         handler_map_file = os.path.join(self._web_socket_tests,
178                                         'handler_map.txt')
179         if os.path.exists(handler_map_file):
180             _log.debug('Using handler_map_file: %s' % handler_map_file)
181             start_cmd.append('--websock-handlers-map-file')
182             start_cmd.append(handler_map_file)
183         else:
184             _log.warning('No handler_map_file found')
185
186         if self._use_tls:
187             start_cmd.extend(['-t', '-k', self._private_key,
188                               '-c', self._certificate])
189
190         env = self._port_obj.setup_environ_for_server()
191         env['PYTHONPATH'] = (pywebsocket_base + os.path.pathsep +
192                              env.get('PYTHONPATH', ''))
193
194         _log.debug('Starting %s server on %d.' % (
195                    self._server_name, self._port))
196         _log.debug('cmdline: %s' % ' '.join(start_cmd))
197         # FIXME: We should direct this call through Executive for testing.
198         # Note: Not thread safe: http://bugs.python.org/issue2320
199         self._process = subprocess.Popen(start_cmd,
200                                          stdin=open(os.devnull, 'r'),
201                                          stdout=self._wsout,
202                                          stderr=subprocess.STDOUT,
203                                          env=env)
204
205         if self._use_tls:
206             url = 'https'
207         else:
208             url = 'http'
209         url = url + '://127.0.0.1:%d/' % self._port
210         if not url_is_alive(url):
211             if self._process.returncode == None:
212                 # FIXME: We should use a non-static Executive for easier
213                 # testing.
214                 Executive().kill_process(self._process.pid)
215             with codecs.open(output_log, "r", "utf-8") as fp:
216                 for line in fp:
217                     _log.error(line)
218             raise PyWebSocketNotStarted(
219                 'Failed to start %s server on port %s.' %
220                     (self._server_name, self._port))
221
222         # Our process terminated already
223         if self._process.returncode != None:
224             raise PyWebSocketNotStarted(
225                 'Failed to start %s server.' % self._server_name)
226         if self._pidfile:
227             with codecs.open(self._pidfile, "w", "ascii") as file:
228                 file.write("%d" % self._process.pid)
229
230     def stop(self, force=False):
231         if not force and not self.is_running():
232             return
233
234         pid = None
235         if self._process:
236             pid = self._process.pid
237         elif self._pidfile:
238             with codecs.open(self._pidfile, "r", "ascii") as file:
239                 pid = int(file.read().strip())
240
241         if not pid:
242             raise PyWebSocketNotFound(
243                 'Failed to find %s server pid.' % self._server_name)
244
245         _log.debug('Shutting down %s server %d.' % (self._server_name, pid))
246         # FIXME: We should use a non-static Executive for easier testing.
247         Executive().kill_process(pid)
248
249         if self._process:
250             # wait() is not threadsafe and can throw OSError due to:
251             # http://bugs.python.org/issue1731717
252             self._process.wait()
253             self._process = None
254
255         if self._wsout:
256             self._wsout.close()
257             self._wsout = None