1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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
80 """An exception indicating that a resource could not be set up."""
81
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
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
109 """Return the name of the target.
110
111 Derived classes must not override this method."""
112
113 return self.name
114
115
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
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
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
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
175 self.__resources = {}
176 self.__order_of_resources = []
177
178
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
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):
260
261
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
272 result[Result.TARGET] = self.GetName()
273
274 self.__response_queue.put(result)
275
276
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
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
323
324
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
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
346 for resource in descriptor.GetResources():
347 (r, outcome, resource_properties) \
348 = self._SetUpResource(resource, context)
349
350
351
352 if outcome != Result.PASS:
353 raise self.__ResourceSetUpException, resource
354
355 context.update(resource_properties)
356
357 return context
358
359
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
371 rop = self._BeginResourceSetUp(resource_name)
372
373
374 if rop:
375 return rop
376
377 wrapper = Context(context)
378 result = Result(Result.RESOURCE_SETUP, resource_name, Result.PASS)
379 resource = None
380
381 try:
382 resource_desc = self.GetDatabase().GetResource(resource_name)
383
384 self.__SetUpResources(resource_desc, wrapper)
385
386 wrapper[Context.ID_CONTEXT_PROPERTY] = resource_name
387
388 try:
389 resource_desc.SetUp(wrapper, result)
390 finally:
391 del wrapper[Context.ID_CONTEXT_PROPERTY]
392
393
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
408
409
410 if self.__engine:
411 self.__engine.RequestTermination()
412 except:
413 result.NoteException()
414
415 self._RecordResult(result)
416
417 return self._FinishResourceSetUp(resource, result,
418 wrapper.GetAddedProperties())
419
420
435
436
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