1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
30 EXPR = re.compile(".*\n$")
31
32
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
77 self.__hProcess = None
78 self.__hThread = None
79 self.__tid = None
80
81 self.__lock = threading.Lock()
82
83
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
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
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
124 """Method to start a process running in the background.
125
126 """
127 with process_lock:
128
129 sAttrs = win32security.SECURITY_ATTRIBUTES()
130 sAttrs.bInheritHandle = 1
131
132
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
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
149
150
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
157
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
170 self.__stdin = hStdin
171
172
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
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
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
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