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 lighttpd server used by layout tests.""" 31 32 33import logging 34import optparse 35import os 36import shutil 37import subprocess 38import sys 39import tempfile 40import time 41import urllib 42 43import http_server_base 44 45 46class HttpdNotStarted(Exception): 47 pass 48 49 50class Lighttpd(http_server_base.HttpServerBase): 51 52 def __init__(self, port_obj, output_dir, background=False, port=None, 53 root=None, register_cygwin=None, run_background=None): 54 """Args: 55 output_dir: the absolute path to the layout test result directory 56 """ 57 # Webkit tests 58 http_server_base.HttpServerBase.__init__(self, port_obj) 59 self._output_dir = output_dir 60 self._process = None 61 self._port = port 62 self._root = root 63 self._register_cygwin = register_cygwin 64 self._run_background = run_background 65 if self._port: 66 self._port = int(self._port) 67 68 try: 69 self._webkit_tests = os.path.join( 70 self._port_obj.layout_tests_dir(), 'http', 'tests') 71 self._js_test_resource = os.path.join( 72 self._port_obj.layout_tests_dir(), 'fast', 'js', 'resources') 73 except: 74 self._webkit_tests = None 75 self._js_test_resource = None 76 77 # Self generated certificate for SSL server (for client cert get 78 # <base-path>\chrome\test\data\ssl\certs\root_ca_cert.crt) 79 self._pem_file = os.path.join( 80 os.path.dirname(os.path.abspath(__file__)), 'httpd2.pem') 81 82 # One mapping where we can get to everything 83 self.VIRTUALCONFIG = [] 84 85 if self._webkit_tests: 86 self.VIRTUALCONFIG.extend( 87 # Three mappings (one with SSL) for LayoutTests http tests 88 [{'port': 8000, 'docroot': self._webkit_tests}, 89 {'port': 8080, 'docroot': self._webkit_tests}, 90 {'port': 8443, 'docroot': self._webkit_tests, 91 'sslcert': self._pem_file}]) 92 93 def is_running(self): 94 return self._process != None 95 96 def start(self): 97 if self.is_running(): 98 raise 'Lighttpd already running' 99 100 base_conf_file = self._port_obj.path_from_webkit_base('WebKitTools', 101 'Scripts', 'webkitpy', 'layout_tests', 'port', 'lighttpd.conf') 102 out_conf_file = os.path.join(self._output_dir, 'lighttpd.conf') 103 time_str = time.strftime("%d%b%Y-%H%M%S") 104 access_file_name = "access.log-" + time_str + ".txt" 105 access_log = os.path.join(self._output_dir, access_file_name) 106 log_file_name = "error.log-" + time_str + ".txt" 107 error_log = os.path.join(self._output_dir, log_file_name) 108 109 # Remove old log files. We only need to keep the last ones. 110 self.remove_log_files(self._output_dir, "access.log-") 111 self.remove_log_files(self._output_dir, "error.log-") 112 113 # Write out the config 114 f = file(base_conf_file, 'rb') 115 base_conf = f.read() 116 f.close() 117 118 f = file(out_conf_file, 'wb') 119 f.write(base_conf) 120 121 # Write out our cgi handlers. Run perl through env so that it 122 # processes the #! line and runs perl with the proper command 123 # line arguments. Emulate apache's mod_asis with a cat cgi handler. 124 f.write(('cgi.assign = ( ".cgi" => "/usr/bin/env",\n' 125 ' ".pl" => "/usr/bin/env",\n' 126 ' ".asis" => "/bin/cat",\n' 127 ' ".php" => "%s" )\n\n') % 128 self._port_obj._path_to_lighttpd_php()) 129 130 # Setup log files 131 f.write(('server.errorlog = "%s"\n' 132 'accesslog.filename = "%s"\n\n') % (error_log, access_log)) 133 134 # Setup upload folders. Upload folder is to hold temporary upload files 135 # and also POST data. This is used to support XHR layout tests that 136 # does POST. 137 f.write(('server.upload-dirs = ( "%s" )\n\n') % (self._output_dir)) 138 139 # Setup a link to where the js test templates are stored 140 f.write(('alias.url = ( "/js-test-resources" => "%s" )\n\n') % 141 (self._js_test_resource)) 142 143 # dump out of virtual host config at the bottom. 144 if self._root: 145 if self._port: 146 # Have both port and root dir. 147 mappings = [{'port': self._port, 'docroot': self._root}] 148 else: 149 # Have only a root dir - set the ports as for LayoutTests. 150 # This is used in ui_tests to run http tests against a browser. 151 152 # default set of ports as for LayoutTests but with a 153 # specified root. 154 mappings = [{'port': 8000, 'docroot': self._root}, 155 {'port': 8080, 'docroot': self._root}, 156 {'port': 8443, 'docroot': self._root, 157 'sslcert': self._pem_file}] 158 else: 159 mappings = self.VIRTUALCONFIG 160 for mapping in mappings: 161 ssl_setup = '' 162 if 'sslcert' in mapping: 163 ssl_setup = (' ssl.engine = "enable"\n' 164 ' ssl.pemfile = "%s"\n' % mapping['sslcert']) 165 166 f.write(('$SERVER["socket"] == "127.0.0.1:%d" {\n' 167 ' server.document-root = "%s"\n' + 168 ssl_setup + 169 '}\n\n') % (mapping['port'], mapping['docroot'])) 170 f.close() 171 172 executable = self._port_obj._path_to_lighttpd() 173 module_path = self._port_obj._path_to_lighttpd_modules() 174 start_cmd = [executable, 175 # Newly written config file 176 '-f', os.path.join(self._output_dir, 'lighttpd.conf'), 177 # Where it can find its module dynamic libraries 178 '-m', module_path] 179 180 if not self._run_background: 181 start_cmd.append(# Don't background 182 '-D') 183 184 # Copy liblightcomp.dylib to /tmp/lighttpd/lib to work around the 185 # bug that mod_alias.so loads it from the hard coded path. 186 if sys.platform == 'darwin': 187 tmp_module_path = '/tmp/lighttpd/lib' 188 if not os.path.exists(tmp_module_path): 189 os.makedirs(tmp_module_path) 190 lib_file = 'liblightcomp.dylib' 191 shutil.copyfile(os.path.join(module_path, lib_file), 192 os.path.join(tmp_module_path, lib_file)) 193 194 # Put the cygwin directory first in the path to find cygwin1.dll 195 env = os.environ 196 if sys.platform in ('cygwin', 'win32'): 197 env['PATH'] = '%s;%s' % ( 198 self._port_obj.path_from_chromium_base('third_party', 199 'cygwin', 'bin'), 200 env['PATH']) 201 202 if sys.platform == 'win32' and self._register_cygwin: 203 setup_mount = port.path_from_chromium_base('third_party', 204 'cygwin', 'setup_mount.bat') 205 subprocess.Popen(setup_mount).wait() 206 207 logging.debug('Starting http server') 208 self._process = subprocess.Popen(start_cmd, env=env) 209 210 # Wait for server to start. 211 self.mappings = mappings 212 server_started = self.wait_for_action( 213 self.is_server_running_on_all_ports) 214 215 # Our process terminated already 216 if not server_started or self._process.returncode != None: 217 raise google.httpd_utils.HttpdNotStarted('Failed to start httpd.') 218 219 logging.debug("Server successfully started") 220 221 # TODO(deanm): Find a nicer way to shutdown cleanly. Our log files are 222 # probably not being flushed, etc... why doesn't our python have os.kill ? 223 224 def stop(self, force=False): 225 if not force and not self.is_running(): 226 return 227 228 httpd_pid = None 229 if self._process: 230 httpd_pid = self._process.pid 231 self._port_obj._shut_down_http_server(httpd_pid) 232 233 if self._process: 234 self._process.wait() 235 self._process = None 236 237if '__main__' == __name__: 238 # Provide some command line params for starting/stopping the http server 239 # manually. Also used in ui_tests to run http layout tests in a browser. 240 option_parser = optparse.OptionParser() 241 option_parser.add_option('-k', '--server', 242 help='Server action (start|stop)') 243 option_parser.add_option('-p', '--port', 244 help='Port to listen on (overrides layout test ports)') 245 option_parser.add_option('-r', '--root', 246 help='Absolute path to DocumentRoot (overrides layout test roots)') 247 option_parser.add_option('--register_cygwin', action="store_true", 248 dest="register_cygwin", help='Register Cygwin paths (on Win try bots)') 249 option_parser.add_option('--run_background', action="store_true", 250 dest="run_background", 251 help='Run on background (for running as UI test)') 252 options, args = option_parser.parse_args() 253 254 if not options.server: 255 print ('Usage: %s --server {start|stop} [--root=root_dir]' 256 ' [--port=port_number]' % sys.argv[0]) 257 else: 258 if (options.root is None) and (options.port is not None): 259 # specifying root but not port means we want httpd on default 260 # set of ports that LayoutTest use, but pointing to a different 261 # source of tests. Specifying port but no root does not seem 262 # meaningful. 263 raise 'Specifying port requires also a root.' 264 httpd = Lighttpd(tempfile.gettempdir(), 265 port=options.port, 266 root=options.root, 267 register_cygwin=options.register_cygwin, 268 run_background=options.run_background) 269 if 'start' == options.server: 270 httpd.start() 271 else: 272 httpd.stop(force=True) 273