c97ea67469b62623cc1021796963ab69e56203a9
[WebKit-https.git] / Tools / Scripts / webkitpy / webdriver_tests / webdriver_w3c_web_server.py
1 # Copyright (C) 2017 Igalia S.L.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions
5 # are met:
6 # 1.  Redistributions of source code must retain the above copyright
7 #     notice, this list of conditions and the following disclaimer.
8 # 2.  Redistributions in binary form must reproduce the above copyright
9 #     notice, this list of conditions and the following disclaimer in the
10 #     documentation and/or other materials provided with the distribution.
11 #
12 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
13 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15 # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
16 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
23 import errno
24 import json
25 import logging
26 import os
27 import socket
28 import tempfile
29 import time
30
31 from webkitpy.common.webkit_finder import WebKitFinder
32
33 _log = logging.getLogger(__name__)
34
35
36 class WebDriverW3CWebServer(object):
37
38     def __init__(self, port):
39         self._port = port
40         self._name = "wptwd"
41
42         layout_root = self._port.layout_tests_dir()
43         self._layout_doc_root = os.path.join(layout_root, 'imported', 'w3c', 'web-platform-tests')
44         self._process = None
45         self._pid = None
46
47         tmpdir = tempfile.gettempdir()
48         if self._port.host.platform.is_mac():
49             tmpdir = '/tmp'
50         self._runtime_path = os.path.join(tmpdir, "WebKitWebDriverTests")
51         self._port.host.filesystem.maybe_make_directory(self._runtime_path)
52
53         self._pid_file = os.path.join(self._runtime_path, '%s.pid' % self._name)
54         self._servers_file = os.path.join(self._runtime_path, '%s_servers.json' % (self._name))
55
56         # FIXME: We use the runtime path for now for log output, since we don't have a results directory yet.
57         self._output_log_path = os.path.join(self._runtime_path, '%s_process_log.out.txt' % (self._name))
58
59     def _wait_for_server(self, wait_secs=20, sleep_secs=1):
60         def check_port(host, port):
61             s = socket.socket()
62             try:
63                 s.connect((host, port))
64             except IOError as e:
65                 if e.errno not in (errno.ECONNREFUSED, errno.ECONNRESET):
66                     raise
67                 return False
68             finally:
69                 s.close()
70             return True
71
72         start_time = time.time()
73         while time.time() - start_time < wait_secs:
74             if self._port._executive.check_running_pid(self._pid) and check_port(self._server_host, self._server_port):
75                 return True
76             time.sleep(sleep_secs)
77         return False
78
79     def start(self):
80         assert not self._pid, '%s server is already running' % self._name
81
82         # Stop any stale servers left over from previous instances.
83         if self._port.host.filesystem.exists(self._pid_file):
84             try:
85                 self._pid = int(self._port.host.filesystem.read_text_file(self._pid_file))
86                 self.stop()
87             except (ValueError, UnicodeDecodeError):
88                 # These could be raised if the pid file is corrupt.
89                 self._port.host.filesystem.remove(self._pid_file)
90                 self._pid = None
91
92         _log.debug('Copying WebDriver WPT server config.json')
93         doc_root = os.path.join(WebKitFinder(self._port.host.filesystem).path_from_webkit_base('WebDriverTests'), 'imported', 'w3c')
94         config_filename = os.path.join(doc_root, 'config.json')
95         config_json = self._port.host.filesystem.read_text_file(config_filename).replace("%DOC_ROOT%", doc_root)
96         self._port.host.filesystem.write_text_file(os.path.join(self._layout_doc_root, 'config.json'), config_json)
97         config = json.loads(config_json)
98         self._server_host = config['host']
99         self._server_port = config['ports']['http'][0]
100
101         self._wsout = self._port.host.filesystem.open_text_file_for_writing(self._output_log_path)
102         launcher = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'layout_tests', 'servers', 'web_platform_test_launcher.py'))
103         cmd = ['python', launcher, self._servers_file]
104         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)
105         self._pid = self._process.pid
106         self._port.host.filesystem.write_text_file(self._pid_file, str(self._pid))
107
108         if not self._wait_for_server():
109             _log.error('WPT Server process exited prematurely with status code %s' % self._process.returncode)
110             self.stop()
111             raise RuntimeError
112
113         _log.info('WebDriver WPT server listening at http://%s:%s/' % (self._server_host, self._server_port))
114
115     def stop(self):
116         _log.debug('Cleaning WebDriver WPT server config.json')
117         self._port.host.filesystem.remove(os.path.join(self._layout_doc_root, 'config.json'))
118         if self._process:
119             self._process.communicate(input='\n')
120         if self._wsout:
121             self._wsout.close()
122             self._wsout = None
123         if self._pid and self._port._executive.check_running_pid(self._pid):
124             _log.warning('Cannot stop %s server normally.' % (self._name))
125             _log.warning('Killing server launcher process (pid: %d).' % (self._pid))
126             self._port._executive.kill_process(self._pid)
127         self._port.host.filesystem.remove(self._pid_file)
128         self._pid = None
129
130     def host(self):
131         return self._server_host
132
133     def port(self):
134         return self._server_port