Package qm :: Package test :: Module target
[hide private]
[frames] | no frames]

Source Code for Module qm.test.target

  1  ######################################################################## 
  2  # 
  3  # File:   target.py 
  4  # Author: Mark Mitchell 
  5  # Date:   10/29/2001 
  6  # 
  7  # Contents: 
  8  #   QMTest Target class. 
  9  # 
 10  # Copyright (c) 2001, 2002, 2003 by CodeSourcery, LLC.  All rights reserved.  
 11  # 
 12  # For license terms see the file COPYING. 
 13  # 
 14  ######################################################################## 
 15   
 16  ######################################################################## 
 17  # imports 
 18  ######################################################################## 
 19   
 20  import qm 
 21  import qm.common 
 22  import qm.extension 
 23  import qm.platform 
 24  import qm.test.base 
 25  from   qm.test.context import * 
 26  from   qm.test.result import * 
 27  from   qm.test.database import NoSuchResourceError 
 28  import re 
 29  import signal 
 30  import sys 
 31   
 32  ######################################################################## 
 33  # classes 
 34  ######################################################################## 
 35   
36 -class Target(qm.extension.Extension):
37 """Base class for target implementations. 38 39 A 'Target' is an entity that can run tests. QMTest can spread the 40 workload from multiple tests across multiple targets. In 41 addition, a single target can run more than one test at once. 42 43 'Target' is an abstract class. 44 45 You can extend QMTest by providing your own target class 46 implementation. 47 48 To create your own target class, you must create a Python class 49 derived (directly or indirectly) from 'Target'. The documentation 50 for each method of 'Target' indicates whether you must override it 51 in your target class implementation. Some methods may be 52 overridden, but do not need to be. You might want to override 53 such a method to provide a more efficient implementation, but 54 QMTest will work fine if you just use the default version.""" 55 56 arguments = [ 57 qm.fields.TextField( 58 name="name", 59 title="Name", 60 description="""The name of this target. 61 62 The name of the target. The target name will be recorded 63 in any tests executed on that target so that you can see 64 where the test was run.""", 65 default_value=""), 66 qm.fields.TextField( 67 name="group", 68 title="Group", 69 description="""The group associated with this target. 70 71 Some tests may only be able to run on some targets. A 72 test can specify a pattern indicating the set of targets 73 on which it will run.""", 74 default_value="") 75 ] 76 77 kind = "target" 78
79 - class __ResourceSetUpException(Exception):
80 """An exception indicating that a resource could not be set up.""" 81
82 - def __init__(self, resource):
83 """Construct a new 'ResourceSetUpException'. 84 85 'resource' -- The name of the resoure that could not be 86 set up.""" 87 88 self.resource = resource
89 90 91
92 - def __init__(self, database, arguments = None, **args):
93 """Construct a 'Target'. 94 95 'database' -- The 'Database' containing the tests that will be 96 run. 97 98 'arguments' -- As for 'Extension.__init__'. 99 100 'args' -- As for 'Extension.__init__'.""" 101 102 if arguments: args.update(arguments) 103 super(Target, self).__init__(**args) 104 105 self.__database = database
106 107
108 - def GetName(self):
109 """Return the name of the target. 110 111 Derived classes must not override this method.""" 112 113 return self.name
114 115
116 - def GetGroup(self):
117 """Return the group of which the target is a member. 118 119 Derived classes must not override this method.""" 120 121 return self.group
122 123
124 - def GetDatabase(self):
125 """Return the 'Database' containing the tests this target will run. 126 127 returns -- The 'Database' containing the tests this target will 128 run. 129 130 Derived classes must not override this method.""" 131 132 return self.__database
133 134
135 - def IsIdle(self):
136 """Return true if the target is idle. 137 138 returns -- True if the target is idle. If the target is idle, 139 additional tasks may be assigned to it. 140 141 Derived classes must override this method.""" 142 143 raise NotImplementedError
144 145
146 - def IsInGroup(self, group_pattern):
147 """Returns true if this 'Target' is in a particular group. 148 149 'group_pattern' -- A string giving a regular expression. 150 151 returns -- Returns true if the 'group_pattern' denotes a 152 regular expression that matches the group for this 'Target', 153 false otherwise.""" 154 155 return re.match(group_pattern, self.GetGroup())
156 157
158 - def Start(self, response_queue, engine=None):
159 """Start the target. 160 161 'response_queue' -- The 'Queue' in which the results of test 162 executions are placed. 163 164 'engine' -- The 'ExecutionEngine' that is starting the target, 165 or 'None' if this target is being started without an 166 'ExecutionEngine'. 167 168 Derived classes may override this method, but the overriding 169 method must call this method at some point during its 170 execution.""" 171 172 self.__response_queue = response_queue 173 self.__engine = engine 174 # There are no resources available on this target yet. 175 self.__resources = {} 176 self.__order_of_resources = []
177 178
179 - def Stop(self):
180 """Stop the target. 181 182 Clean up all resources that have been set up on this target 183 and take whatever other actions are required to stop the 184 target. 185 186 Derived classes may override this method.""" 187 188 # Clean up any available resources. 189 self.__order_of_resources.reverse() 190 for name in self.__order_of_resources: 191 rop = self.__resources[name] 192 if rop and rop[1] == Result.PASS: 193 self._CleanUpResource(name, rop[0]) 194 del self.__response_queue 195 del self.__engine 196 del self.__resources 197 del self.__order_of_resources
198 199
200 - def RunTest(self, descriptor, context):
201 """Run the test given by 'test_id'. 202 203 'descriptor' -- The 'TestDescriptor' for the test. 204 205 'context' -- The 'Context' in which to run the test. 206 207 Derived classes may override this method.""" 208 209 # Create the result. 210 result = Result(Result.TEST, descriptor.GetId()) 211 try: 212 # Augment the context appropriately. 213 context = Context(context) 214 context[context.TARGET_CONTEXT_PROPERTY] = self.GetName() 215 context[context.TMPDIR_CONTEXT_PROPERTY] \ 216 = self._GetTemporaryDirectory() 217 context[context.DB_PATH_CONTEXT_PROPERTY] \ 218 = descriptor.GetDatabase().GetPath() 219 # Set up any required resources. 220 self.__SetUpResources(descriptor, context) 221 # Make the ID of the test available. 222 context[context.ID_CONTEXT_PROPERTY] = descriptor.GetId() 223 # Note the start time. 224 result[Result.START_TIME] = qm.common.format_time_iso() 225 # Run the test. 226 try: 227 descriptor.Run(context, result) 228 finally: 229 # Note the end time. 230 result[Result.END_TIME] = qm.common.format_time_iso() 231 except KeyboardInterrupt: 232 result.NoteException(cause = "Interrupted by user.") 233 # We received a KeyboardInterrupt, indicating that the 234 # user would like to exit QMTest. Ask the execution 235 # engine to stop. 236 if self.__engine: 237 self.__engine.RequestTermination() 238 except qm.platform.SignalException, e: 239 # Note the exception. 240 result.NoteException(cause = str(e)) 241 # If we get a SIGTERM, propagate it so that QMTest 242 # terminates. 243 if e.GetSignalNumber() == signal.SIGTERM: 244 # Record the result so that the traceback is 245 # available. 246 self._RecordResult(result) 247 # Ask the execution engine to stop running tests. 248 if self.__engine: 249 self.__engine.RequestTermination() 250 # Re-raise the exception. 251 raise 252 except self.__ResourceSetUpException, e: 253 result.SetOutcome(Result.UNTESTED) 254 result[Result.CAUSE] = qm.message("failed resource") 255 result[Result.RESOURCE] = e.resource 256 except: 257 result.NoteException() 258 # Record the result. 259 self._RecordResult(result)
260 261
262 - def _RecordResult(self, result):
263 """Record the 'result'. 264 265 'result' -- A 'Result' of a test or resource execution. 266 267 Derived classes may override this method, but the overriding 268 method must call this method at some point during its 269 execution.""" 270 271 # Record the target in the result. 272 result[Result.TARGET] = self.GetName() 273 # Put the result into the response queue. 274 self.__response_queue.put(result)
275 276
277 - def _BeginResourceSetUp(self, resource_name):
278 """Begin setting up the indicated resource. 279 280 'resource_name' -- A string naming a resource. 281 282 returns -- If at attempt to set up the resource has already 283 been made, returns a tuple '(resource, outcome, properties)'. 284 The 'resource' is the 'Resource' object itself, but may be 285 'None' if the resource could not be set up. The 'outcome' 286 indicates the outcome that resulted when the resource was set 287 up. The 'properties' are a map from strings to strings 288 indicating properties added by this resource. 289 290 If the resource has not been set up, but _BeginResourceSetUp 291 has already been called for the resource, then the contents of 292 the tuple will all be 'None'. 293 294 If this is the first time _BeginResourceSetUp has been called 295 for this resource, then 'None' is returned, but the resource 296 is marked as in the process of being set up. It is the 297 caller's responsibility to finish setting it up by calling 298 '_FinishResourceSetUp'.""" 299 300 rop = self.__resources.get(resource_name) 301 if rop: 302 return rop 303 self.__resources[resource_name] = (None, None, None) 304 return None
305 306
307 - def _FinishResourceSetUp(self, resource, result, properties):
308 """Finish setting up a resource. 309 310 'resource' -- The 'Resource' itself. 311 312 'result' -- The 'Result' associated with setting up the 313 resource. 314 315 'properties' -- A dictionary of additional context properties 316 that should be provided to tests that depend on this resource. 317 318 returns -- A tuple of the same form as is returned by 319 '_BeginResourceSetUp' when the resource has already been set 320 up.""" 321 322 # The temporary directory is not be preserved; there is no 323 # guarantee that it will be the same in a test that depends on 324 # this resource as it was in the resource itself. 325 del properties[Context.TMPDIR_CONTEXT_PROPERTY] 326 rop = (resource, result.GetOutcome(), properties) 327 self.__resources[result.GetId()] = rop 328 self.__order_of_resources.append(result.GetId()) 329 return rop
330 331
332 - def __SetUpResources(self, descriptor, context):
333 """Set up all the resources associated with 'descriptor'. 334 335 'descriptor' -- The 'TestDescriptor' or 'ResourceDescriptor' 336 indicating the test or resource that is about to be run. 337 338 'context' -- The 'Context' in which the resources will be 339 executed. 340 341 returns -- A tuple of the same form as is returned by 342 '_BeginResourceSetUp' when the resource has already been set 343 up.""" 344 345 # See if there are resources that need to be set up. 346 for resource in descriptor.GetResources(): 347 (r, outcome, resource_properties) \ 348 = self._SetUpResource(resource, context) 349 350 # If the resource was not set up successfully, 351 # indicate that the test itself could not be run. 352 if outcome != Result.PASS: 353 raise self.__ResourceSetUpException, resource 354 # Update the list of additional context properties. 355 context.update(resource_properties) 356 357 return context
358 359
360 - def _SetUpResource(self, resource_name, context):
361 """Set up the resource given by 'resource_id'. 362 363 'resource_name' -- The name of the resource to be set up. 364 365 'context' -- The 'Context' in which to run the resource. 366 367 returns -- A map from strings to strings indicating additional 368 properties added by this resource.""" 369 370 # Begin setting up the resource. 371 rop = self._BeginResourceSetUp(resource_name) 372 # If it has already been set up, there is no need to do it 373 # again. 374 if rop: 375 return rop 376 # Set up the context. 377 wrapper = Context(context) 378 result = Result(Result.RESOURCE_SETUP, resource_name, Result.PASS) 379 resource = None 380 # Get the resource descriptor. 381 try: 382 resource_desc = self.GetDatabase().GetResource(resource_name) 383 # Set up the resources on which this resource depends. 384 self.__SetUpResources(resource_desc, wrapper) 385 # Make the ID of the resource available. 386 wrapper[Context.ID_CONTEXT_PROPERTY] = resource_name 387 # Set up the resource itself. 388 try: 389 resource_desc.SetUp(wrapper, result) 390 finally: 391 del wrapper[Context.ID_CONTEXT_PROPERTY] 392 # Obtain the resource within the try-block so that if it 393 # cannot be obtained the exception is handled below. 394 resource = resource_desc.GetItem() 395 except self.__ResourceSetUpException, e: 396 result.Fail(qm.message("failed resource"), 397 { result.RESOURCE : e.resource }) 398 except NoSuchResourceError: 399 result.NoteException(cause="Resource is missing from the database.") 400 self._RecordResult(result) 401 return (None, result, None) 402 except qm.test.base.CouldNotLoadExtensionError, e: 403 result.NoteException(e.exc_info, 404 cause = "Could not load extension class") 405 except KeyboardInterrupt: 406 result.NoteException() 407 # We received a KeyboardInterrupt, indicating that the 408 # user would like to exit QMTest. Ask the execution 409 # engine to stop. 410 if self.__engine: 411 self.__engine.RequestTermination() 412 except: 413 result.NoteException() 414 # Record the result. 415 self._RecordResult(result) 416 # And update the table of available resources. 417 return self._FinishResourceSetUp(resource, result, 418 wrapper.GetAddedProperties())
419 420
421 - def _CleanUpResource(self, name, resource):
422 """Clean up the 'resource'. 423 424 'resource' -- The 'Resource' that should be cleaned up. 425 426 'name' -- The name of the resource itself.""" 427 428 result = Result(Result.RESOURCE_CLEANUP, name) 429 # Clean up the resource. 430 try: 431 val = resource.CleanUp(result) 432 except: 433 result.NoteException() 434 self._RecordResult(result)
435 436
437 - def _GetTemporaryDirectory(self):
438 """Return the path to a temporary directory. 439 440 returns -- The path to a temporary directory to pass along to 441 tests and resources via the 'TMPDIR_CONTEXT_PROPERTY'.""" 442 443 raise NotImplementedError
444