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 four implementations of writers distributed with the PySys framework,
24 namely the L{writer.TextResultsWriter}, the L{writer.XMLResultsWriter}, the
25 L{writer.JUnitXMLResultsWriter} and the L{writer.CSVResultsWriter}. Project configuration of
26 the writers is through the PySys project file using the <writer> tag - multiple writers may
27 be configured and their individual properties set through the nested <property> tag. Writer
28 properties are set as attributes to the class through the setattr() function. Custom (site
29 specific) modules can be created and configured by users of the PySys framework (e.g. to
30 output test results into a relational database etc), though they must adhere to the interface
31 demonstrated by the implementations 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", "CSVResultsWriter", "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("outcomeReason")
312 element.appendChild(self.document.createTextNode( testObj.getOutcomeReason() ))
313 resultElement.appendChild(element)
314
315 element = self.document.createElement("timestamp")
316 element.appendChild(self.document.createTextNode(time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(time.time()))))
317 resultElement.appendChild(element)
318
319 element = self.document.createElement("descriptor")
320 element.appendChild(self.document.createTextNode(self.__pathToURL(testObj.descriptor.file)))
321 resultElement.appendChild(element)
322
323 element = self.document.createElement("output")
324 element.appendChild(self.document.createTextNode(self.__pathToURL(testObj.output)))
325 resultElement.appendChild(element)
326
327 self.resultsElement.appendChild(resultElement)
328
329
330 self.numResults = self.numResults + 1
331 self.completedAttribute.value="%s/%s" % (self.numResults, self.numTests)
332
333
334 self.fp.write(self.document.toprettyxml(indent=" "))
335
336
338 self.resultsElement = self.document.createElement("results")
339 cycleAttribute = self.document.createAttribute("cycle")
340 cycleAttribute.value="%d"%self.cycle
341 self.resultsElement.setAttributeNode(cycleAttribute)
342 self.rootElement.appendChild(self.resultsElement)
343
344
346 try:
347 if self.useFileURL.lower() == "false": return path
348 except:
349 return path
350 else:
351 return urlparse.urlunparse(["file", HOSTNAME, path.replace("\\", "/"), "","",""])
352
353
355 """Class to log test results in Apache Ant JUnit XML format (one output file per test per cycle).
356
357 @ivar outputDir: Path to output directory to write the test summary files
358 @type outputDir: string
359
360 """
361 outputDir = None
362
364 """Create an instance of the TextResultsWriter class.
365
366 @param logfile: The (optional) filename template for the logging of test results
367
368 """
369 self.cycle = -1
370
371
372 - def setup(self, **kwargs):
373 """Implementation of the setup method.
374
375 Creates the output directory for the writing of the test summary files.
376
377 @param kwargs: Variable argument list
378
379 """
380 self.outputDir = os.path.join(PROJECT.root, 'target','pysys-reports') if self.outputDir is None else self.outputDir
381 if os.path.exists(self.outputDir): self.purgeDirectory(self.outputDir, True)
382 os.makedirs(self.outputDir)
383
384
386 """Implementation of the cleanup method.
387
388 @param kwargs: Variable argument list
389
390 """
391 pass
392
393
395 """Implementation of the processResult method.
396
397 Creates a test summary file in the Apache Ant Junit XML format.
398
399 @param testObj: Reference to an instance of a L{pysys.basetest.BaseTest} class
400 @param kwargs: Variable argument list
401
402 """
403 if kwargs.has_key("cycle"):
404 if self.cycle != kwargs["cycle"]:
405 self.cycle = kwargs["cycle"]
406
407 impl = getDOMImplementation()
408 document = impl.createDocument(None, 'testsuite', None)
409 rootElement = document.documentElement
410 attr1 = document.createAttribute('name')
411 attr1.value = testObj.descriptor.id
412 attr2 = document.createAttribute('tests')
413 attr2.value='1'
414 attr3 = document.createAttribute('failures')
415 attr3.value = '%d'%(int)(testObj.getOutcome() in FAILS)
416 attr4 = document.createAttribute('skipped')
417 attr4.value = '%d'%(int)(testObj.getOutcome() == SKIPPED)
418 rootElement.setAttributeNode(attr1)
419 rootElement.setAttributeNode(attr2)
420 rootElement.setAttributeNode(attr3)
421 rootElement.setAttributeNode(attr4)
422
423
424 testcase = document.createElement('testcase')
425 attr1 = document.createAttribute('classname')
426 attr1.value = testObj.descriptor.classname
427 attr2 = document.createAttribute('name')
428 attr2.value = testObj.descriptor.id
429 testcase.setAttributeNode(attr1)
430 testcase.setAttributeNode(attr2)
431
432
433 if (testObj.getOutcome() in FAILS):
434 failure = document.createElement('failure')
435 attr1 = document.createAttribute('message')
436 attr1.value = LOOKUP[testObj.getOutcome()]
437 failure.setAttributeNode(attr1)
438 failure.appendChild(document.createTextNode( testObj.getOutcomeReason() ))
439
440 stdout = document.createElement('system-out')
441 fp = open(os.path.join(testObj.output, 'run.log'))
442 stdout.appendChild(document.createTextNode(fp.read()))
443 fp.close()
444
445 testcase.appendChild(failure)
446 testcase.appendChild(stdout)
447 rootElement.appendChild(testcase)
448
449
450 if self.cycle > 0:
451 fp = open(os.path.join(self.outputDir,'TEST-%s.%s.xml'%(testObj.descriptor.id, self.cycle)), 'w')
452 else:
453 fp = open(os.path.join(self.outputDir,'TEST-%s.xml'%(testObj.descriptor.id)), 'w')
454 fp.write(document.toprettyxml(indent=' '))
455 fp.close()
456
457
459 for file in os.listdir(dir):
460 path = os.path.join(dir, file)
461 if PLATFORM in ['sunos', 'linux']:
462 mode = os.lstat(path)[stat.ST_MODE]
463 else:
464 mode = os.stat(path)[stat.ST_MODE]
465
466 if stat.S_ISLNK(mode):
467 os.unlink(path)
468 if stat.S_ISREG(mode):
469 os.remove(path)
470 elif stat.S_ISDIR(mode):
471 self.purgeDirectory(path, delTop=True)
472
473 if delTop:
474 os.rmdir(dir)
475
476
478 """Class to log results to logfile in CSV format.
479
480 Writing of the test summary file defaults to the working directory. This can be be over-ridden in the PySys
481 project file using the nested <property> tag on the <writer> tag. The CSV column output is in the form;
482
483 id, title, cycle, startTime, duration, outcome
484
485 @ivar outputDir: Path to output directory to write the test summary files
486 @type outputDir: string
487
488 """
489 outputDir = None
490
492 """Create an instance of the TextResultsWriter class.
493
494 @param logfile: The filename template for the logging of test results
495
496 """
497 self.logfile = time.strftime(logfile, time.gmtime(time.time()))
498 self.fp = None
499
500
501 - def setup(self, **kwargs):
502 """Implementation of the setup method.
503
504 Creates the file handle to the logfile and logs initial details of the date,
505 platform and test host.
506
507 @param kwargs: Variable argument list
508
509 """
510 self.logfile = os.path.join(self.outputDir, self.logfile) if self.outputDir is not None else self.logfile
511
512 try:
513 self.fp = flushfile(open(self.logfile, "w"))
514 self.fp.write('id, title, cycle, startTime, duration, outcome\n')
515 except:
516 pass
517
518
520 """Implementation of the cleanup method.
521
522 Flushes and closes the file handle to the logfile.
523
524 @param kwargs: Variable argument list
525
526 """
527 try:
528 if self.fp:
529 self.fp.write('\n\n\n')
530 self.fp.close()
531 except:
532 log.info("caught %s: %s", sys.exc_info()[0], sys.exc_info()[1], exc_info=1)
533
534
536 """Implementation of the processResult method.
537
538 Writes the test id and outcome to the logfile.
539
540 @param testObj: Reference to an instance of a L{pysys.basetest.BaseTest} class
541 @param kwargs: Variable argument list
542
543 """
544 testStart = kwargs["testStart"] if kwargs.has_key("testStart") else time.time()
545 testTime = kwargs["testTime"] if kwargs.has_key("testTime") else 0
546 cycle = kwargs["cycle"] if kwargs.has_key("cycle") else 0
547
548 csv = []
549 csv.append(testObj.descriptor.id)
550 csv.append('\"%s\"'%testObj.descriptor.title)
551 csv.append(str(cycle))
552 csv.append((time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(testStart))))
553 csv.append(str(testTime))
554 csv.append(LOOKUP[testObj.getOutcome()])
555 self.fp.write('%s \n' % ','.join(csv))
556