73b54ceb9f3aa2d7d86f23a262568575609d4156
[WebKit-https.git] / WebDriverTests / imported / w3c / tools / wptrunner / wptrunner / environment.py
1 import json
2 import os
3 import multiprocessing
4 import signal
5 import socket
6 import sys
7 import time
8
9 from mozlog import get_default_logger, handlers, proxy
10
11 from wptlogging import LogLevelRewriter
12
13 here = os.path.split(__file__)[0]
14
15 serve = None
16 sslutils = None
17
18
19 hostnames = ["web-platform.test",
20              "www.web-platform.test",
21              "www1.web-platform.test",
22              "www2.web-platform.test",
23              "xn--n8j6ds53lwwkrqhv28a.web-platform.test",
24              "xn--lve-6lad.web-platform.test"]
25
26
27 def do_delayed_imports(logger, test_paths):
28     global serve, sslutils
29
30     serve_root = serve_path(test_paths)
31     sys.path.insert(0, serve_root)
32
33     failed = []
34
35     try:
36         from tools.serve import serve
37     except ImportError:
38         from wpt_tools.serve import serve
39     except ImportError:
40         failed.append("serve")
41
42     try:
43         import sslutils
44     except ImportError:
45         failed.append("sslutils")
46
47     if failed:
48         logger.critical(
49             "Failed to import %s. Ensure that tests path %s contains web-platform-tests" %
50             (", ".join(failed), serve_root))
51         sys.exit(1)
52
53
54 def serve_path(test_paths):
55     return test_paths["/"]["tests_path"]
56
57
58 def get_ssl_kwargs(**kwargs):
59     if kwargs["ssl_type"] == "openssl":
60         args = {"openssl_binary": kwargs["openssl_binary"]}
61     elif kwargs["ssl_type"] == "pregenerated":
62         args = {"host_key_path": kwargs["host_key_path"],
63                 "host_cert_path": kwargs["host_cert_path"],
64                  "ca_cert_path": kwargs["ca_cert_path"]}
65     else:
66         args = {}
67     return args
68
69
70 def ssl_env(logger, **kwargs):
71     ssl_env_cls = sslutils.environments[kwargs["ssl_type"]]
72     return ssl_env_cls(logger, **get_ssl_kwargs(**kwargs))
73
74
75 class TestEnvironmentError(Exception):
76     pass
77
78
79 class TestEnvironment(object):
80     def __init__(self, test_paths, ssl_env, pause_after_test, debug_info, options, env_extras):
81         """Context manager that owns the test environment i.e. the http and
82         websockets servers"""
83         self.test_paths = test_paths
84         self.ssl_env = ssl_env
85         self.server = None
86         self.config = None
87         self.external_config = None
88         self.pause_after_test = pause_after_test
89         self.test_server_port = options.pop("test_server_port", True)
90         self.debug_info = debug_info
91         self.options = options if options is not None else {}
92
93         self.cache_manager = multiprocessing.Manager()
94         self.stash = serve.stash.StashServer()
95         self.env_extras = env_extras
96
97
98     def __enter__(self):
99         self.stash.__enter__()
100         self.ssl_env.__enter__()
101         self.cache_manager.__enter__()
102         for cm in self.env_extras:
103             cm.__enter__(self.options)
104         self.setup_server_logging()
105         self.config = self.load_config()
106         serve.set_computed_defaults(self.config)
107         self.external_config, self.servers = serve.start(self.config, self.ssl_env,
108                                                          self.get_routes())
109         if self.options.get("supports_debugger") and self.debug_info and self.debug_info.interactive:
110             self.ignore_interrupts()
111         return self
112
113     def __exit__(self, exc_type, exc_val, exc_tb):
114         self.process_interrupts()
115
116         for scheme, servers in self.servers.iteritems():
117             for port, server in servers:
118                 server.kill()
119         for cm in self.env_extras:
120             cm.__exit__(exc_type, exc_val, exc_tb)
121         self.cache_manager.__exit__(exc_type, exc_val, exc_tb)
122         self.ssl_env.__exit__(exc_type, exc_val, exc_tb)
123         self.stash.__exit__()
124
125     def ignore_interrupts(self):
126         signal.signal(signal.SIGINT, signal.SIG_IGN)
127
128     def process_interrupts(self):
129         signal.signal(signal.SIGINT, signal.SIG_DFL)
130
131     def load_config(self):
132         default_config_path = os.path.join(serve_path(self.test_paths), "config.default.json")
133         local_config_path = os.path.join(here, "config.json")
134
135         with open(default_config_path) as f:
136             default_config = json.load(f)
137
138         with open(local_config_path) as f:
139             data = f.read()
140             local_config = json.loads(data % self.options)
141
142         #TODO: allow non-default configuration for ssl
143
144         local_config["external_host"] = self.options.get("external_host", None)
145         local_config["ssl"]["encrypt_after_connect"] = self.options.get("encrypt_after_connect", False)
146
147         config = serve.merge_json(default_config, local_config)
148         config["doc_root"] = serve_path(self.test_paths)
149
150         if not self.ssl_env.ssl_enabled:
151             config["ports"]["https"] = [None]
152
153         host = self.options.get("certificate_domain", config["host"])
154         hosts = [host]
155         hosts.extend("%s.%s" % (item[0], host) for item in serve.get_subdomains(host).values())
156         key_file, certificate = self.ssl_env.host_cert_path(hosts)
157
158         config["key_file"] = key_file
159         config["certificate"] = certificate
160
161         return config
162
163     def setup_server_logging(self):
164         server_logger = get_default_logger(component="wptserve")
165         assert server_logger is not None
166         log_filter = handlers.LogLevelFilter(lambda x:x, "info")
167         # Downgrade errors to warnings for the server
168         log_filter = LogLevelRewriter(log_filter, ["error"], "warning")
169         server_logger.component_filter = log_filter
170
171         server_logger = proxy.QueuedProxyLogger(server_logger)
172
173         try:
174             #Set as the default logger for wptserve
175             serve.set_logger(server_logger)
176             serve.logger = server_logger
177         except Exception:
178             # This happens if logging has already been set up for wptserve
179             pass
180
181     def get_routes(self):
182         route_builder = serve.RoutesBuilder()
183
184         for path, format_args, content_type, route in [
185                 ("testharness_runner.html", {}, "text/html", "/testharness_runner.html"),
186                 (self.options.get("testharnessreport", "testharnessreport.js"),
187                  {"output": self.pause_after_test}, "text/javascript",
188                  "/resources/testharnessreport.js")]:
189             path = os.path.normpath(os.path.join(here, path))
190             route_builder.add_static(path, format_args, content_type, route)
191
192         for url_base, paths in self.test_paths.iteritems():
193             if url_base == "/":
194                 continue
195             route_builder.add_mount_point(url_base, paths["tests_path"])
196
197         if "/" not in self.test_paths:
198             del route_builder.mountpoint_routes["/"]
199
200         return route_builder.get_routes()
201
202     def ensure_started(self):
203         # Pause for a while to ensure that the server has a chance to start
204         for _ in xrange(20):
205             failed = self.test_servers()
206             if not failed:
207                 return
208             time.sleep(0.5)
209         raise EnvironmentError("Servers failed to start (scheme:port): %s" % ("%s:%s" for item in failed))
210
211     def test_servers(self):
212         failed = []
213         for scheme, servers in self.servers.iteritems():
214             for port, server in servers:
215                 if self.test_server_port:
216                     s = socket.socket()
217                     try:
218                         s.connect((self.config["host"], port))
219                     except socket.error:
220                         failed.append((scheme, port))
221                     finally:
222                         s.close()
223
224                 if not server.is_alive():
225                     failed.append((scheme, port))