OLD | NEW |
| (Empty) |
1 #!/usr/bin/python | |
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 """Launches and kills ChromeDriver. | |
7 | |
8 For ChromeDriver documentation, refer to: | |
9 http://dev.chromium.org/developers/testing/webdriver-for-chrome | |
10 """ | |
11 | |
12 import logging | |
13 import os | |
14 import platform | |
15 import signal | |
16 import subprocess | |
17 import sys | |
18 import threading | |
19 import urllib2 | |
20 | |
21 | |
22 class ChromeDriverLauncher: | |
23 """Launches and kills the ChromeDriver process.""" | |
24 | |
25 def __init__(self, exe_path=None, root_path=None, port=None, url_base=None): | |
26 """Initializes a new launcher. | |
27 | |
28 Args: | |
29 exe_path: path to the ChromeDriver executable | |
30 root_path: base path from which ChromeDriver webserver will serve files | |
31 port: port that ChromeDriver will listen on | |
32 url_base: base URL which ChromeDriver webserver will listen from | |
33 """ | |
34 self._exe_path = exe_path | |
35 self._root_path = root_path | |
36 self._port = port | |
37 self._url_base = url_base | |
38 if self._exe_path is None: | |
39 self._exe_path = ChromeDriverLauncher.LocateExe() | |
40 if self._exe_path is None: | |
41 raise RuntimeError('ChromeDriver exe could not be found in its default ' | |
42 'location. Searched in following directories: ' + | |
43 ', '.join(self.DefaultExeLocations())) | |
44 if self._root_path is not None: | |
45 self._root_path = os.path.abspath(self._root_path) | |
46 self._process = None | |
47 | |
48 if not os.path.exists(self._exe_path): | |
49 raise RuntimeError('ChromeDriver exe not found at: ' + self._exe_path) | |
50 | |
51 os.environ['PATH'] = os.path.dirname(self._exe_path) + os.environ['PATH'] | |
52 self.Start() | |
53 | |
54 @staticmethod | |
55 def DefaultExeLocations(): | |
56 """Returns the paths that are used to find the ChromeDriver executable. | |
57 | |
58 Returns: | |
59 a list of directories that would be searched for the executable | |
60 """ | |
61 script_dir = os.path.dirname(__file__) | |
62 chrome_src = os.path.abspath(os.path.join( | |
63 script_dir, os.pardir, os.pardir, os.pardir)) | |
64 bin_dirs = { | |
65 'linux2': [ os.path.join(chrome_src, 'out', 'Debug'), | |
66 os.path.join(chrome_src, 'sconsbuild', 'Debug'), | |
67 os.path.join(chrome_src, 'out', 'Release'), | |
68 os.path.join(chrome_src, 'sconsbuild', 'Release')], | |
69 'linux3': [ os.path.join(chrome_src, 'out', 'Debug'), | |
70 os.path.join(chrome_src, 'sconsbuild', 'Debug'), | |
71 os.path.join(chrome_src, 'out', 'Release'), | |
72 os.path.join(chrome_src, 'sconsbuild', 'Release')], | |
73 'darwin': [ os.path.join(chrome_src, 'xcodebuild', 'Debug'), | |
74 os.path.join(chrome_src, 'xcodebuild', 'Release')], | |
75 'win32': [ os.path.join(chrome_src, 'chrome', 'Debug'), | |
76 os.path.join(chrome_src, 'build', 'Debug'), | |
77 os.path.join(chrome_src, 'chrome', 'Release'), | |
78 os.path.join(chrome_src, 'build', 'Release')], | |
79 } | |
80 return [os.getcwd()] + bin_dirs.get(sys.platform, []) | |
81 | |
82 @staticmethod | |
83 def LocateExe(): | |
84 """Attempts to locate the ChromeDriver executable. | |
85 | |
86 This searches the current directory, then checks the appropriate build | |
87 locations according to platform. | |
88 | |
89 Returns: | |
90 absolute path to the ChromeDriver executable, or None if not found | |
91 """ | |
92 exe_name = 'chromedriver' | |
93 if platform.system() == 'Windows': | |
94 exe_name += '.exe' | |
95 | |
96 for dir in ChromeDriverLauncher.DefaultExeLocations(): | |
97 path = os.path.join(dir, exe_name) | |
98 if os.path.exists(path): | |
99 return os.path.abspath(path) | |
100 return None | |
101 | |
102 def Start(self): | |
103 """Starts a new ChromeDriver process. | |
104 | |
105 Kills a previous one if it is still running. | |
106 | |
107 Raises: | |
108 RuntimeError if ChromeDriver does not start | |
109 """ | |
110 def _WaitForLaunchResult(stdout, started_event, launch_result): | |
111 """Reads from the stdout of ChromeDriver and parses the launch result. | |
112 | |
113 Args: | |
114 stdout: handle to ChromeDriver's standard output | |
115 started_event: condition variable to notify when the launch result | |
116 has been parsed | |
117 launch_result: dictionary to add the result of this launch to | |
118 """ | |
119 status_line = stdout.readline() | |
120 started_event.acquire() | |
121 try: | |
122 launch_result['success'] = status_line.startswith('Started') | |
123 launch_result['status_line'] = status_line | |
124 if launch_result['success']: | |
125 port_line = stdout.readline() | |
126 launch_result['port'] = int(port_line.split('=')[1]) | |
127 started_event.notify() | |
128 finally: | |
129 started_event.release() | |
130 | |
131 if self._process is not None: | |
132 self.Kill() | |
133 | |
134 chromedriver_args = [self._exe_path] | |
135 if self._root_path is not None: | |
136 chromedriver_args += ['--root=%s' % self._root_path] | |
137 if self._port is not None: | |
138 chromedriver_args += ['--port=%d' % self._port] | |
139 if self._url_base is not None: | |
140 chromedriver_args += ['--url-base=%s' % self._url_base] | |
141 proc = subprocess.Popen(chromedriver_args, | |
142 stdout=subprocess.PIPE) | |
143 if proc is None: | |
144 raise RuntimeError('ChromeDriver cannot be started') | |
145 self._process = proc | |
146 | |
147 # Wait for ChromeDriver to be initialized before returning. | |
148 launch_result = {} | |
149 started_event = threading.Condition() | |
150 started_event.acquire() | |
151 spawn_thread = threading.Thread( | |
152 target=_WaitForLaunchResult, | |
153 args=(proc.stdout, started_event, launch_result)) | |
154 spawn_thread.start() | |
155 started_event.wait(20) | |
156 timed_out = 'success' not in launch_result | |
157 started_event.release() | |
158 if timed_out: | |
159 raise RuntimeError('ChromeDriver did not respond') | |
160 elif not launch_result['success']: | |
161 raise RuntimeError('ChromeDriver failed to launch: ' + | |
162 launch_result['status_line']) | |
163 self._port = launch_result['port'] | |
164 logging.info('ChromeDriver running on port %s' % self._port) | |
165 | |
166 def Kill(self): | |
167 """Kills a currently running ChromeDriver process, if it is running.""" | |
168 def _WaitForShutdown(process, shutdown_event): | |
169 """Waits for the process to quit and then notifies.""" | |
170 process.wait() | |
171 shutdown_event.acquire() | |
172 shutdown_event.notify() | |
173 shutdown_event.release() | |
174 | |
175 if self._process is None: | |
176 return | |
177 try: | |
178 urllib2.urlopen(self.GetURL() + '/shutdown').close() | |
179 except urllib2.URLError: | |
180 # Could not shutdown. Kill. | |
181 pid = self._process.pid | |
182 if platform.system() == 'Windows': | |
183 subprocess.call(['taskkill.exe', '/T', '/F', '/PID', str(pid)]) | |
184 else: | |
185 os.kill(pid, signal.SIGTERM) | |
186 | |
187 # Wait for ChromeDriver process to exit before returning. | |
188 # Even if we had to kill the process above, we still should call wait | |
189 # to cleanup the zombie. | |
190 shutdown_event = threading.Condition() | |
191 shutdown_event.acquire() | |
192 wait_thread = threading.Thread( | |
193 target=_WaitForShutdown, | |
194 args=(self._process, shutdown_event)) | |
195 wait_thread.start() | |
196 shutdown_event.wait(10) | |
197 shutdown_event.release() | |
198 self._process = None | |
199 | |
200 def GetURL(self): | |
201 url = 'http://localhost:' + str(self._port) | |
202 if self._url_base: | |
203 url += self._url_base | |
204 return url | |
205 | |
206 def GetPort(self): | |
207 return self._port | |
OLD | NEW |