# Copyright (C) 2017 Igalia S.L. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. 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. # # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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. import errno import json import logging import os import socket import tempfile import time from webkitpy.common.webkit_finder import WebKitFinder _log = logging.getLogger(__name__) class WebDriverW3CWebServer(object): def __init__(self, port): self._port = port self._name = "wptwd" layout_root = self._port.layout_tests_dir() self._layout_doc_root = os.path.join(layout_root, 'imported', 'w3c', 'web-platform-tests') self._process = None self._pid = None self._wsout = None tmpdir = tempfile.gettempdir() if self._port.host.platform.is_mac(): tmpdir = '/tmp' self._runtime_path = os.path.join(tmpdir, "WebKitWebDriverTests") self._port.host.filesystem.maybe_make_directory(self._runtime_path) self._pid_file = os.path.join(self._runtime_path, '%s.pid' % self._name) # FIXME: We use the runtime path for now for log output, since we don't have a results directory yet. self._output_log_path = os.path.join(self._runtime_path, '%s_process_log.out.txt' % (self._name)) def _wait_for_server(self, wait_secs=20, sleep_secs=1): def check_port(host, port): s = socket.socket() try: s.connect((host, port)) except IOError as e: if e.errno not in (errno.ECONNREFUSED, errno.ECONNRESET): raise return False finally: s.close() return True start_time = time.time() while time.time() - start_time < wait_secs: if self._port._executive.check_running_pid(self._pid) \ and check_port(self._server_host, self._server_http_port) \ and check_port(self._server_host, self._server_https_port): return True time.sleep(sleep_secs) return False def start(self): assert not self._pid, '%s server is already running' % self._name # Stop any stale servers left over from previous instances. if self._port.host.filesystem.exists(self._pid_file): try: self._pid = int(self._port.host.filesystem.read_text_file(self._pid_file)) self.stop() except (ValueError, UnicodeDecodeError): # These could be raised if the pid file is corrupt. self._port.host.filesystem.remove(self._pid_file) self._pid = None _log.debug('Copying WebDriver WPT server config.json') doc_root = os.path.join(WebKitFinder(self._port.host.filesystem).path_from_webkit_base('WebDriverTests'), 'imported', 'w3c') config_filename = os.path.join(doc_root, 'config.json') config = json.loads(self._port.host.filesystem.read_text_file(config_filename)) config['doc_root'] = doc_root config['ssl']['openssl']['base_path'] = os.path.join(self._runtime_path, '_wpt_certs') self._port.host.filesystem.write_text_file(os.path.join(self._layout_doc_root, 'config.json'), json.dumps(config)) self._server_host = config['browser_host'] self._server_http_port = config['ports']['http'][0] self._server_https_port = config['ports']['https'][0] self._wsout = self._port.host.filesystem.open_text_file_for_writing(self._output_log_path) wpt_file = os.path.join(self._layout_doc_root, "wpt.py") cmd = ["python3", wpt_file, "serve", "--config", os.path.join(self._layout_doc_root, 'config.json')] self._process = self._port._executive.popen(cmd, cwd=self._layout_doc_root, shell=False, stdin=self._port._executive.PIPE, stdout=self._wsout, stderr=self._wsout) self._pid = self._process.pid self._port.host.filesystem.write_text_file(self._pid_file, str(self._pid)) if not self._wait_for_server(): _log.error('WPT Server process exited prematurely with status code %s' % self._process.returncode) self.stop() raise RuntimeError _log.info('WebDriver WPT server listening at http://%s:%s/ and https://%s:%s/' % (self._server_host, self._server_http_port, self._server_host, self._server_https_port)) def stop(self): _log.debug('Cleaning WebDriver WPT server config.json') temporary_config_file = os.path.join(self._layout_doc_root, 'config.json') if self._port.host.filesystem.exists(temporary_config_file): self._port.host.filesystem.remove(temporary_config_file) if self._wsout: self._wsout.close() self._wsout = None if self._pid: # kill_process will not kill the subprocesses, interrupt does the job. self._port._executive.interrupt(self._pid) self._port.host.filesystem.remove(self._pid_file) self._pid = None def host(self): return self._server_host def http_port(self): return self._server_http_port def https_port(self): return self._server_https_port def document_root(self): return self._layout_doc_root # Waits indefinitely until the webserver process is terminated. def wait(self): if not self._pid: return self._process.wait() def __enter__(self): if not self._pid: self.start() return self def __exit__(self, exc_type, exc_value, exc_traceback): if self._pid: self.stop()