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

Unified 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: Update file permissions. 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/test/kasko/py/kasko/exceptions.py ('k') | chrome/test/kasko/py/kasko/report.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/test/kasko/py/kasko/process.py
diff --git a/chrome/test/kasko/py/kasko/process.py b/chrome/test/kasko/py/kasko/process.py
new file mode 100755
index 0000000000000000000000000000000000000000..89edaf250276cbdcd4a80a077bd6444c9ffeb416
--- /dev/null
+++ b/chrome/test/kasko/py/kasko/process.py
@@ -0,0 +1,222 @@
+#!/usr/bin/env python
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Utilities for interacting with processes on a win32 system."""
+
+import logging
+import os
+import pywintypes
+import re
+import subprocess
+import sys
+import time
+import win32api
+import win32com.client
+import win32con
+import win32event
+import win32gui
+import win32process
+
+from kasko.util import ScopedStartStop
+
+
+_DEFAULT_TIMEOUT = 10 # Seconds.
+_LOGGER = logging.getLogger(os.path.basename(__file__))
+
+
+def ImportSelenium(webdriver_dir=None):
+ """Imports Selenium from the given path."""
+ global webdriver
+ global service
+ if webdriver_dir:
+ sys.path.append(webdriver_dir)
+ from selenium import webdriver
+ import selenium.webdriver.chrome.service as service
+
+
+def FindChromeProcessId(user_data_dir, timeout=_DEFAULT_TIMEOUT):
+ """Finds the process ID of a given Chrome instance."""
+ udd = os.path.abspath(user_data_dir)
+
+ # Find the message window.
+ started = time.time()
+ elapsed = 0
+ msg_win = None
+ while msg_win is None:
+ try:
+ win = win32gui.FindWindowEx(None, None, 'Chrome_MessageWindow', udd)
+ if win != 0:
+ msg_win = win
+ break
+ except pywintypes.error:
+ continue
+
+ time.sleep(0.1)
+ elapsed = time.time() - started
+ if elapsed >= timeout:
+ raise TimeoutException()
+
+ # Get the process ID associated with the message window.
+ tid, pid = win32process.GetWindowThreadProcessId(msg_win)
+
+ return pid
+
+
+def ShutdownProcess(process_id, timeout, force=False):
+ """Attempts to nicely close the specified process.
+
+ Returns the exit code on success. Raises an error on failure.
+ """
+
+ # Open the process in question, so we can wait for it to exit.
+ permissions = win32con.SYNCHRONIZE | win32con.PROCESS_QUERY_INFORMATION
+ process_handle = win32api.OpenProcess(permissions, False, process_id)
+
+ # Loop around to periodically retry to close Chrome.
+ started = time.time()
+ elapsed = 0
+ while True:
+ _LOGGER.debug('Shutting down process with PID=%d.', process_id)
+
+ with open(os.devnull, 'w') as f:
+ cmd = ['taskkill.exe', '/PID', str(process_id)]
+ if force:
+ cmd.append('/F')
+ subprocess.call(cmd, shell=True, stdout=f, stderr=f)
+
+ # Wait at most 2 seconds after each call to taskkill.
+ curr_timeout_ms = int(max(2, timeout - elapsed) * 1000)
+
+ _LOGGER.debug('Waiting for process with PID=%d to exit.', process_id)
+ result = win32event.WaitForSingleObject(process_handle, curr_timeout_ms)
+ # Exit the loop on successful wait.
+ if result == win32event.WAIT_OBJECT_0:
+ break
+
+ elapsed = time.time() - started
+ if elapsed > timeout:
+ _LOGGER.debug('Timeout waiting for process to exit.')
+ raise TimeoutException()
+
+ exit_status = win32process.GetExitCodeProcess(process_handle)
+ process_handle.Close()
+ _LOGGER.debug('Process exited with status %d.', exit_status)
+
+ return exit_status
+
+
+def _WmiTimeToLocalEpoch(wmitime):
+ """Converts a WMI time string to a Unix epoch time."""
+ # The format of WMI times is: yyyymmddHHMMSS.xxxxxx[+-]UUU, where
+ # UUU is the number of minutes between local time and UTC.
+ m = re.match('^(?P<year>\d{4})(?P<month>\d{2})(?P<day>\d{2})'
+ '(?P<hour>\d{2})(?P<minutes>\d{2})(?P<seconds>\d{2}\.\d+)'
+ '(?P<offset>[+-]\d{3})$', wmitime)
+ if not m:
+ raise Exception('Invalid WMI time string.')
+
+ # This parses the time as a local time.
+ t = time.mktime(time.strptime(wmitime[0:14], '%Y%m%d%H%M%S'))
+
+ # Add the fractional part of the seconds that wasn't parsed by strptime.
+ s = float(m.group('seconds'))
+ t += s - int(s)
+
+ return t
+
+
+def GetProcessCreationDate(pid):
+ """Returns the process creation date as local unix epoch time."""
+ wmi = win32com.client.GetObject('winmgmts:')
+ procs = wmi.ExecQuery(
+ 'select CreationDate from Win32_Process where ProcessId = %s' % pid)
+ for proc in procs:
+ return _WmiTimeToLocalEpoch(proc.Properties_('CreationDate').Value)
+ raise Exception('Unable to find process with PID %d.' % pid)
+
+
+def ShutdownChildren(parent_pid, child_exe, started_after, started_before,
+ timeout=_DEFAULT_TIMEOUT, force=False):
+ """Shuts down any lingering child processes of a given parent.
+
+ This is an inherently racy thing to do as process IDs are aggressively reused
+ on Windows. Filtering by a valid known |started_after| and |started_before|
+ timestamp, as well as by the executable of the child process resolves this
+ issue. Ugh.
+ """
+ started = time.time()
+ wmi = win32com.client.GetObject('winmgmts:')
+ _LOGGER.debug('Shutting down lingering children processes.')
+ for proc in wmi.InstancesOf('Win32_Process'):
+ if proc.Properties_('ParentProcessId').Value != parent_pid:
+ continue
+ if proc.Properties_('ExecutablePath').Value != child_exe:
+ continue
+ t = _WmiTimeToLocalEpoch(proc.Properties_('CreationDate').Value)
+ if t <= started_after or t >= started_before:
+ continue
+ pid = proc.Properties_('ProcessId').Value
+ remaining = max(0, started + timeout - time.time())
+ ShutdownProcess(pid, remaining, force=force)
+
+
+class ChromeInstance(object):
+ """A class encapsulating a running instance of Chrome for testing.
+
+ The Chrome instance is controlled via chromedriver and Selenium."""
+
+ def __init__(self, chromedriver, chrome, user_data_dir):
+ self.chromedriver_ = os.path.abspath(chromedriver)
+ self.chrome_ = os.path.abspath(chrome)
+ self.user_data_dir_ = user_data_dir
+
+ def start(self, timeout=_DEFAULT_TIMEOUT):
+ capabilities = {
+ 'chromeOptions': {
+ 'args': [
+ # This allows automated navigation to chrome:// URLs.
+ '--enable-gpu-benchmarking',
+ '--user-data-dir=%s' % self.user_data_dir_,
+ ],
+ 'binary': self.chrome_,
+ }
+ }
+
+ # Use a _ScopedStartStop helper so the service and driver clean themselves
+ # up in case of any exceptions.
+ _LOGGER.info('Starting chromedriver')
+ with ScopedStartStop(service.Service(self.chromedriver_)) as \
+ scoped_service:
+ _LOGGER.info('Starting chrome')
+ with ScopedStartStop(webdriver.Remote(scoped_service.service.service_url,
+ capabilities),
+ start=lambda x: None, stop=lambda x: x.quit()) as \
+ scoped_driver:
+ self.pid_ = FindChromeProcessId(self.user_data_dir_, timeout)
+ self.started_at_ = GetProcessCreationDate(self.pid_)
+ _LOGGER.debug('Chrome launched.')
+ self.driver_ = scoped_driver.release()
+ self.service_ = scoped_service.release()
+
+
+ def stop(self, timeout=_DEFAULT_TIMEOUT):
+ started = time.time()
+ self.driver_.quit()
+ self.stopped_at_ = time.time()
+ self.service_.stop()
+ self.driver_ = None
+ self.service = None
+
+ # Ensure that any lingering children processes are torn down as well. This
+ # is generally racy on Windows, but is gated based on parent process ID,
+ # child executable, and start time of the child process. These criteria
+ # ensure we don't go indiscriminately killing processes.
+ remaining = max(0, started + timeout - time.time())
+ ShutdownChildren(self.pid_, self.chrome_, self.started_at_,
+ self.stopped_at_, remaining, force=True)
+
+ def navigate_to(self, url):
+ """Navigates the running Chrome instance to the provided URL."""
+ self.driver_.get(url)
« no previous file with comments | « chrome/test/kasko/py/kasko/exceptions.py ('k') | chrome/test/kasko/py/kasko/report.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698