[GTK] Clear application cache between tests in DumpRenderTree
[WebKit-https.git] / Tools / Scripts / webkitpy / layout_tests / servers / http_server_base.py
1 #!/usr/bin/env python
2 # Copyright (C) 2011 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 """Base class with common routines between the Apache, Lighttpd, and websocket servers."""
31
32 import errno
33 import logging
34 import socket
35 import sys
36 import tempfile
37 import time
38
39
40 _log = logging.getLogger(__name__)
41
42
43 class ServerError(Exception):
44     pass
45
46
47 class HttpServerBase(object):
48     """A skeleton class for starting and stopping servers used by the layout tests."""
49
50     def __init__(self, port_obj, number_of_servers=None):
51         self._executive = port_obj._executive
52         self._filesystem = port_obj._filesystem
53         self._name = '<virtual>'
54         self._mappings = {}
55         self._pid = None
56         self._pid_file = None
57         self._port_obj = port_obj
58         self._number_of_servers = number_of_servers
59
60         # We need a non-checkout-dependent place to put lock files, etc. We
61         # don't use the Python default on the Mac because it defaults to a
62         # randomly-generated directory under /var/folders and no one would ever
63         # look there.
64         tmpdir = tempfile.gettempdir()
65         if port_obj.host.platform.is_mac():
66             tmpdir = '/tmp'
67
68         self._runtime_path = self._filesystem.join(tmpdir, "WebKit")
69         self._filesystem.maybe_make_directory(self._runtime_path)
70
71     def start(self):
72         """Starts the server. It is an error to start an already started server.
73
74         This method also stops any stale servers started by a previous instance."""
75         assert not self._pid, '%s server is already running' % self._name
76
77         # Stop any stale servers left over from previous instances.
78         if self._filesystem.exists(self._pid_file):
79             self._pid = int(self._filesystem.read_text_file(self._pid_file))
80             self._stop_running_server()
81             self._pid = None
82
83         self._remove_stale_logs()
84         self._prepare_config()
85         self._check_that_all_ports_are_available()
86
87         self._pid = self._spawn_process()
88
89         if self._wait_for_action(self._is_server_running_on_all_ports):
90             _log.debug("%s successfully started (pid = %d)" % (self._name, self._pid))
91         else:
92             self._stop_running_server()
93             raise ServerError('Failed to start %s server' % self._name)
94
95     def stop(self):
96         """Stops the server. Stopping a server that isn't started is harmless."""
97         actual_pid = None
98         if self._filesystem.exists(self._pid_file):
99             actual_pid = int(self._filesystem.read_text_file(self._pid_file))
100             if not self._pid:
101                 self._pid = actual_pid
102
103         if not self._pid:
104             return
105
106         if not actual_pid:
107             _log.warning('Failed to stop %s: pid file is missing' % self._name)
108             return
109         if self._pid != actual_pid:
110             _log.warning('Failed to stop %s: pid file contains %d, not %d' %
111                          (self._name, actual_pid, self._pid))
112             # Try to kill the existing pid, anyway, in case it got orphaned.
113             self._executive.kill_process(self._pid)
114             self._pid = None
115             return
116
117         _log.debug("Attempting to shut down %s server at pid %d" % (self._name, self._pid))
118         self._stop_running_server()
119         _log.debug("%s server at pid %d stopped" % (self._name, self._pid))
120         self._pid = None
121
122     def _prepare_config(self):
123         """This routine can be overridden by subclasses to do any sort
124         of initialization required prior to starting the server that may fail."""
125         pass
126
127     def _remove_stale_logs(self):
128         """This routine can be overridden by subclasses to try and remove logs
129         left over from a prior run. This routine should log warnings if the
130         files cannot be deleted, but should not fail unless failure to
131         delete the logs will actually cause start() to fail."""
132         pass
133
134     def _spawn_process(self):
135         """This routine must be implemented by subclasses to actually start the server.
136
137         This routine returns the pid of the started process, and also ensures that that
138         pid has been written to self._pid_file."""
139         raise NotImplementedError()
140
141     def _stop_running_server(self):
142         """This routine must be implemented by subclasses to actually stop the running server listed in self._pid_file."""
143         raise NotImplementedError()
144
145     # Utility routines.
146
147     def _remove_log_files(self, folder, starts_with):
148         files = self._filesystem.listdir(folder)
149         for file in files:
150             if file.startswith(starts_with):
151                 full_path = self._filesystem.join(folder, file)
152                 self._filesystem.remove(full_path)
153
154     def _wait_for_action(self, action, wait_secs=20.0, sleep_secs=1.0):
155         """Repeat the action for wait_sec or until it succeeds, sleeping for sleep_secs
156         in between each attempt. Returns whether it succeeded."""
157         start_time = time.time()
158         while time.time() - start_time < wait_secs:
159             if action():
160                 return True
161             _log.debug("Waiting for action: %s" % action)
162             time.sleep(sleep_secs)
163
164         return False
165
166     def _is_server_running_on_all_ports(self):
167         """Returns whether the server is running on all the desired ports."""
168         if not self._executive.check_running_pid(self._pid):
169             _log.debug("Server isn't running at all")
170             raise ServerError("Server exited")
171
172         for mapping in self._mappings:
173             s = socket.socket()
174             port = mapping['port']
175             try:
176                 s.connect(('localhost', port))
177                 _log.debug("Server running on %d" % port)
178             except IOError, e:
179                 if e.errno not in (errno.ECONNREFUSED, errno.ECONNRESET):
180                     raise
181                 _log.debug("Server NOT running on %d: %s" % (port, e))
182                 return False
183             finally:
184                 s.close()
185         return True
186
187     def _check_that_all_ports_are_available(self):
188         for mapping in self._mappings:
189             s = socket.socket()
190             s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
191             port = mapping['port']
192             try:
193                 s.bind(('localhost', port))
194             except IOError, e:
195                 if e.errno in (errno.EALREADY, errno.EADDRINUSE):
196                     raise ServerError('Port %d is already in use.' % port)
197                 elif sys.platform == 'win32' and e.errno in (errno.WSAEACCES,):
198                     raise ServerError('Port %d is already in use.' % port)
199                 else:
200                     raise
201             finally:
202                 s.close()