Package pysys :: Package writer
[hide private]
[frames] | no frames]

Source Code for Package pysys.writer

  1  #!/usr/bin/env python 
  2  # PySys System Test Framework, Copyright (C) 2006-2016  M.B.Grieve 
  3   
  4  # This library is free software; you can redistribute it and/or 
  5  # modify it under the terms of the GNU Lesser General Public 
  6  # License as published by the Free Software Foundation; either 
  7  # version 2.1 of the License, or (at your option) any later version. 
  8   
  9  # This library is distributed in the hope that it will be useful, 
 10  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 11  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 12  # Lesser General Public License for more details. 
 13   
 14  # You should have received a copy of the GNU Lesser General Public 
 15  # License along with this library; if not, write to the Free Software 
 16  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 
 17   
 18  # Contact: moraygrieve@users.sourceforge.net 
 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   
55 -class flushfile():
56 """Class to flush on each write operation. 57 58 """ 59 fp=None 60
61 - def __init__(self, fp):
62 """Create an instance of the class. 63 64 @param fp: The file object 65 66 """ 67 self.fp = fp 68
69 - def write(self, msg):
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
85 - def close(self):
86 """Close the file objet. 87 88 """ 89 if self.fp is not None: self.fp.close()
90 91
92 -class TextResultsWriter:
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
169 -class XMLResultsWriter:
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
188 - def __init__(self, logfile):
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 # create the root and add in the status, number of tests and number completed 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 # add the data node 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 # add the platform node 235 element = self.document.createElement("platform") 236 element.appendChild(self.document.createTextNode(PLATFORM)) 237 self.rootElement.appendChild(element) 238 239 # add the test host node 240 element = self.document.createElement("host") 241 element.appendChild(self.document.createTextNode(HOSTNAME)) 242 self.rootElement.appendChild(element) 243 244 # add the test host node 245 element = self.document.createElement("root") 246 element.appendChild(self.document.createTextNode(self.__pathToURL(PROJECT.root))) 247 self.rootElement.appendChild(element) 248 249 # add the extra params nodes 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 # write the file out 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
269 - def cleanup(self, **kwargs):
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
286 - def processResult(self, testObj, **kwargs):
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 # create the results entry 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 # update the count of completed tests 330 self.numResults = self.numResults + 1 331 self.completedAttribute.value="%s/%s" % (self.numResults, self.numTests) 332 333 # write the file out 334 self.fp.write(self.document.toprettyxml(indent=" "))
335 336
337 - def __createResultsNode(self):
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
345 - def __pathToURL(self, path):
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
354 -class JUnitXMLResultsWriter:
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
363 - def __init__(self, logfile):
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
385 - def cleanup(self, **kwargs):
386 """Implementation of the cleanup method. 387 388 @param kwargs: Variable argument list 389 390 """ 391 pass
392 393
394 - def processResult(self, testObj, **kwargs):
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 # add the testcase information 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 # add in failure information if the test has failed 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 # write out the test result 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
458 - def purgeDirectory(self, dir, delTop=False):
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
477 -class CSVResultsWriter:
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
491 - def __init__(self, logfile):
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
519 - def cleanup(self, **kwargs):
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
535 - def processResult(self, testObj, **kwargs):
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