Package pysys :: Package process :: Module helper
[hide private]
[frames] | no frames]

Source Code for Module pysys.process.helper

  1  #!/usr/bin/env python 
  2  # PySys System Test Framework, Copyright (C) 2006-2016  M.B.Grieve 
  3   
  4  # This library is free software; you can redistribute it and/or 
  5  # modify it under the terms of the GNU Lesser General Public 
  6  # License as published by the Free Software Foundation; either 
  7  # version 2.1 of the License, or (at your option) any later version. 
  8   
  9  # This library is distributed in the hope that it will be useful, 
 10  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 11  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 12  # Lesser General Public License for more details. 
 13   
 14  # You should have received a copy of the GNU Lesser General Public 
 15  # License along with this library; if not, write to the Free Software 
 16  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 
 17   
 18  # Contact: moraygrieve@users.sourceforge.net 
 19   
 20  import string, os.path, time, thread, logging, Queue 
 21  import win32api, win32pdh, win32security, win32process, win32file, win32pipe, win32con, pywintypes, threading 
 22   
 23  from pysys import log 
 24  from pysys import process_lock 
 25  from pysys.constants import * 
 26  from pysys.exceptions import * 
 27  from pysys.process.commonwrapper import CommonProcessWrapper, _stringToUnicode 
 28   
 29  # check for new lines on end of a string 
 30  EXPR = re.compile(".*\n$") 
 31   
 32   
33 -class ProcessWrapper(CommonProcessWrapper):
34 """Windows Process wrapper for process execution and management. 35 36 The process wrapper provides the ability to start and stop an external process, setting 37 the process environment, working directory and state i.e. a foreground process in which case 38 a call to the L{start} method will not return until the process has exited, or a background 39 process in which case the process is started in a separate thread allowing concurrent execution 40 within the testcase. Processes started in the foreground can have a timeout associated with them, such 41 that should the timeout be exceeded, the process will be terminated and control passed back to the 42 caller of the method. The wrapper additionally allows control over logging of the process stdout 43 and stderr to file, and writing to the process stdin. 44 45 Usage of the class is to first create an instance, setting all runtime parameters of the process 46 as data attributes to the class instance via the constructor. The process can then be started 47 and stopped via the L{start} and L{stop} methods of the class, as well as interrogated for 48 its executing status via the L{running} method, and waited for its completion via the L{wait} 49 method. During process execution the C{self.pid} and C{seld.exitStatus} data attributes are set 50 within the class instance, and these values can be accessed directly via it's object reference. 51 52 @ivar pid: The process id for a running or complete process (as set by the OS) 53 @type pid: integer 54 @ivar exitStatus: The process exit status for a completed process 55 @type exitStatus: integer 56 57 """ 58
59 - def __init__(self, command, arguments, environs, workingDir, state, timeout, stdout=None, stderr=None, displayName=None, **kwargs):
60 """Create an instance of the process wrapper. 61 62 @param command: The full path to the command to execute 63 @param arguments: A list of arguments to the command 64 @param environs: A dictionary of environment variables (key, value) for the process context execution 65 @param workingDir: The working directory for the process 66 @param state: The state of the process (L{pysys.constants.FOREGROUND} or L{pysys.constants.BACKGROUND} 67 @param timeout: The timeout in seconds to be applied to the process 68 @param stdout: The full path to the filename to write the stdout of the process 69 @param stderr: The full path to the filename to write the sdterr of the process 70 @param displayName: Display name for this process 71 72 """ 73 CommonProcessWrapper.__init__(self, command, arguments, environs, workingDir, 74 state, timeout, stdout, stderr, displayName, **kwargs) 75 76 # private instance variables 77 self.__hProcess = None 78 self.__hThread = None 79 self.__tid = None 80 81 self.__lock = threading.Lock() # to protect access to the fields that get updated 82 83 # set the stdout|err file handles 84 self.fStdout = 'nul' 85 self.fStderr = 'nul' 86 try: 87 if stdout is not None: self.fStdout = _stringToUnicode(stdout) 88 except: 89 log.info("Unable to create file to capture stdout - using the null device") 90 try: 91 if stderr is not None: self.fStderr = _stringToUnicode(stderr) 92 except: 93 log.info("Unable to create file to capture stdout - using the null device")
94 95
96 - def writeStdin(self):
97 """Method to write to the process stdin pipe. 98 99 """ 100 while self._outQueue: 101 try: 102 data = self._outQueue.get(block=True, timeout=0.25) 103 except Queue.Empty: 104 if not self.running(): 105 break 106 else: 107 with self.__lock: 108 if self.__stdin: 109 win32file.WriteFile(self.__stdin, data, None)
110 111
112 - def __quotePath(self, input):
113 """Private method to sanitise a windows path. 114 115 """ 116 i = input 117 if i.find(' ') > 0: 118 return '\"%s\"' % i 119 else: 120 return i
121 122
123 - def startBackgroundProcess(self):
124 """Method to start a process running in the background. 125 126 """ 127 with process_lock: 128 # security attributes for pipes 129 sAttrs = win32security.SECURITY_ATTRIBUTES() 130 sAttrs.bInheritHandle = 1 131 132 # create pipes for the process to write to 133 hStdin_r, hStdin = win32pipe.CreatePipe(sAttrs, 0) 134 hStdout = win32file.CreateFile(_stringToUnicode(self.fStdout), win32file.GENERIC_WRITE | win32file.GENERIC_READ, 135 win32file.FILE_SHARE_DELETE | win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE, 136 sAttrs, win32file.CREATE_ALWAYS, win32file.FILE_ATTRIBUTE_NORMAL, None) 137 hStderr = win32file.CreateFile(_stringToUnicode(self.fStderr), win32file.GENERIC_WRITE | win32file.GENERIC_READ, 138 win32file.FILE_SHARE_DELETE | win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE, 139 sAttrs, win32file.CREATE_ALWAYS, win32file.FILE_ATTRIBUTE_NORMAL, None) 140 141 # set the info structure for the new process. 142 StartupInfo = win32process.STARTUPINFO() 143 StartupInfo.hStdInput = hStdin_r 144 StartupInfo.hStdOutput = hStdout 145 StartupInfo.hStdError = hStderr 146 StartupInfo.dwFlags = win32process.STARTF_USESTDHANDLES 147 148 # Create new handles for the thread ends of the pipes. The duplicated handles will 149 # have their inheritence properties set to false so that any children inheriting these 150 # handles will not have non-closeable handles to the pipes 151 pid = win32api.GetCurrentProcess() 152 tmp = win32api.DuplicateHandle(pid, hStdin, pid, 0, 0, win32con.DUPLICATE_SAME_ACCESS) 153 win32file.CloseHandle(hStdin) 154 hStdin = tmp 155 156 # start the process, and close down the copies of the process handles 157 # we have open after the process creation (no longer needed here) 158 old_command = command = self.__quotePath(self.command) 159 for arg in self.arguments: command = '%s %s' % (command, self.__quotePath(arg)) 160 try: 161 self.__hProcess, self.__hThread, self.pid, self.__tid = win32process.CreateProcess( None, command, None, None, 1, 0, self.environs or None, os.path.normpath(self.workingDir), StartupInfo) 162 except pywintypes.error, e: 163 raise ProcessError, "Error creating process %s: %s" % (old_command, e) 164 165 win32file.CloseHandle(hStdin_r) 166 win32file.CloseHandle(hStdout) 167 win32file.CloseHandle(hStderr) 168 169 # set the handle to the stdin of the process 170 self.__stdin = hStdin
171 172
173 - def setExitStatus(self):
174 """Method to set the exit status of the process. 175 176 """ 177 with self.__lock: 178 if self.exitStatus is not None: return self.exitStatus 179 exitStatus = win32process.GetExitCodeProcess(self.__hProcess) 180 if exitStatus != win32con.STILL_ACTIVE: 181 try: 182 if self.__hProcess: win32file.CloseHandle(self.__hProcess) 183 if self.__hThread: win32file.CloseHandle(self.__hThread) 184 if self.__stdin: win32file.CloseHandle(self.__stdin) 185 except Exception, e: 186 # these failed sometimes with 'handle is invalid', probably due to interference of stdin writer thread 187 log.warning('Could not close process and thread handles for process %s: %s', self.pid, e) 188 self.__stdin = self.__hThread = self.__hProcess = None 189 self._outQueue = None 190 self.exitStatus = exitStatus 191 192 return self.exitStatus
193 194
195 - def stop(self, timeout=TIMEOUTS['WaitForProcessStop']):
196 """Stop a process running. 197 198 @raise ProcessError: Raised if an error occurred whilst trying to stop the process 199 200 """ 201 try: 202 with self.__lock: 203 if self.exitStatus is not None: return 204 win32api.TerminateProcess(self.__hProcess,0) 205 206 self.wait(timeout=timeout) 207 except: 208 raise ProcessError, "Error stopping process"
209 210
211 - def signal(self, signal):
212 """Send a signal to a running process. 213 214 Note that this method is not implemented for win32 processes, and calling this on a 215 win32 OS will raise a NotImplementedError. 216 217 @param signal: The integer signal to send to the process 218 @raise ProcessError: Raised if an error occurred whilst trying to signal the process 219 220 """ 221 raise NotImplementedError , "Unable to send a signal to a windows process"
222