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):
263
264
266 """Record the 'result'.
267
268 'result' -- A 'Result' of a test or resource execution.
269
270 Derived classes may override this method, but the overriding
271 method must call this method at some point during its
272 execution."""
273
274
275 result[Result.TARGET] = self.GetName()
276
277 self.__response_queue.put(result)
278
279
281 """Begin setting up the indicated resource.
282
283 'resource_name' -- A string naming a resource.
284
285 returns -- If at attempt to set up the resource has already
286 been made, returns a tuple '(resource, outcome, properties)'.
287 The 'resource' is the 'Resource' object itself, but may be
288 'None' if the resource could not be set up. The 'outcome'
289 indicates the outcome that resulted when the resource was set
290 up. The 'properties' are a map from strings to strings
291 indicating properties added by this resource.
292
293 If the resource has not been set up, but _BeginResourceSetUp
294 has already been called for the resource, then the contents of
295 the tuple will all be 'None'.
296
297 If this is the first time _BeginResourceSetUp has been called
298 for this resource, then 'None' is returned, but the resource
299 is marked as in the process of being set up. It is the
300 caller's responsibility to finish setting it up by calling
301 '_FinishResourceSetUp'."""
302
303 rop = self.__resources.get(resource_name)
304 if rop:
305 return rop
306 self.__resources[resource_name] = (None, None, None)
307 return None
308
309
311 """Finish setting up a resource.
312
313 'resource' -- The 'Resource' itself.
314
315 'result' -- The 'Result' associated with setting up the
316 resource.
317
318 'properties' -- A dictionary of additional context properties
319 that should be provided to tests that depend on this resource.
320
321 returns -- A tuple of the same form as is returned by
322 '_BeginResourceSetUp' when the resource has already been set
323 up."""
324
325
326
327
328 del properties[Context.TMPDIR_CONTEXT_PROPERTY]
329 rop = (resource, result.GetOutcome(), properties)
330 self.__resources[result.GetId()] = rop
331 self.__order_of_resources.append(result.GetId())
332 return rop
333
334
336 """Set up all the resources associated with 'descriptor'.
337
338 'descriptor' -- The 'TestDescriptor' or 'ResourceDescriptor'
339 indicating the test or resource that is about to be run.
340
341 'context' -- The 'Context' in which the resources will be
342 executed.
343
344 returns -- A tuple of the same form as is returned by
345 '_BeginResourceSetUp' when the resource has already been set
346 up."""
347
348
349 for resource in descriptor.GetResources():
350 (r, outcome, resource_properties) \
351 = self._SetUpResource(resource, context)
352
353
354
355 if outcome != Result.PASS:
356 raise self.__ResourceSetUpException, resource
357
358 context.update(resource_properties)
359
360 return context
361
362
364 """Set up the resource given by 'resource_id'.
365
366 'resource_name' -- The name of the resource to be set up.
367
368 'context' -- The 'Context' in which to run the resource.
369
370 returns -- A map from strings to strings indicating additional
371 properties added by this resource."""
372
373
374 rop = self._BeginResourceSetUp(resource_name)
375
376
377 if rop:
378 return rop
379
380 wrapper = Context(context)
381 result = Result(Result.RESOURCE_SETUP, resource_name, Result.PASS)
382 resource = None
383
384 try:
385 resource_desc = self.GetDatabase().GetResource(resource_name)
386
387 self.__SetUpResources(resource_desc, wrapper)
388
389 wrapper[Context.ID_CONTEXT_PROPERTY] = resource_name
390
391 try:
392 resource_desc.SetUp(wrapper, result)
393 finally:
394 del wrapper[Context.ID_CONTEXT_PROPERTY]
395
396
397 resource = resource_desc.GetItem()
398 except self.__ResourceSetUpException, e:
399 result.Fail(qm.message("failed resource"),
400 { result.RESOURCE : e.resource })
401 except NoSuchResourceError:
402 result.NoteException(cause="Resource is missing from the database.")
403 self._RecordResult(result)
404 return (None, result, None)
405 except qm.test.base.CouldNotLoadExtensionError, e:
406 result.NoteException(e.exc_info,
407 cause = "Could not load extension class")
408 except KeyboardInterrupt:
409 result.NoteException()
410
411
412
413 if self.__engine:
414 self.__engine.RequestTermination()
415 except:
416 result.NoteException()
417
418 self._RecordResult(result)
419
420 return self._FinishResourceSetUp(resource, result,
421 wrapper.GetAddedProperties())
422
423
438
439
441 """Return the path to a temporary directory.
442
443 returns -- The path to a temporary directory to pass along to
444 tests and resources via the 'TMPDIR_CONTEXT_PROPERTY'."""
445
446 raise NotImplementedError
447