1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 """
21 Contains implementations of test output summary writers used to output test results during runtime execution.
22
23 There are currently three implementations of writers distributed with the PySys framework,
24 namely the L{writer.TextResultsWriter}, the L{writer.XMLResultsWriter} and the
25 L{writer.JUnitXMLResultsWriter}. Project configuration of the writers is through the PySys
26 project file using the <writer> tag - multiple writers may be configured and their individual
27 properties set through the nested <property> tag. Writer properties are set as attributes to
28 the class through the setattr() function. Custom (site specific) modules can be created and
29 configured by users of the PySys framework (e.g. to output test results into a relational
30 database etc), though they must adhere to the interface demonstrated by the implementations
31 demonstrated here.
32
33 The writers are instantiated and invoked by the L{pysys.baserunner.BaseRunner} class
34 instance. This calls the class constructors of all configured test writers, and then
35 the setup (prior to executing the set of tests), processResult (process a test result),
36 and cleanup (upon completion of the execution of all tests). The **kwargs method parameter
37 is used for variable argument passing in the interface methods to allow modification of
38 the PySys framework without breaking writer implementations already in existence. Currently
39 the L{pysys.baserunner.BaseRunner} includes numTests in the call to the setup action (the
40 number of tests to be executed), and cycle in the call to the processResult action
41 (the cycle number when iterations through the same set of tests was requested).
42
43 """
44
45 __all__ = ["TextResultsWriter", "XMLResultsWriter", "JUnitXMLResultsWriter"]
46
47 import logging, time, urlparse, os, stat
48
49 from pysys import log
50 from pysys.constants import *
51 from pysys.exceptions import *
52
53 from xml.dom.minidom import getDOMImplementation
54
56 """Class to flush on each write operation.
57
58 """
59 fp=None
60
62 """Create an instance of the class.
63
64 @param fp: The file object
65
66 """
67 self.fp = fp
68
70 """Perform a write to the file object.
71
72 @param msg: The string message to write.
73
74 """
75 if self.fp is not None:
76 self.fp.write(msg)
77 self.fp.flush()
78
79 - def seek(self, index):
80 """Perform a seek on the file objet.
81
82 """
83 if self.fp is not None: self.fp.seek(index)
84
86 """Close the file objet.
87
88 """
89 if self.fp is not None: self.fp.close()
90
91
93 """Class to log results to logfile in text format.
94
95 Writing of the test summary file defaults to the working directory. This can be be over-ridden in the PySys
96 project file using the nested <property> tag on the <writer> tag.
97
98 @ivar outputDir: Path to output directory to write the test summary files
99 @type outputDir: string
100
101 """
102 outputDir = None
103
104 - def __init__(self, logfile):
105 """Create an instance of the TextResultsWriter class.
106
107 @param logfile: The filename template for the logging of test results
108
109 """
110 self.logfile = time.strftime(logfile, time.gmtime(time.time()))
111 self.cycle = -1
112 self.fp = None
113
114
115 - def setup(self, **kwargs):
116 """Implementation of the setup method.
117
118 Creates the file handle to the logfile and logs initial details of the date,
119 platform and test host.
120
121 @param kwargs: Variable argument list
122
123 """
124 self.logfile = os.path.join(self.outputDir, self.logfile) if self.outputDir is not None else self.logfile
125
126 try:
127 self.fp = flushfile(open(self.logfile, "w"))
128 self.fp.write('DATE: %s (GMT)\n' % (time.strftime('%y-%m-%d %H:%M:%S', time.gmtime(time.time())) ))
129 self.fp.write('PLATFORM: %s\n' % (PLATFORM))
130 self.fp.write('TEST HOST: %s\n' % (HOSTNAME))
131 except:
132 pass
133
134
135 - def cleanup(self, **kwargs):
136 """Implementation of the cleanup method.
137
138 Flushes and closes the file handle to the logfile.
139
140 @param kwargs: Variable argument list
141
142 """
143 try:
144 if self.fp:
145 self.fp.write('\n\n\n')
146 self.fp.close()
147 except:
148 log.info("caught %s: %s", sys.exc_info()[0], sys.exc_info()[1], exc_info=1)
149
150
151 - def processResult(self, testObj, **kwargs):
152 """Implementation of the processResult method.
153
154 Writes the test id and outcome to the logfile.
155
156 @param testObj: Reference to an instance of a L{pysys.basetest.BaseTest} class
157 @param kwargs: Variable argument list
158
159 """
160 if kwargs.has_key("cycle"):
161 if self.cycle != kwargs["cycle"]:
162 self.cycle = kwargs["cycle"]
163 self.fp.write('\n[Cycle %d]:\n'%self.cycle)
164
165 self.fp.write("%s: %s\n" % (LOOKUP[testObj.getOutcome()], testObj.descriptor.id))
166
167
168
170 """Class to log results to logfile in XML format.
171
172 The class creates a DOM document to represent the test output results and writes the DOM to the
173 logfile using toprettyxml(). The outputDir, stylesheet, useFileURL attributes of the class can
174 be over-ridden in the PySys project file using the nested <property> tag on the <writer> tag.
175
176 @ivar outputDir: Path to output directory to write the test summary files
177 @type outputDir: string
178 @ivar stylesheet: Path to the XSL stylesheet
179 @type stylesheet: string
180 @ivar useFileURL: Indicates if full file URLs are to be used for local resource references
181 @type useFileURL: string (true | false)
182
183 """
184 outputDir = None
185 stylesheet = DEFAULT_STYLESHEET
186 useFileURL = "false"
187
189 """Create an instance of the TextResultsWriter class.
190
191 @param logfile: The filename template for the logging of test results
192
193 """
194 self.logfile = time.strftime(logfile, time.gmtime(time.time()))
195 self.cycle = -1
196 self.numResults = 0
197 self.fp = None
198
199
200 - def setup(self, **kwargs):
201 """Implementation of the setup method.
202
203 Creates the DOM for the test output summary and writes to logfile.
204
205 @param kwargs: Variable argument list
206
207 """
208 self.numTests = kwargs["numTests"] if kwargs.has_key("numTests") else 0
209 self.logfile = os.path.join(self.outputDir, self.logfile) if self.outputDir is not None else self.logfile
210
211 try:
212 self.fp = flushfile(open(self.logfile, "w"))
213
214 impl = getDOMImplementation()
215 self.document = impl.createDocument(None, "pysyslog", None)
216 stylesheet = self.document.createProcessingInstruction("xml-stylesheet", "href=\"%s\" type=\"text/xsl\"" % (self.stylesheet))
217 self.document.insertBefore(stylesheet, self.document.childNodes[0])
218
219
220 self.rootElement = self.document.documentElement
221 self.statusAttribute = self.document.createAttribute("status")
222 self.statusAttribute.value="running"
223 self.rootElement.setAttributeNode(self.statusAttribute)
224
225 self.completedAttribute = self.document.createAttribute("completed")
226 self.completedAttribute.value="%s/%s" % (self.numResults, self.numTests)
227 self.rootElement.setAttributeNode(self.completedAttribute)
228
229
230 element = self.document.createElement("timestamp")
231 element.appendChild(self.document.createTextNode(time.strftime('%y-%m-%d %H:%M:%S', time.gmtime(time.time()))))
232 self.rootElement.appendChild(element)
233
234
235 element = self.document.createElement("platform")
236 element.appendChild(self.document.createTextNode(PLATFORM))
237 self.rootElement.appendChild(element)
238
239
240 element = self.document.createElement("host")
241 element.appendChild(self.document.createTextNode(HOSTNAME))
242 self.rootElement.appendChild(element)
243
244
245 element = self.document.createElement("root")
246 element.appendChild(self.document.createTextNode(self.__pathToURL(PROJECT.root)))
247 self.rootElement.appendChild(element)
248
249
250 element = self.document.createElement("xargs")
251 if kwargs.has_key("xargs"):
252 for key in kwargs["xargs"].keys():
253 childelement = self.document.createElement("xarg")
254 nameAttribute = self.document.createAttribute("name")
255 valueAttribute = self.document.createAttribute("value")
256 nameAttribute.value=key
257 valueAttribute.value=kwargs["xargs"][key].__str__()
258 childelement.setAttributeNode(nameAttribute)
259 childelement.setAttributeNode(valueAttribute)
260 element.appendChild(childelement)
261 self.rootElement.appendChild(element)
262
263
264 self.fp.write(self.document.toprettyxml(indent=" "))
265 except:
266 log.info("caught %s: %s", sys.exc_info()[0], sys.exc_info()[1], exc_info=1)
267
268
270 """Implementation of the cleanup method.
271
272 Updates the test run status in the DOM, and re-writes to logfile.
273
274 @param kwargs: Variable argument list
275
276 """
277 self.fp.seek(0)
278 self.statusAttribute.value="complete"
279 self.fp.write(self.document.toprettyxml(indent=" "))
280 try:
281 if self.fp: self.fp.close()
282 except:
283 log.info("caught %s: %s", sys.exc_info()[0], sys.exc_info()[1], exc_info=1)
284
285
287 """Implementation of the processResult method.
288
289 Adds the results node to the DOM and re-writes to logfile.
290
291 @param testObj: Reference to an instance of a L{pysys.basetest.BaseTest} class
292 @param kwargs: Variable argument list
293
294 """
295 self.fp.seek(0)
296
297 if kwargs.has_key("cycle"):
298 if self.cycle != kwargs["cycle"]:
299 self.cycle = kwargs["cycle"]
300 self.__createResultsNode()
301
302
303 resultElement = self.document.createElement("result")
304 nameAttribute = self.document.createAttribute("id")
305 outcomeAttribute = self.document.createAttribute("outcome")
306 nameAttribute.value=testObj.descriptor.id
307 outcomeAttribute.value=LOOKUP[testObj.getOutcome()]
308 resultElement.setAttributeNode(nameAttribute)
309 resultElement.setAttributeNode(outcomeAttribute)
310
311 element = self.document.createElement("timestamp")
312 element.appendChild(self.document.createTextNode(time.strftime('%y-%m-%d %H:%M:%S', time.gmtime(time.time()))))
313 resultElement.appendChild(element)
314
315 element = self.document.createElement("descriptor")
316 element.appendChild(self.document.createTextNode(self.__pathToURL(testObj.descriptor.file)))
317 resultElement.appendChild(element)
318
319 element = self.document.createElement("output")
320 element.appendChild(self.document.createTextNode(self.__pathToURL(testObj.output)))
321 resultElement.appendChild(element)
322
323 self.resultsElement.appendChild(resultElement)
324
325
326 self.numResults = self.numResults + 1
327 self.completedAttribute.value="%s/%s" % (self.numResults, self.numTests)
328
329
330 self.fp.write(self.document.toprettyxml(indent=" "))
331
332
334 self.resultsElement = self.document.createElement("results")
335 cycleAttribute = self.document.createAttribute("cycle")
336 cycleAttribute.value="%d"%self.cycle
337 self.resultsElement.setAttributeNode(cycleAttribute)
338 self.rootElement.appendChild(self.resultsElement)
339
340
342 try:
343 if self.useFileURL.lower() == "false": return path
344 except:
345 return path
346 else:
347 return urlparse.urlunparse(["file", HOSTNAME, path.replace("\\", "/"), "","",""])
348
349
351 """Class to log test results in Apache Ant JUnit XML format (one output file per test per cycle).
352
353 @ivar outputDir: Path to output directory to write the test summary files
354 @type outputDir: string
355
356 """
357 outputDir = None
358
360 """Create an instance of the TextResultsWriter class.
361
362 @param logfile: The (optional) filename template for the logging of test results
363
364 """
365 self.cycle = -1
366
367
368 - def setup(self, **kwargs):
369 """Implementation of the setup method.
370
371 Creates the output directory for the writing of the test summary files.
372
373 @param kwargs: Variable argument list
374
375 """
376 self.outputDir = os.path.join(PROJECT.root, 'target','pysys-reports')
377 if kwargs.has_key("outputDir"): self.outputDir = kwargs["outputDir"]
378 if os.path.exists(self.outputDir): self.purgeDirectory(self.outputDir, True)
379 os.makedirs(self.outputDir)
380
381
383 """Implementation of the cleanup method.
384
385 @param kwargs: Variable argument list
386
387 """
388 pass
389
390
392 """Implementation of the processResult method.
393
394 Creates a test summary file in the Apache Ant Junit XML format.
395
396 @param testObj: Reference to an instance of a L{pysys.basetest.BaseTest} class
397 @param kwargs: Variable argument list
398
399 """
400 if kwargs.has_key("cycle"):
401 if self.cycle != kwargs["cycle"]:
402 self.cycle = kwargs["cycle"]
403
404 impl = getDOMImplementation()
405 document = impl.createDocument(None, 'testsuite', None)
406 rootElement = document.documentElement
407 attr1 = document.createAttribute('name')
408 attr1.value = testObj.descriptor.id
409 attr2 = document.createAttribute('tests')
410 attr2.value='1'
411 attr3 = document.createAttribute('failures')
412 attr3.value = '%d'%(int)(testObj.getOutcome() in FAILS)
413 attr4 = document.createAttribute('skipped')
414 attr4.value = '%d'%(int)(testObj.getOutcome() == SKIPPED)
415 rootElement.setAttributeNode(attr1)
416 rootElement.setAttributeNode(attr2)
417 rootElement.setAttributeNode(attr3)
418 rootElement.setAttributeNode(attr4)
419
420
421 testcase = document.createElement('testcase')
422 attr1 = document.createAttribute('classname')
423 attr1.value = testObj.descriptor.classname
424 attr2 = document.createAttribute('name')
425 attr2.value = testObj.descriptor.id
426 testcase.setAttributeNode(attr1)
427 testcase.setAttributeNode(attr2)
428
429
430 if (testObj.getOutcome() in FAILS):
431 failure = document.createElement('failure')
432 attr1 = document.createAttribute('message')
433 attr1.value = LOOKUP[testObj.getOutcome()]
434 failure.setAttributeNode(attr1)
435
436 stdout = document.createElement('system-out')
437 fp = open(os.path.join(testObj.output, 'run.log'))
438 stdout.appendChild(document.createTextNode(fp.read()))
439 fp.close()
440
441 testcase.appendChild(failure)
442 testcase.appendChild(stdout)
443 rootElement.appendChild(testcase)
444
445
446 if self.cycle > 0:
447 fp = open(os.path.join(self.outputDir,'TEST-%s.%s.xml'%(testObj.descriptor.id, self.cycle)), 'w')
448 else:
449 fp = open(os.path.join(self.outputDir,'TEST-%s.xml'%(testObj.descriptor.id)), 'w')
450 fp.write(document.toprettyxml(indent=' '))
451 fp.close()
452
453
455 for file in os.listdir(dir):
456 path = os.path.join(dir, file)
457 if PLATFORM in ['sunos', 'linux']:
458 mode = os.lstat(path)[stat.ST_MODE]
459 else:
460 mode = os.stat(path)[stat.ST_MODE]
461
462 if stat.S_ISLNK(mode):
463 os.unlink(path)
464 if stat.S_ISREG(mode):
465 os.remove(path)
466 elif stat.S_ISDIR(mode):
467 self.purgeDirectory(path, delTop=True)
468
469 if delTop:
470 os.rmdir(dir)
471