| 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 |