Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(301)

Side by Side Diff: chrome/test/kasko/py/kasko/process.py

Issue 1582613002: [win] Create a SyzyAsan/Chrome/Kasko/Crashpad integration test. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Slight refactor to use pre-instrumented binaries. Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2016 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 """Utilities for interacting with processes on a win32 system."""
7
8 import logging
9 import os
10 import pywintypes
11 import re
12 import subprocess
13 import sys
14 import time
15 import win32api
16 import win32com.client
17 import win32con
18 import win32event
19 import win32gui
20 import win32process
21
22 from kasko.util import ScopedStartStop
23
24
25 _DEFAULT_TIMEOUT = 10 # Seconds.
26 _LOGGER = logging.getLogger(os.path.basename(__file__))
27
28
29 def ImportSelenium(webdriver_dir=None):
30 """Imports Selenium from the given path."""
31 global webdriver
32 global service
33 if webdriver_dir:
34 sys.path.append(webdriver_dir)
35 from selenium import webdriver
36 import selenium.webdriver.chrome.service as service
37
38
39 def FindChromeProcessId(user_data_dir, timeout=_DEFAULT_TIMEOUT):
40 """Finds the process ID of a given Chrome instance."""
41 udd = os.path.abspath(user_data_dir)
42
43 # Find the message window.
44 started = time.time()
45 elapsed = 0
46 msg_win = None
47 while msg_win is None:
48 try:
49 win = win32gui.FindWindowEx(None, None, 'Chrome_MessageWindow', udd)
50 if win != 0:
51 msg_win = win
52 break
53 except pywintypes.error:
54 continue
55
56 time.sleep(0.1)
57 elapsed = time.time() - started
58 if elapsed >= timeout:
59 raise TimeoutException()
60
61 # Get the process ID associated with the message window.
62 tid, pid = win32process.GetWindowThreadProcessId(msg_win)
63
64 return pid
65
66
67 def ShutdownProcess(process_id, timeout, force=False):
68 """Attempts to nicely close the specified process.
69
70 Returns the exit code on success. Raises an error on failure.
71 """
72
73 # Open the process in question, so we can wait for it to exit.
74 permissions = win32con.SYNCHRONIZE | win32con.PROCESS_QUERY_INFORMATION
75 process_handle = win32api.OpenProcess(permissions, False, process_id)
76
77 # Loop around to periodically retry to close Chrome.
78 started = time.time()
79 elapsed = 0
80 while True:
81 _LOGGER.debug('Shutting down process with PID=%d.', process_id)
82
83 with open(os.devnull, 'w') as f:
84 cmd = ['taskkill.exe', '/PID', str(process_id)]
85 if force:
86 cmd.append('/F')
87 subprocess.call(cmd, shell=True, stdout=f, stderr=f)
88
89 # Wait at most 2 seconds after each call to taskkill.
90 curr_timeout_ms = int(max(2, timeout - elapsed) * 1000)
91
92 _LOGGER.debug('Waiting for process with PID=%d to exit.', process_id)
93 result = win32event.WaitForSingleObject(process_handle, curr_timeout_ms)
94 # Exit the loop on successful wait.
95 if result == win32event.WAIT_OBJECT_0:
96 break
97
98 elapsed = time.time() - started
99 if elapsed > timeout:
100 _LOGGER.debug('Timeout waiting for process to exit.')
101 raise TimeoutException()
102
103 exit_status = win32process.GetExitCodeProcess(process_handle)
104 process_handle.Close()
105 _LOGGER.debug('Process exited with status %d.', exit_status)
106
107 return exit_status
108
109
110 def _WmiTimeToLocalEpoch(wmitime):
111 """Converts a WMI time string to a Unix epoch time."""
112 # The format of WMI times is: yyyymmddHHMMSS.xxxxxx[+-]UUU, where
113 # UUU is the number of minutes between local time and UTC.
114 m = re.match('^(?P<year>\d{4})(?P<month>\d{2})(?P<day>\d{2})'
115 '(?P<hour>\d{2})(?P<minutes>\d{2})(?P<seconds>\d{2}\.\d+)'
116 '(?P<offset>[+-]\d{3})$', wmitime)
117 if not m:
118 raise Exception('Invalid WMI time string.')
119
120 # This parses the time as a local time.
121 t = time.mktime(time.strptime(wmitime[0:14], '%Y%m%d%H%M%S'))
122
123 # Add the fractional part of the seconds that wasn't parsed by strptime.
124 s = float(m.group('seconds'))
125 t += s - int(s)
126
127 return t
128
129
130 def GetProcessCreationDate(pid):
131 """Returns the process creation date as local unix epoch time."""
132 wmi = win32com.client.GetObject('winmgmts:')
133 procs = wmi.ExecQuery(
134 'select CreationDate from Win32_Process where ProcessId = %s' % pid)
135 for proc in procs:
136 return _WmiTimeToLocalEpoch(proc.Properties_('CreationDate').Value)
137 raise Exception('Unable to find process with PID %d.' % pid)
138
139
140 def ShutdownChildren(parent_pid, child_exe, started_after, started_before,
141 timeout=_DEFAULT_TIMEOUT, force=False):
142 """Shuts down any lingering child processes of a given parent.
143
144 This is an inherently racy thing to do as process IDs are aggressively reused
145 on Windows. Filtering by a valid known |started_after| and |started_before|
146 timestamp, as well as by the executable of the child process resolves this
147 issue. Ugh.
148 """
149 started = time.time()
150 wmi = win32com.client.GetObject('winmgmts:')
151 _LOGGER.debug('Shutting down lingering children processes.')
152 for proc in wmi.InstancesOf('Win32_Process'):
153 if proc.Properties_('ParentProcessId').Value != parent_pid:
154 continue
155 if proc.Properties_('ExecutablePath').Value != child_exe:
156 continue
157 t = _WmiTimeToLocalEpoch(proc.Properties_('CreationDate').Value)
158 if t <= started_after or t >= started_before:
159 continue
160 pid = proc.Properties_('ProcessId').Value
161 remaining = max(0, started + timeout - time.time())
162 ShutdownProcess(pid, remaining, force=force)
163
164
165 class ChromeInstance(object):
166 """A class encapsulating a running instance of Chrome for testing.
167
168 The Chrome instance is controlled via chromedriver and Selenium."""
169
170 def __init__(self, chromedriver, chrome, user_data_dir):
171 self.chromedriver_ = os.path.abspath(chromedriver)
172 self.chrome_ = os.path.abspath(chrome)
173 self.user_data_dir_ = user_data_dir
174
175 def start(self, timeout=_DEFAULT_TIMEOUT):
176 capabilities = {
177 'chromeOptions': {
178 'args': [
179 # This allows automated navigation to chrome:// URLs.
180 '--enable-gpu-benchmarking',
181 '--user-data-dir=%s' % self.user_data_dir_,
182 ],
183 'binary': self.chrome_,
184 }
185 }
186
187 # Use a _ScopedStartStop helper so the service and driver clean themselves
188 # up in case of any exceptions.
189 _LOGGER.info('Starting chromedriver')
190 with ScopedStartStop(service.Service(self.chromedriver_)) as \
191 scoped_service:
192 _LOGGER.info('Starting chrome')
193 with ScopedStartStop(webdriver.Remote(scoped_service.service.service_url,
194 capabilities),
195 start=lambda x: None, stop=lambda x: x.quit()) as \
196 scoped_driver:
197 self.pid_ = FindChromeProcessId(self.user_data_dir_, timeout)
198 self.started_at_ = GetProcessCreationDate(self.pid_)
199 _LOGGER.debug('Chrome launched.')
200 self.driver_ = scoped_driver.release()
201 self.service_ = scoped_service.release()
202
203
204 def stop(self, timeout=_DEFAULT_TIMEOUT):
205 started = time.time()
206 self.driver_.quit()
207 self.stopped_at_ = time.time()
208 self.service_.stop()
209 self.driver_ = None
210 self.service = None
211
212 # Ensure that any lingering children processes are torn down as well. This
213 # is generally racy on Windows, but is gated based on parent process ID,
214 # child executable, and start time of the child process. These criteria
215 # ensure we don't go indiscriminately killing processes.
216 remaining = max(0, started + timeout - time.time())
217 ShutdownChildren(self.pid_, self.chrome_, self.started_at_,
218 self.stopped_at_, remaining, force=True)
219
220 def navigate_to(self, url):
221 """Navigates the running Chrome instance to the provided URL."""
222 self.driver_.get(url)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698