1# Copyright (C) 2011 Google Inc. All rights reserved. 2# 3# Redistribution and use in source and binary forms, with or without 4# modification, are permitted provided that the following conditions are 5# met: 6# 7# * Redistributions of source code must retain the above copyright 8# notice, this list of conditions and the following disclaimer. 9# * Redistributions in binary form must reproduce the above 10# copyright notice, this list of conditions and the following disclaimer 11# in the documentation and/or other materials provided with the 12# distribution. 13# * Neither the name of Google Inc. nor the names of its 14# contributors may be used to endorse or promote products derived from 15# this software without specific prior written permission. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29"""A class to help start/stop the lighttpd server used by layout tests.""" 30 31import logging 32import os 33import time 34 35from webkitpy.layout_tests.servers import http_server_base 36 37 38_log = logging.getLogger(__name__) 39 40 41class Lighttpd(http_server_base.HttpServerBase): 42 43 def __init__(self, port_obj, output_dir, background=False, port=None, 44 root=None, run_background=None, additional_dirs=None, 45 layout_tests_dir=None, number_of_servers=None): 46 """Args: 47 output_dir: the absolute path to the layout test result directory 48 """ 49 # Webkit tests 50 http_server_base.HttpServerBase.__init__(self, port_obj, number_of_servers) 51 self._name = 'lighttpd' 52 self._output_dir = output_dir 53 self._port = port 54 self._root = root 55 self._run_background = run_background 56 self._additional_dirs = additional_dirs 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, '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 if self._additional_dirs: 128 for alias, path in self._additional_dirs.iteritems(): 129 f.write(('alias.url += ( "%s" => "%s" )\n\n') % (alias, path)) 130 131 # Setup a link to where the media resources are stored. 132 f.write(('alias.url += ( "/media-resources" => "%s" )\n\n') % 133 (self._media_resource)) 134 135 # dump out of virtual host config at the bottom. 136 if self._root: 137 if self._port: 138 # Have both port and root dir. 139 mappings = [{'port': self._port, 'docroot': self._root}] 140 else: 141 # Have only a root dir - set the ports as for LayoutTests. 142 # This is used in ui_tests to run http tests against a browser. 143 144 # default set of ports as for LayoutTests but with a 145 # specified root. 146 mappings = [{'port': 8000, 'docroot': self._root}, 147 {'port': 8080, 'docroot': self._root}, 148 {'port': 8443, 'docroot': self._root, 149 'sslcert': self._pem_file}] 150 else: 151 mappings = self.VIRTUALCONFIG 152 for mapping in mappings: 153 ssl_setup = '' 154 if 'sslcert' in mapping: 155 ssl_setup = (' ssl.engine = "enable"\n' 156 ' ssl.pemfile = "%s"\n' % mapping['sslcert']) 157 158 f.write(('$SERVER["socket"] == "127.0.0.1:%d" {\n' 159 ' server.document-root = "%s"\n' + 160 ssl_setup + 161 '}\n\n') % (mapping['port'], mapping['docroot'])) 162 f.close() 163 164 executable = self._port_obj._path_to_lighttpd() 165 module_path = self._port_obj._path_to_lighttpd_modules() 166 start_cmd = [executable, 167 # Newly written config file 168 '-f', os.path.join(self._output_dir, 'lighttpd.conf'), 169 # Where it can find its module dynamic libraries 170 '-m', module_path] 171 172 if not self._run_background: 173 start_cmd.append(# Don't background 174 '-D') 175 176 # Copy liblightcomp.dylib to /tmp/lighttpd/lib to work around the 177 # bug that mod_alias.so loads it from the hard coded path. 178 if self._port_obj.host.platform.is_mac(): 179 tmp_module_path = '/tmp/lighttpd/lib' 180 if not self._filesystem.exists(tmp_module_path): 181 self._filesystem.maybe_make_directory(tmp_module_path) 182 lib_file = 'liblightcomp.dylib' 183 self._filesystem.copyfile(self._filesystem.join(module_path, lib_file), 184 self._filesystem.join(tmp_module_path, lib_file)) 185 186 self._start_cmd = start_cmd 187 self._env = self._port_obj.setup_environ_for_server('lighttpd') 188 self._mappings = mappings 189 190 def _remove_stale_logs(self): 191 # Sometimes logs are open in other processes but they should clear eventually. 192 for log_prefix in ('access.log-', 'error.log-'): 193 try: 194 self._remove_log_files(self._output_dir, log_prefix) 195 except OSError, e: 196 _log.warning('Failed to remove old %s %s files' % (self._name, log_prefix)) 197 198 def _spawn_process(self): 199 _log.debug('Starting %s server, cmd="%s"' % (self._name, self._start_cmd)) 200 process = self._executive.popen(self._start_cmd, env=self._env, shell=False, stderr=self._executive.PIPE) 201 pid = process.pid 202 self._filesystem.write_text_file(self._pid_file, str(pid)) 203 return pid 204 205 def _stop_running_server(self): 206 # FIXME: It would be nice if we had a cleaner way of killing this process. 207 # Currently we throw away the process object created in _spawn_process, 208 # since there doesn't appear to be any way to kill the server any more 209 # cleanly using it than just killing the pid, and we need to support 210 # killing a pid directly anyway for run-webkit-httpd and run-webkit-websocketserver. 211 self._wait_for_action(self._check_and_kill) 212 if self._filesystem.exists(self._pid_file): 213 self._filesystem.remove(self._pid_file) 214 215 def _check_and_kill(self): 216 if self._executive.check_running_pid(self._pid): 217 host = self._port_obj.host 218 if host.platform.is_win() and not host.platform.is_cygwin(): 219 # FIXME: https://bugs.webkit.org/show_bug.cgi?id=106838 220 # We need to kill all of the child processes as well as the 221 # parent, so we can't use executive.kill_process(). 222 # 223 # If this is actually working, we should figure out a clean API. 224 self._executive.run_command(["taskkill.exe", "/f", "/t", "/pid", self._pid], error_handler=self._executive.ignore_error) 225 else: 226 self._executive.kill_process(self._pid) 227 return False 228 return True 229