WebDriver: fix some nits that prevent running W3C tests on Mac
[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         self._wsout = None
47
48         tmpdir = tempfile.gettempdir()
49         if self._port.host.platform.is_mac():
50             tmpdir = '/tmp'
51         self._runtime_path = os.path.join(tmpdir, "WebKitWebDriverTests")
52         self._port.host.filesystem.maybe_make_directory(self._runtime_path)
53
54         self._pid_file = os.path.join(self._runtime_path, '%s.pid' % self._name)
55         self._servers_file = os.path.join(self._runtime_path, '%s_servers.json' % (self._name))
56
57         # FIXME: We use the runtime path for now for log output, since we don't have a results directory yet.
58         self._output_log_path = os.path.join(self._runtime_path, '%s_process_log.out.txt' % (self._name))
59
60     def _wait_for_server(self, wait_secs=20, sleep_secs=1):
61         def check_port(host, port):
62             s = socket.socket()
63             try:
64                 s.connect((host, port))
65             except IOError as e:
66                 if e.errno not in (errno.ECONNREFUSED, errno.ECONNRESET):
67                     raise
68                 return False
69             finally:
70                 s.close()
71             return True
72
73         start_time = time.time()
74         while time.time() - start_time < wait_secs:
75             if self._port._executive.check_running_pid(self._pid) and check_port(self._server_host, self._server_port):
76                 return True
77             time.sleep(sleep_secs)
78         return False
79
80     def start(self):
81         assert not self._pid, '%s server is already running' % self._name
82
83         # Stop any stale servers left over from previous instances.
84         if self._port.host.filesystem.exists(self._pid_file):
85             try:
86                 self._pid = int(self._port.host.filesystem.read_text_file(self._pid_file))
87                 self.stop()
88             except (ValueError, UnicodeDecodeError):
89                 # These could be raised if the pid file is corrupt.
90                 self._port.host.filesystem.remove(self._pid_file)
91                 self._pid = None
92
93         _log.debug('Copying WebDriver WPT server config.json')
94         doc_root = os.path.join(WebKitFinder(self._port.host.filesystem).path_from_webkit_base('WebDriverTests'), 'imported', 'w3c')
95         config_filename = os.path.join(doc_root, 'config.json')
96         config_json = self._port.host.filesystem.read_text_file(config_filename).replace("%DOC_ROOT%", doc_root)
97         self._port.host.filesystem.write_text_file(os.path.join(self._layout_doc_root, 'config.json'), config_json)
98         config = json.loads(config_json)
99         self._server_host = config['host']
100         self._server_port = config['ports']['http'][0]
101
102         self._wsout = self._port.host.filesystem.open_text_file_for_writing(self._output_log_path)
103         launcher = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'layout_tests', 'servers', 'web_platform_test_launcher.py'))
104         cmd = ['python', launcher, self._servers_file]
105         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)
106         self._pid = self._process.pid
107         self._port.host.filesystem.write_text_file(self._pid_file, str(self._pid))
108
109         if not self._wait_for_server():
110             _log.error('WPT Server process exited prematurely with status code %s' % self._process.returncode)
111             self.stop()
112             raise RuntimeError
113
114         _log.info('WebDriver WPT server listening at http://%s:%s/' % (self._server_host, self._server_port))
115
116     def stop(self):
117         _log.debug('Cleaning WebDriver WPT server config.json')
118         temporary_config_file = os.path.join(self._layout_doc_root, 'config.json')
119         if self._port.host.filesystem.exists(temporary_config_file):
120             self._port.host.filesystem.remove(temporary_config_file)
121         if self._process:
122             self._process.communicate(input='\n')
123         if self._wsout:
124             self._wsout.close()
125             self._wsout = None
126         if self._pid and self._port._executive.check_running_pid(self._pid):
127             _log.warning('Cannot stop %s server normally.' % (self._name))
128             _log.warning('Killing server launcher process (pid: %d).' % (self._pid))
129             self._port._executive.kill_process(self._pid)
130         self._port.host.filesystem.remove(self._pid_file)
131         self._pid = None
132
133     def host(self):
134         return self._server_host
135
136     def port(self):
137         return self._server_port