blob: ce0c7bdc8d441f3064798428f6da899de8b2ec77 [file] [log] [blame]
# Copyright (C) 2011 Google Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""A class to help start/stop the lighttpd server used by layout tests."""
import logging
import os
import time
from webkitpy.common.iteration_compatibility import iteritems
from webkitpy.layout_tests.servers import http_server_base
_log = logging.getLogger(__name__)
class Lighttpd(http_server_base.HttpServerBase):
def __init__(self, port_obj, output_dir, port=None,
root=None, run_background=None, additional_dirs=None,
layout_tests_dir=None):
"""Args:
output_dir: the absolute path to the layout test result directory
"""
# Webkit tests
http_server_base.HttpServerBase.__init__(self, port_obj)
self._name = 'lighttpd'
self._output_dir = output_dir
self._port = port
self._root = root
self._run_background = run_background
self._additional_dirs = additional_dirs
if layout_tests_dir:
self.tests_dir = layout_tests_dir
self._pid_file = self._filesystem.join(self._runtime_path, '%s.pid' % self._name)
if self._port:
self._port = int(self._port)
self._webkit_tests = os.path.join(self.tests_dir, 'http', 'tests')
# Self generated certificate for SSL server (for client cert get
# <base-path>\chrome\test\data\ssl\certs\root_ca_cert.crt)
self._pem_file = os.path.join(
os.path.dirname(os.path.abspath(__file__)), 'httpd2.pem')
# One mapping where we can get to everything
self.VIRTUALCONFIG = []
if self._webkit_tests:
self.VIRTUALCONFIG.extend(
# Three mappings (one with SSL) for LayoutTests http tests
[{'port': self.HTTP_SERVER_PORT, 'docroot': self._webkit_tests},
{'port': self.ALTERNATIVE_HTTP_SERVER_PORT, 'docroot': self._webkit_tests},
{'port': self.HTTPS_SERVER_PORT, 'docroot': self._webkit_tests,
'sslcert': self._pem_file}])
def _prepare_config(self):
base_conf_file = self._port_obj.path_from_webkit_base('Tools',
'Scripts', 'webkitpy', 'layout_tests', 'servers', 'lighttpd.conf')
out_conf_file = os.path.join(self._output_dir, 'lighttpd.conf')
time_str = time.strftime("%d%b%Y-%H%M%S")
access_file_name = "access.log-" + time_str + ".txt"
access_log = os.path.join(self._output_dir, access_file_name)
log_file_name = "error.log-" + time_str + ".txt"
error_log = os.path.join(self._output_dir, log_file_name)
if self._port_obj.get_option('http_access_log'):
access_log = self._port_obj.get_option('http_access_log')
if self._port_obj.get_option('http_error_log'):
error_log = self._port_obj.get_option('http_error_log')
# Write out the config
base_conf = self._filesystem.read_text_file(base_conf_file)
# FIXME: This should be re-worked so that this block can
# use with open() instead of a manual file.close() call.
f = self._filesystem.open_text_file_for_writing(out_conf_file)
f.write(base_conf)
# Write out our cgi handlers. Run perl through env so that it
# processes the #! line and runs perl with the proper command
# line arguments. Emulate apache's mod_asis with a cat cgi handler.
f.write('cgi.assign = ( ".cgi" => "/usr/bin/env",\n'
' ".pl" => "/usr/bin/env",\n'
' ".asis" => "/bin/cat",\n'
' ".py" => "/bin/python3" )\n\n')
# Setup log files
f.write(('server.errorlog = "%s"\n'
'accesslog.filename = "%s"\n\n') % (error_log, access_log))
# Setup upload folders. Upload folder is to hold temporary upload files
# and also POST data. This is used to support XHR layout tests that
# does POST.
f.write(('server.upload-dirs = ( "%s" )\n\n') % (self._output_dir))
# Setup a link to where the js test templates and media resources are stored.
operator = "="
for alias in self.aliases():
f.write(('alias.url %s ( "%s" => "%s" )\n\n') % (operator, alias[0], alias[1]))
operator = "+="
if self._additional_dirs:
for alias, path in iteritems(self._additional_dirs):
f.write(('alias.url += ( "%s" => "%s" )\n\n') % (alias, path))
# dump out of virtual host config at the bottom.
if self._root:
if self._port:
# Have both port and root dir.
mappings = [{'port': self._port, 'docroot': self._root}]
else:
# Have only a root dir - set the ports as for LayoutTests.
# This is used in ui_tests to run http tests against a browser.
# default set of ports as for LayoutTests but with a
# specified root.
mappings = [{'port': self.HTTP_SERVER_PORT, 'docroot': self._root},
{'port': self.ALTERNATIVE_HTTP_SERVER_PORT, 'docroot': self._root},
{'port': self.HTTPS_SERVER_PORT, 'docroot': self._root,
'sslcert': self._pem_file}]
else:
mappings = self.VIRTUALCONFIG
bind_address = '' if self._port_obj.get_option('http_all_addresses') else '127.0.0.1'
for mapping in mappings:
ssl_setup = ''
if 'sslcert' in mapping:
ssl_setup = (' ssl.engine = "enable"\n'
' ssl.pemfile = "%s"\n' % mapping['sslcert'])
f.write(('$SERVER["socket"] == "%s:%d" {\n'
' server.document-root = "%s"\n' +
ssl_setup +
'}\n\n') % (bind_address, mapping['port'], mapping['docroot']))
f.close()
executable = self._port_obj._path_to_lighttpd()
module_path = self._port_obj._path_to_lighttpd_modules()
start_cmd = [executable,
# Newly written config file
'-f', os.path.join(self._output_dir, 'lighttpd.conf'),
# Where it can find its module dynamic libraries
'-m', module_path]
if not self._run_background:
start_cmd.append('-D') # Don't background.
# Copy liblightcomp.dylib to /tmp/lighttpd/lib to work around the
# bug that mod_alias.so loads it from the hard coded path.
if self._port_obj.host.platform.is_mac():
tmp_module_path = '/tmp/lighttpd/lib'
if not self._filesystem.exists(tmp_module_path):
self._filesystem.maybe_make_directory(tmp_module_path)
lib_file = 'liblightcomp.dylib'
self._filesystem.copyfile(self._filesystem.join(module_path, lib_file),
self._filesystem.join(tmp_module_path, lib_file))
self._start_cmd = start_cmd
self._env = self._port_obj.setup_environ_for_server('lighttpd')
self._mappings = mappings
def _remove_stale_logs(self):
# Sometimes logs are open in other processes but they should clear eventually.
for log_prefix in ('access.log-', 'error.log-'):
try:
self._remove_log_files(self._output_dir, log_prefix)
except OSError:
_log.warning('Failed to remove old %s %s files' % (self._name, log_prefix))
def _spawn_process(self):
_log.debug('Starting %s server, cmd="%s"' % (self._name, self._start_cmd))
self._process = self._executive.popen(self._start_cmd, env=self._env, shell=False, stderr=self._executive.PIPE)
pid = self._process.pid
self._filesystem.write_text_file(self._pid_file, str(pid))
return pid
def _stop_running_server(self):
# FIXME: It would be nice if we had a cleaner way of killing this process.
# Currently we throw away the process object created in _spawn_process,
# since there doesn't appear to be any way to kill the server any more
# cleanly using it than just killing the pid, and we need to support
# killing a pid directly anyway for run-webkit-httpd and run-webkit-websocketserver.
self._wait_for_action(self._check_and_kill)
if self._filesystem.exists(self._pid_file):
self._filesystem.remove(self._pid_file)
def _check_and_kill(self):
if self._process is not None:
self._process.poll()
if self._process.returncode is not None:
return True
if self._executive.check_running_pid(self._pid):
host = self._port_obj.host
if host.platform.is_win() and not host.platform.is_cygwin():
# FIXME: https://bugs.webkit.org/show_bug.cgi?id=106838
# We need to kill all of the child processes as well as the
# parent, so we can't use executive.kill_process().
#
# If this is actually working, we should figure out a clean API.
self._executive.run_command(["taskkill.exe", "/f", "/t", "/pid", self._pid], ignore_errors=True)
else:
self._executive.kill_process(self._pid)
return False
return True
def _is_server_running_on_all_ports(self):
if self._process is not None:
self._process.poll()
if self._process.returncode is not None:
_log.debug("Server isn't running at all")
raise http_server_base.ServerError("Server exited")
return super(Lighttpd, self)._is_server_running_on_all_ports()