f78593e4e599322bdfc688431e0a4aee27bd2bdd
[WebKit-https.git] / WebDriverTests / imported / w3c / tools / wptrunner / wptrunner / webdriver_server.py
1 import abc
2 import errno
3 import os
4 import platform
5 import socket
6 import threading
7 import time
8 import traceback
9 import urlparse
10
11 import mozprocess
12
13
14 __all__ = ["SeleniumServer", "ChromeDriverServer",
15            "GeckoDriverServer", "InternetExplorerDriverServer",
16            "ServoDriverServer", "WebDriverServer"]
17
18
19 class WebDriverServer(object):
20     __metaclass__ = abc.ABCMeta
21
22     default_base_path = "/"
23     _used_ports = set()
24
25     def __init__(self, logger, binary, host="127.0.0.1", port=None,
26                  base_path="", env=None, args=None):
27         if binary is None:
28             raise ValueError("WebDriver server binary must be given "
29                              "to --webdriver-binary argument")
30
31         self.logger = logger
32         self.binary = binary
33         self.host = host
34         if base_path == "":
35             self.base_path = self.default_base_path
36         else:
37             self.base_path = base_path
38         self.env = os.environ.copy() if env is None else env
39
40         self._port = port
41         self._cmd = None
42         self._args = args if args is not None else []
43         self._proc = None
44
45     @abc.abstractmethod
46     def make_command(self):
47         """Returns the full command for starting the server process as a list."""
48
49     def start(self, block=False):
50         try:
51             self._run(block)
52         except KeyboardInterrupt:
53             self.stop()
54
55     def _run(self, block):
56         self._cmd = self.make_command()
57         self._proc = mozprocess.ProcessHandler(
58             self._cmd,
59             processOutputLine=self.on_output,
60             env=self.env,
61             storeOutput=False)
62
63         try:
64             self._proc.run()
65         except OSError as e:
66             if e.errno == errno.ENOENT:
67                 raise IOError(
68                     "WebDriver HTTP server executable not found: %s" % self.binary)
69             raise
70
71         self.logger.debug(
72             "Waiting for server to become accessible: %s" % self.url)
73         try:
74             wait_for_service((self.host, self.port))
75         except:
76             self.logger.error(
77                 "WebDriver HTTP server was not accessible "
78                 "within the timeout:\n%s" % traceback.format_exc())
79             raise
80
81         if block:
82             self._proc.wait()
83
84     def stop(self, force=False):
85         if self.is_alive:
86             return self._proc.kill()
87         return not self.is_alive
88
89     @property
90     def is_alive(self):
91         return hasattr(self._proc, "proc") and self._proc.poll() is None
92
93     def on_output(self, line):
94         self.logger.process_output(self.pid,
95                                    line.decode("utf8", "replace"),
96                                    command=" ".join(self._cmd))
97
98     @property
99     def pid(self):
100         if self._proc is not None:
101             return self._proc.pid
102
103     @property
104     def url(self):
105         return "http://%s:%i%s" % (self.host, self.port, self.base_path)
106
107     @property
108     def port(self):
109         if self._port is None:
110             self._port = self._find_next_free_port()
111         return self._port
112
113     @staticmethod
114     def _find_next_free_port():
115         port = get_free_port(4444, exclude=WebDriverServer._used_ports)
116         WebDriverServer._used_ports.add(port)
117         return port
118
119
120 class SeleniumServer(WebDriverServer):
121     default_base_path = "/wd/hub"
122
123     def make_command(self):
124         return ["java", "-jar", self.binary, "-port", str(self.port)] + self._args
125
126
127 class ChromeDriverServer(WebDriverServer):
128     default_base_path = "/"
129
130     def __init__(self, logger, binary="chromedriver", port=None,
131                  base_path="", args=None):
132         WebDriverServer.__init__(
133             self, logger, binary, port=port, base_path=base_path, args=args)
134
135     def make_command(self):
136         return [self.binary,
137                 cmd_arg("port", str(self.port)),
138                 cmd_arg("url-base", self.base_path) if self.base_path else ""] + self._args
139
140
141 class EdgeDriverServer(WebDriverServer):
142     def __init__(self, logger, binary="MicrosoftWebDriver.exe", port=None,
143                  base_path="", host="localhost", args=None):
144         WebDriverServer.__init__(
145             self, logger, binary, host=host, port=port, args=args)
146
147     def make_command(self):
148         return [self.binary,
149                 "--port=%s" % str(self.port)] + self._args
150
151
152 class InternetExplorerDriverServer(WebDriverServer):
153     def __init__(self, logger, binary="IEDriverServer.exe", port=None,
154                  base_path="", host="localhost", args=None):
155         WebDriverServer.__init__(
156             self, logger, binary, host=host, port=port, args=args)
157
158     def make_command(self):
159         return [self.binary,
160                 "--port=%s" % str(self.port)] + self._args
161
162
163 class GeckoDriverServer(WebDriverServer):
164     def __init__(self, logger, marionette_port=2828, binary="geckodriver",
165                  host="127.0.0.1", port=None, args=None):
166         env = os.environ.copy()
167         env["RUST_BACKTRACE"] = "1"
168         WebDriverServer.__init__(self, logger, binary, host=host, port=port, env=env, args=args)
169         self.marionette_port = marionette_port
170
171     def make_command(self):
172         return [self.binary,
173                 "--marionette-port", str(self.marionette_port),
174                 "--host", self.host,
175                 "--port", str(self.port)] + self._args
176
177
178 class ServoDriverServer(WebDriverServer):
179     def __init__(self, logger, binary="servo", binary_args=None, host="127.0.0.1", port=None):
180         env = os.environ.copy()
181         env["RUST_BACKTRACE"] = "1"
182         WebDriverServer.__init__(self, logger, binary, host=host, port=port, env=env)
183         self.binary_args = binary_args
184
185     def make_command(self):
186         command = [self.binary,
187                    "--webdriver", str(self.port),
188                    "--hard-fail",
189                    "--headless"] + self._args
190         if self.binary_args:
191             command += self.binary_args
192         return command
193
194
195 def cmd_arg(name, value=None):
196     prefix = "-" if platform.system() == "Windows" else "--"
197     rv = prefix + name
198     if value is not None:
199         rv += "=" + value
200     return rv
201
202
203 def get_free_port(start_port, exclude=None):
204     """Get the first port number after start_port (inclusive) that is
205     not currently bound.
206
207     :param start_port: Integer port number at which to start testing.
208     :param exclude: Set of port numbers to skip"""
209     port = start_port
210     while True:
211         if exclude and port in exclude:
212             port += 1
213             continue
214         s = socket.socket()
215         try:
216             s.bind(("127.0.0.1", port))
217         except socket.error:
218             port += 1
219         else:
220             return port
221         finally:
222             s.close()
223
224
225 def wait_for_service(addr, timeout=15):
226     """Waits until network service given as a tuple of (host, port) becomes
227     available or the `timeout` duration is reached, at which point
228     ``socket.error`` is raised."""
229     end = time.time() + timeout
230     while end > time.time():
231         so = socket.socket()
232         try:
233             so.connect(addr)
234         except socket.timeout:
235             pass
236         except socket.error as e:
237             if e[0] != errno.ECONNREFUSED:
238                 raise
239         else:
240             return True
241         finally:
242             so.close()
243         time.sleep(0.5)
244     raise socket.error("Service is unavailable: %s:%i" % addr)