initial import
[vuplus_webkit] / Tools / Scripts / webkitpy / layout_tests / servers / http_server.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 """A class to help start/stop the lighttpd server used by layout tests."""
31
32 import logging
33 import os
34 import sys
35 import time
36
37 from webkitpy.layout_tests.servers import http_server_base
38
39
40 _log = logging.getLogger(__name__)
41
42
43 class Lighttpd(http_server_base.HttpServerBase):
44
45     def __init__(self, port_obj, output_dir, background=False, port=None,
46                  root=None, run_background=None, layout_tests_dir=None):
47         """Args:
48           output_dir: the absolute path to the layout test result directory
49         """
50         # Webkit tests
51         http_server_base.HttpServerBase.__init__(self, port_obj)
52         self._name = 'lighttpd'
53         self._output_dir = output_dir
54         self._port = port
55         self._root = root
56         self._run_background = run_background
57         self._layout_tests_dir = layout_tests_dir
58
59         self._pid_file = self._filesystem.join(self._runtime_path, '%s.pid' % self._name)
60
61         if self._port:
62             self._port = int(self._port)
63
64         if not self._layout_tests_dir:
65             self._layout_tests_dir = self._port_obj.layout_tests_dir()
66
67         self._webkit_tests = os.path.join(self._layout_tests_dir, 'http', 'tests')
68         self._js_test_resource = os.path.join(self._layout_tests_dir, 'fast', 'js', 'resources')
69         self._media_resource = os.path.join(self._layout_tests_dir, 'media')
70
71         # Self generated certificate for SSL server (for client cert get
72         # <base-path>\chrome\test\data\ssl\certs\root_ca_cert.crt)
73         self._pem_file = os.path.join(
74             os.path.dirname(os.path.abspath(__file__)), 'httpd2.pem')
75
76         # One mapping where we can get to everything
77         self.VIRTUALCONFIG = []
78
79         if self._webkit_tests:
80             self.VIRTUALCONFIG.extend(
81                # Three mappings (one with SSL) for LayoutTests http tests
82                [{'port': 8000, 'docroot': self._webkit_tests},
83                 {'port': 8080, 'docroot': self._webkit_tests},
84                 {'port': 8443, 'docroot': self._webkit_tests,
85                  'sslcert': self._pem_file}])
86
87     def _prepare_config(self):
88         base_conf_file = self._port_obj.path_from_webkit_base('Tools',
89             'Scripts', 'webkitpy', 'layout_tests', 'servers', 'lighttpd.conf')
90         out_conf_file = os.path.join(self._output_dir, 'lighttpd.conf')
91         time_str = time.strftime("%d%b%Y-%H%M%S")
92         access_file_name = "access.log-" + time_str + ".txt"
93         access_log = os.path.join(self._output_dir, access_file_name)
94         log_file_name = "error.log-" + time_str + ".txt"
95         error_log = os.path.join(self._output_dir, log_file_name)
96
97         # Write out the config
98         base_conf = self._filesystem.read_text_file(base_conf_file)
99
100         # FIXME: This should be re-worked so that this block can
101         # use with open() instead of a manual file.close() call.
102         f = self._filesystem.open_text_file_for_writing(out_conf_file)
103         f.write(base_conf)
104
105         # Write out our cgi handlers.  Run perl through env so that it
106         # processes the #! line and runs perl with the proper command
107         # line arguments. Emulate apache's mod_asis with a cat cgi handler.
108         f.write(('cgi.assign = ( ".cgi"  => "/usr/bin/env",\n'
109                  '               ".pl"   => "/usr/bin/env",\n'
110                  '               ".asis" => "/bin/cat",\n'
111                  '               ".php"  => "%s" )\n\n') %
112                                      self._port_obj._path_to_lighttpd_php())
113
114         # Setup log files
115         f.write(('server.errorlog = "%s"\n'
116                  'accesslog.filename = "%s"\n\n') % (error_log, access_log))
117
118         # Setup upload folders. Upload folder is to hold temporary upload files
119         # and also POST data. This is used to support XHR layout tests that
120         # does POST.
121         f.write(('server.upload-dirs = ( "%s" )\n\n') % (self._output_dir))
122
123         # Setup a link to where the js test templates are stored
124         f.write(('alias.url = ( "/js-test-resources" => "%s" )\n\n') %
125                     (self._js_test_resource))
126
127         # Setup a link to where the media resources are stored.
128         f.write(('alias.url += ( "/media-resources" => "%s" )\n\n') %
129                     (self._media_resource))
130
131         # dump out of virtual host config at the bottom.
132         if self._root:
133             if self._port:
134                 # Have both port and root dir.
135                 mappings = [{'port': self._port, 'docroot': self._root}]
136             else:
137                 # Have only a root dir - set the ports as for LayoutTests.
138                 # This is used in ui_tests to run http tests against a browser.
139
140                 # default set of ports as for LayoutTests but with a
141                 # specified root.
142                 mappings = [{'port': 8000, 'docroot': self._root},
143                             {'port': 8080, 'docroot': self._root},
144                             {'port': 8443, 'docroot': self._root,
145                              'sslcert': self._pem_file}]
146         else:
147             mappings = self.VIRTUALCONFIG
148         for mapping in mappings:
149             ssl_setup = ''
150             if 'sslcert' in mapping:
151                 ssl_setup = ('  ssl.engine = "enable"\n'
152                              '  ssl.pemfile = "%s"\n' % mapping['sslcert'])
153
154             f.write(('$SERVER["socket"] == "127.0.0.1:%d" {\n'
155                      '  server.document-root = "%s"\n' +
156                      ssl_setup +
157                      '}\n\n') % (mapping['port'], mapping['docroot']))
158         f.close()
159
160         executable = self._port_obj._path_to_lighttpd()
161         module_path = self._port_obj._path_to_lighttpd_modules()
162         start_cmd = [executable,
163                      # Newly written config file
164                      '-f', os.path.join(self._output_dir, 'lighttpd.conf'),
165                      # Where it can find its module dynamic libraries
166                      '-m', module_path]
167
168         if not self._run_background:
169             start_cmd.append(# Don't background
170                              '-D')
171
172         # Copy liblightcomp.dylib to /tmp/lighttpd/lib to work around the
173         # bug that mod_alias.so loads it from the hard coded path.
174         if sys.platform == 'darwin':
175             tmp_module_path = '/tmp/lighttpd/lib'
176             if not os.path.exists(tmp_module_path):
177                 os.makedirs(tmp_module_path)
178             lib_file = 'liblightcomp.dylib'
179             self._filesystem.copyfile(os.path.join(module_path, lib_file),
180                                       os.path.join(tmp_module_path, lib_file))
181
182         self._start_cmd = start_cmd
183         self._env = self._port_obj.setup_environ_for_server('lighttpd')
184         self._mappings = mappings
185
186     def _remove_stale_logs(self):
187         # Sometimes logs are open in other processes but they should clear eventually.
188         for log_prefix in ('access.log-', 'error.log-'):
189             try:
190                 self._remove_log_files(self._output_dir, log_prefix)
191             except OSError, e:
192                 _log.warning('Failed to remove old %s %s files' % self._name, log_prefix)
193
194     def _spawn_process(self):
195         _log.debug('Starting %s server, cmd="%s"' % (self._name, self._start_cmd))
196         process = self._executive.popen(self._start_cmd, env=self._env, shell=False, stderr=self._executive.PIPE)
197         pid = process.pid
198         self._filesystem.write_text_file(self._pid_file, str(pid))
199         return pid
200
201     def _stop_running_server(self):
202         # FIXME: It would be nice if we had a cleaner way of killing this process.
203         # Currently we throw away the process object created in _spawn_process,
204         # since there doesn't appear to be any way to kill the server any more
205         # cleanly using it than just killing the pid, and we need to support
206         # killing a pid directly anyway for run-webkit-httpd and run-webkit-websocketserver.
207         self._wait_for_action(self._check_and_kill)
208         if self._filesystem.exists(self._pid_file):
209             self._filesystem.remove(self._pid_file)
210
211     def _check_and_kill(self):
212         if self._executive.check_running_pid(self._pid):
213             self._executive.kill_process(self._pid)
214             return False
215         return True