1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import sys, os, time
21
22 from pysys import log
23 from pysys.constants import *
24 from pysys.utils.filegrep import getmatches
25 from pysys.process.helper import ProcessWrapper
26
27
29 """Class providing basic operations over interacting with processes.
30
31 The ProcessUser class provides the mimimum set of operations for managing and interacting with
32 processes. The class is designed to be extended by the L{pysys.baserunner.BaseRunner} and
33 L{pysys.basetest.BaseTest} classes so that they prescribe a common set of process operations
34 that any application helper classes can use, i.e. where an application helper class is
35 instantiated with a call back reference to the runner or base test for the process operations.
36
37 @ivar input: Location for input to any processes (defaults to current working directory)
38 @type input: string
39 @ivar output: Location for output from any processes (defaults to current working directory)
40 @type output: string
41
42 """
43
45 """Default constructor.
46
47 """
48 self.processList = []
49 self.processCount = {}
50
51
53 """Set self.input or self.output to the current working directory if not defined.
54
55 """
56 if name == "input" or name == "output":
57 return os.getcwd()
58 else:
59 raise AttributeError("Unknown class attribute ", name)
60
61
63 """Return the number of processes started within the testcase matching the supplied displayName.
64
65 The ProcessUserInterface class maintains a reference count of processes started within the class instance
66 via the L{startProcess()} method. The reference count is maintained against a logical name for
67 the process, which is the displayName used in the method call to L{startProcess()}, or the
68 basename of the command if no displayName was supplied. The method returns the number of
69 processes started with the supplied logical name, or 0 if no processes have been started.
70
71 @param displayName: The process display name
72 @return: The number of processes started matching the command basename
73 @rtype: integer
74
75 """
76 if self.processCount.has_key(displayName):
77 return self.processCount[displayName]
78 else:
79 return 0
80
81
82
83 - def startProcess(self, command, arguments, environs={}, workingDir=None, state=FOREGROUND, timeout=None, stdout=None, stderr=None, displayName=None):
84 """Start a process running in the foreground or background, and return the process handle.
85
86 The method allows spawning of new processes in a platform independent way. The command, arguments, environment and
87 working directory to run the process in can all be specified in the arguments to the method, along with the filenames
88 used for capturing the stdout and stderr of the process. Processes may be started in the C{FOREGROUND}, in which case
89 the method does not return until the process has completed or a time out occurs, or in the C{BACKGROUND} in which case
90 the method returns immediately to the caller returning a handle to the process to allow manipulation at a later stage.
91 All processes started in the C{BACKGROUND} and not explicitly killed using the returned process handle are automatically
92 killed on completion of the test via the L{__del__()} destructor.
93
94 @param command: The command to start the process (should include the full path)
95 @param arguments: A list of arguments to pass to the command
96 @param environs: A dictionary of the environment to run the process in (defaults to clean environment)
97 @param workingDir: The working directory for the process to run in (defaults to the testcase output subdirectory)
98 @param state: Run the process either in the C{FOREGROUND} or C{BACKGROUND} (defaults to C{FOREGROUND})
99 @param timeout: The timeout period after which to termintate processes running in the C{FOREGROUND}
100 @param stdout: The filename used to capture the stdout of the process
101 @param stderr: The filename user to capture the stderr of the process
102 @param displayName: Logical name of the process used for display and reference counting (defaults to the basename of the command)
103 @return: The process handle of the process (L{pysys.process.helper.ProcessWrapper})
104 @rtype: handle
105
106 """
107 if workingDir is None: workingDir = r'%s' % self.output
108 if displayName is None: displayName = os.path.basename(command)
109
110 try:
111 process = ProcessWrapper(command, arguments, environs, workingDir, state, timeout, stdout, stderr)
112 process.start()
113 if state == FOREGROUND:
114 log.info("Executed %s in foreground with exit status = %d", displayName, process.exitStatus)
115 elif state == BACKGROUND:
116 log.info("Started %s in background with process id %d", displayName, process.pid)
117 except ProcessError:
118 log.info("%s", sys.exc_info()[1], exc_info=0)
119 except ProcessTimeout:
120 log.info("Process timedout after %d seconds, stopping process", timeout)
121 process.stop()
122 else:
123 self.processList.append(process)
124 try:
125 if self.processCount.has_key(displayName):
126 self.processCount[displayName] = self.processCount[displayName] + 1
127 else:
128 self.processCount[displayName] = 1
129 except:
130 pass
131 return process
132
133
135 """Send a soft or hard kill to a running process to stop its execution.
136
137 This method uses the L{pysys.process.helper} module to stop a running process.
138
139 @param process: The process handle returned from the L{startProcess()} method
140
141 """
142 if process.running():
143 try:
144 process.stop()
145 log.info("Stopped process with process id %d", process.pid)
146 except ProcessError:
147 log.info("Unable to stop process")
148
149
151 """Send a signal to a running process (Unix only).
152
153 This method uses the L{pysys.process.helper} module to send a signal to a running
154 process.
155
156 @param process: The process handle returned from the L{startProcess()} method
157 @param signal: The integer value of the signal to send
158
159 """
160 if process.running():
161 try:
162 process.signal(signal)
163 log.info("Sent %d signal to process with process id %d", signal, process.pid)
164 except ProcessError:
165 log.info("Unable to send signal to process")
166
167
169 """Wait for a process to terminate, return on termination or expiry of the timeout.
170
171 @param process: The process handle returned from the L{startProcess()} method
172 @param timeout: The timeout value in seconds to wait before returning
173
174 """
175 try:
176 log.info("Waiting %d secs for process with process id %d", timeout, process.pid)
177 process.wait(timeout)
178 except ProcessTimeout:
179 log.warn("Timedout waiting for process")
180
181
183 """Write data to the stdin of a process.
184
185 This method uses the L{pysys.process.helper} module to write a data string to the
186 stdin of a process. This wrapper around the write method of the process helper only
187 adds checking of the process running status prior to the write being performed, and
188 logging to the testcase run log to detail the write.
189
190 @param process: The process handle returned from the L{startProcess()} method
191 @param data: The data to write to the process
192 @param addNewLine: True if a new line character is to be added to the end of the data string
193
194 """
195 if process.running():
196 process.write(data, addNewLine)
197 log.info("Written to stdin of process with process id %d", process.pid)
198 log.debug(" %s" % data)
199 else:
200 log.info("Write to process with process id %d stdin not performed as process is not running", process.pid)
201
202
203 - def waitForSocket(self, port, host='localhost', timeout=TIMEOUTS['WaitForSocket']):
204 """Wait for a socket connection to be established.
205
206 This method blocks until connection to a particular host:port pair can be established.
207 This is useful for test timing where a component under test creates a socket for client
208 server interaction - calling of this method ensures that on return of the method call
209 the server process is running and a client is able to create connections to it. If a
210 connection cannot be made within the specified timeout interval, the method returns
211 to the caller.
212
213 @param port: The port value in the socket host:port pair
214 @param host: The host value in the socket host:port pair
215 @param timeout: The timeout in seconds to wait for connection to the socket
216
217 """
218 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
219
220 log.debug("Performing wait for socket creation:")
221 log.debug(" file: %d" % port)
222 log.debug(" filedir: %s" % host)
223
224 exit = False
225 startTime = time.time()
226 while not exit:
227 try:
228 s.connect((host, port))
229 exit = True
230 except socket.error:
231 if timeout:
232 currentTime = time.time()
233 if currentTime > startTime + timeout:
234 log.info("Timedout waiting for creation of socket")
235 break
236 time.sleep(0.01)
237 if exit: log.debug("Wait for socket creation completed successfully")
238
239
240 - def waitForFile(self, file, filedir=None, timeout=TIMEOUTS['WaitForFile']):
241 """Wait for a file to be written to disk.
242
243 This method blocks until a file is created on disk. This is useful for test timing where
244 a component under test creates a file (e.g. for logging) indicating it has performed all
245 initialisation actions and is ready for the test execution steps. If a file is not created
246 on disk within the specified timeout interval, the method returns to the caller.
247
248 @param file: The basename of the file used to wait to be created
249 @param filedir: The dirname of the file (defaults to the testcase output subdirectory)
250 @param timeout: The timeout in seconds to wait for the file to be created
251
252 """
253 if filedir is None: filedir = self.output
254 f = os.path.join(filedir, file)
255
256 log.debug("Performing wait for file creation:")
257 log.debug(" file: %s" % file)
258 log.debug(" filedir: %s" % filedir)
259
260 exit = False
261 startTime = time.time()
262 while not exit:
263 if timeout:
264 currentTime = time.time()
265 if currentTime > startTime + timeout:
266 log.info("Timedout waiting for creation of file %s" % file)
267 break
268 time.sleep(0.01)
269 exit = os.path.exists(f)
270 if exit: log.debug("Wait for file creation completed successfully")
271
272
273 - def waitForSignal(self, file, filedir=None, expr="", condition=">=1", timeout=TIMEOUTS['WaitForSignal'], poll=0.25):
274 """Wait for a particular regular expression to be seen on a set number of lines in a text file.
275
276 This method blocks until a particular regular expression is seen in a text file on a set
277 number of lines. The number of lines which should match the regular expression is given by
278 the C{condition} argument in textual form i.e. for a match on more than 2 lines use condition =\">2\".
279 If the regular expression is not seen in the file matching the supplied condition within the
280 specified timeout interval, the method returns to the caller.
281
282 @param file: The basename of the file used to wait for the signal
283 @param filedir: The dirname of the file (defaults to the testcase output subdirectory)
284 @param expr: The regular expression to search for in the text file
285 @param condition: The condition to be met for the number of lines matching the regular expression
286 @param timeout: The timeout in seconds to wait for the regular expression and to check against the condition
287 @param poll: The time in seconds to poll the file looking for the regular expression and to check against the condition
288 """
289 if filedir is None: filedir = self.output
290 f = os.path.join(filedir, file)
291
292 log.debug("Performing wait for signal in file:")
293 log.debug(" file: %s" % file)
294 log.debug(" filedir: %s" % filedir)
295 log.debug(" expression: %s" % expr)
296 log.debug(" condition: %s" % condition)
297
298 matches = []
299 startTime = time.time()
300 while 1:
301 if os.path.exists(f):
302 matches = getmatches(f, expr)
303 if eval("%d %s" % (len(matches), condition)):
304 log.info("Wait for signal in %s completed successfully", file)
305 break
306
307 currentTime = time.time()
308 if currentTime > startTime + timeout:
309 log.info("Wait for signal in %s timedout", file)
310 log.info("Number of matches to the expression are %d" % len(matches))
311 break
312 time.sleep(poll)
313 return matches
314
315
317 """Class destructor which stops any running processes started by the class instance.
318
319 """
320 for process in self.processList:
321 try:
322 if process.running(): process.stop()
323 except:
324 log.info("caught %s: %s", sys.exc_info()[0], sys.exc_info()[1], exc_info=1)
325 self.processList = []
326 self.processCount = {}
327