| # 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() |