WPT server should be able to do logging even if its output folder is not created...
[WebKit-https.git] / Tools / Scripts / webkitpy / layout_tests / servers / web_platform_test_server.py
1 #  Copyright (c) 2014, Canon Inc. All rights reserved.
2 #  Redistribution and use in source and binary forms, with or without
3 #  modification, are permitted provided that the following conditions
4 #  are met:
5 #  1.  Redistributions of source code must retain the above copyright
6 #      notice, this list of conditions and the following disclaimer.
7 #  2.  Redistributions in binary form must reproduce the above copyright
8 #      notice, this list of conditions and the following disclaimer in the
9 #      documentation and/or other materials provided with the distribution.
10 #  3.  Neither the name of Canon Inc. nor the names of
11 #      its contributors may be used to endorse or promote products derived
12 #      from this software without specific prior written permission.
13 #  THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY
14 #  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 #  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 #  DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR
17 #  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 #  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 #  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20 #  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
21 #  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22 #  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
24 import json
25 import logging
26 import time
27
28 from webkitpy.layout_tests.servers import http_server_base
29
30 _log = logging.getLogger(__name__)
31
32
33 def doc_root(port_obj):
34     doc_root = port_obj.get_option("wptserver_doc_root")
35     if doc_root is None:
36         return port_obj.host.filesystem.join("imported", "w3c", "web-platform-tests")
37     return doc_root
38
39
40 def wpt_config_json(port_obj):
41     config_wk_filepath = port_obj._filesystem.join(port_obj.layout_tests_dir(), "imported", "w3c", "resources", "config.json")
42     if not port_obj.host.filesystem.isfile(config_wk_filepath):
43         return
44     json_data = port_obj._filesystem.read_text_file(config_wk_filepath)
45     return json.loads(json_data)
46
47
48 def base_http_url(port_obj):
49     config = wpt_config_json(port_obj)
50     if not config:
51         # This should only be hit by webkitpy unit tests
52         _log.debug("No WPT config file found")
53         return "http://localhost:8800/"
54     ports = config["ports"]
55     return "http://" + config["host"] + ":" + str(ports["http"][0]) + "/"
56
57
58 def base_https_url(port_obj):
59     config = wpt_config_json(port_obj)
60     if not config:
61         # This should only be hit by webkitpy unit tests
62         _log.debug("No WPT config file found")
63         return "https://localhost:9443/"
64     ports = config["ports"]
65     return "https://" + config["host"] + ":" + str(ports["https"][0]) + "/"
66
67
68 def is_wpt_server_running(port_obj):
69     config = wpt_config_json(port_obj)
70     if not config:
71         return False
72     return http_server_base.HttpServerBase._is_running_on_port(config["ports"]["http"][0])
73
74
75 class WebPlatformTestServer(http_server_base.HttpServerBase):
76     def __init__(self, port_obj, name, pidfile=None):
77         http_server_base.HttpServerBase.__init__(self, port_obj)
78         self._output_dir = port_obj.results_directory()
79
80         self._name = name
81         self._log_file_name = '%s_process_log.out.txt' % (self._name)
82
83         self._output_log_path = None
84         self._wsout = None
85         self._process = None
86         self._pid_file = pidfile
87         if not self._pid_file:
88             self._pid_file = self._filesystem.join(self._runtime_path, '%s.pid' % self._name)
89         self._servers_file = self._filesystem.join(self._runtime_path, '%s_servers.json' % (self._name))
90
91         self._filesystem = port_obj.host.filesystem
92         self._layout_root = port_obj.layout_tests_dir()
93         self._doc_root = self._filesystem.join(self._layout_root, doc_root(port_obj))
94
95         self._resources_files_to_copy = ['testharness.css', 'testharnessreport.js']
96
97         current_dir_path = self._filesystem.abspath(self._filesystem.split(__file__)[0])
98         self._start_cmd = ["python", self._filesystem.join(current_dir_path, "web_platform_test_launcher.py"), self._servers_file]
99         self._doc_root_path = self._filesystem.join(self._layout_root, self._doc_root)
100
101         self._mappings = []
102         config = wpt_config_json(port_obj)
103         if config:
104             ports = config["ports"]
105             for key in ports:
106                 for value in ports[key]:
107                     port = {"port": value}
108                     if key == "https":
109                         port["sslcert"] = True
110                     self._mappings.append(port)
111
112     def ports_to_forward(self):
113         return [mapping['port'] for mapping in self._mappings]
114
115     def _copy_webkit_test_files(self):
116         _log.debug('Copying WebKit resources files')
117         for f in self._resources_files_to_copy:
118             webkit_filename = self._filesystem.join(self._layout_root, "resources", f)
119             if self._filesystem.isfile(webkit_filename):
120                 self._filesystem.copyfile(webkit_filename, self._filesystem.join(self._doc_root, "resources", f))
121         _log.debug('Copying WebKit web platform server config.json')
122         config_wk_filename = self._filesystem.join(self._layout_root, "imported", "w3c", "resources", "config.json")
123         if self._filesystem.isfile(config_wk_filename):
124             config_json = self._filesystem.read_text_file(config_wk_filename).replace("%CERTS_DIR%", self._filesystem.join(self._output_dir, "_wpt_certs"))
125             self._filesystem.write_text_file(self._filesystem.join(self._doc_root, "config.json"), config_json)
126
127         wpt_testharnessjs_file = self._filesystem.join(self._doc_root, "resources", "testharness.js")
128         layout_tests_testharnessjs_file = self._filesystem.join(self._layout_root, "resources", "testharness.js")
129         if (not self._filesystem.compare(wpt_testharnessjs_file, layout_tests_testharnessjs_file)):
130             _log.warning("\n//////////\nWPT tests are not using the same testharness.js file as other WebKit Layout tests.\nWebKit testharness.js might need to be updated according WPT testharness.js.\n//////////\n")
131
132     def _clean_webkit_test_files(self):
133         _log.debug('Cleaning WPT resources files')
134         for f in self._resources_files_to_copy:
135             wpt_filename = self._filesystem.join(self._doc_root, "resources", f)
136             if self._filesystem.isfile(wpt_filename):
137                 self._filesystem.remove(wpt_filename)
138         _log.debug('Cleaning WPT web platform server config.json')
139         config_wpt_filename = self._filesystem.join(self._doc_root, "config.json")
140         if self._filesystem.isfile(config_wpt_filename):
141             self._filesystem.remove(config_wpt_filename)
142
143     def _prepare_config(self):
144         self._filesystem.maybe_make_directory(self._output_dir)
145         self._output_log_path = self._filesystem.join(self._output_dir, self._log_file_name)
146         self._wsout = self._filesystem.open_text_file_for_writing(self._output_log_path)
147         self._copy_webkit_test_files()
148
149     def _spawn_process(self):
150         self._process = self._executive.popen(self._start_cmd, cwd=self._doc_root_path, shell=False, stdin=self._executive.PIPE, stdout=self._wsout, stderr=self._wsout)
151         self._filesystem.write_text_file(self._pid_file, str(self._process.pid))
152
153         # Wait a second for the server to actually start so that tests do not start until server is running.
154         time.sleep(1)
155
156         # The server is not running after 1 second, something went wrong.
157         if self._process.poll() is not None:
158             self._stop_running_server()
159             error_log = ('WPT Server process exited prematurely with status code %s\n' % self._process.returncode
160                          + 'The cmdline for running the WPT server was: %s\n' % self._start_cmd
161                          + 'The working dir was: %s\n' % self._doc_root_path)
162             if self._output_log_path is not None and self._filesystem.exists(self._output_log_path):
163                 error_log += 'Check the logfile for the command at: %s\n' % self._output_log_path
164             raise http_server_base.ServerError(error_log)
165
166         return self._process.pid
167
168     def _stop_running_subservers(self):
169         if self._filesystem.exists(self._servers_file):
170             try:
171                 json_data = self._filesystem.read_text_file(self._servers_file)
172                 started_servers = json.loads(json_data)
173                 for server in started_servers:
174                     if self._executive.check_running_pid(server['pid']):
175                         _log.warning('Killing server process (protocol: %s , port: %d, pid: %d).' % (server['protocol'], server['port'], server['pid']))
176                         self._executive.kill_process(server['pid'])
177             finally:
178                 self._filesystem.remove(self._servers_file)
179
180     def stop(self):
181         super(WebPlatformTestServer, self).stop()
182         # In case of orphaned pid, kill the running subservers if any still alive.
183         self._stop_running_subservers()
184
185     def _stop_running_server(self):
186         _log.debug('Stopping %s server' % (self._name))
187         self._clean_webkit_test_files()
188
189         if self._process:
190             self._process.communicate(input='\n')
191         if self._wsout:
192             self._wsout.close()
193             self._wsout = None
194
195         if self._pid and self._executive.check_running_pid(self._pid):
196             _log.warning('Cannot stop %s server normally.' % (self._name))
197             _log.warning('Killing server launcher process (pid: %d).' % (self._pid))
198             self._executive.kill_process(self._pid)
199
200         self._remove_pid_file()
201         self._stop_running_subservers()