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-2013  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 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   
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("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 # update the count of completed tests 326 self.numResults = self.numResults + 1 327 self.completedAttribute.value="%s/%s" % (self.numResults, self.numTests) 328 329 # write the file out 330 self.fp.write(self.document.toprettyxml(indent=" "))
331 332
333 - def __createResultsNode(self):
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
341 - def __pathToURL(self, path):
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
350 -class JUnitXMLResultsWriter:
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
359 - def __init__(self, logfile):
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
382 - def cleanup(self, **kwargs):
383 """Implementation of the cleanup method. 384 385 @param kwargs: Variable argument list 386 387 """ 388 pass
389 390
391 - def processResult(self, testObj, **kwargs):
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 # add the testcase information 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 # add in failure information if the test has failed 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 # write out the test result 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
454 - def purgeDirectory(self, dir, delTop=False):
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