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

Source Code for Module pysys.process.user

  1  #!/usr/bin/env python 
  2  # PySys System Test Framework, Copyright (C) 2006-2013  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 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   
28 -class ProcessUser:
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
44 - def __init__(self):
45 """Default constructor. 46 47 """ 48 self.processList = [] 49 self.processCount = {}
50 51
52 - def __getattr__(self, name):
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
62 - def getInstanceCount(self, displayName):
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 # process manipulation methods of ProcessUserInterface
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
134 - def stopProcess(self, process):
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
150 - def signalProcess(self, process, signal):
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
168 - def waitProcess(self, process, timeout):
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
182 - def writeProcess(self, process, data, addNewLine=True):
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
316 - def __del__(self):
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