1dfafb49cSJed Brown#!/usr/bin/env python 2*93b6d819Srezgarshakeri# -*- coding: UTF-8 -*- 3*93b6d819Srezgarshakeriimport warnings 4dfafb49cSJed Brownfrom collections import defaultdict 5dfafb49cSJed Brownimport sys 6dfafb49cSJed Brownimport re 7dfafb49cSJed Brownimport xml.etree.ElementTree as ET 8dfafb49cSJed Brownimport xml.dom.minidom 9dfafb49cSJed Brown 10*93b6d819SrezgarshakeriIS_PY2 = sys.version_info[0] == 2 11*93b6d819Srezgarshakeri 12dfafb49cSJed Browntry: 13dfafb49cSJed Brown # Python 2 14dfafb49cSJed Brown unichr 15dfafb49cSJed Brownexcept NameError: # pragma: nocover 16dfafb49cSJed Brown # Python 3 17dfafb49cSJed Brown unichr = chr 18dfafb49cSJed Brown 19dfafb49cSJed Brown""" 20dfafb49cSJed BrownBased on the understanding of what Jenkins can parse for JUnit XML files. 21dfafb49cSJed Brown 22dfafb49cSJed Brown<?xml version="1.0" encoding="utf-8"?> 23dfafb49cSJed Brown<testsuites errors="1" failures="1" tests="4" time="45"> 24dfafb49cSJed Brown <testsuite errors="1" failures="1" hostname="localhost" id="0" name="test1" 25dfafb49cSJed Brown package="testdb" tests="4" timestamp="2012-11-15T01:02:29"> 26dfafb49cSJed Brown <properties> 27dfafb49cSJed Brown <property name="assert-passed" value="1"/> 28dfafb49cSJed Brown </properties> 29dfafb49cSJed Brown <testcase classname="testdb.directory" name="1-passed-test" time="10"/> 30dfafb49cSJed Brown <testcase classname="testdb.directory" name="2-failed-test" time="20"> 31dfafb49cSJed Brown <failure message="Assertion FAILED: failed assert" type="failure"> 32dfafb49cSJed Brown the output of the testcase 33dfafb49cSJed Brown </failure> 34dfafb49cSJed Brown </testcase> 35dfafb49cSJed Brown <testcase classname="package.directory" name="3-errord-test" time="15"> 36dfafb49cSJed Brown <error message="Assertion ERROR: error assert" type="error"> 37dfafb49cSJed Brown the output of the testcase 38dfafb49cSJed Brown </error> 39dfafb49cSJed Brown </testcase> 40dfafb49cSJed Brown <testcase classname="package.directory" name="3-skipped-test" time="0"> 41dfafb49cSJed Brown <skipped message="SKIPPED Test" type="skipped"> 42dfafb49cSJed Brown the output of the testcase 43dfafb49cSJed Brown </skipped> 44dfafb49cSJed Brown </testcase> 45dfafb49cSJed Brown <testcase classname="testdb.directory" name="3-passed-test" time="10"> 46dfafb49cSJed Brown <system-out> 47dfafb49cSJed Brown I am system output 48dfafb49cSJed Brown </system-out> 49dfafb49cSJed Brown <system-err> 50dfafb49cSJed Brown I am the error output 51dfafb49cSJed Brown </system-err> 52dfafb49cSJed Brown </testcase> 53dfafb49cSJed Brown </testsuite> 54dfafb49cSJed Brown</testsuites> 55dfafb49cSJed Brown""" 56dfafb49cSJed Brown 57dfafb49cSJed Brown 58dfafb49cSJed Browndef decode(var, encoding): 59dfafb49cSJed Brown """ 60dfafb49cSJed Brown If not already unicode, decode it. 61dfafb49cSJed Brown """ 62*93b6d819Srezgarshakeri if IS_PY2: 63*93b6d819Srezgarshakeri if isinstance(var, unicode): # noqa: F821 64dfafb49cSJed Brown ret = var 65dfafb49cSJed Brown elif isinstance(var, str): 66dfafb49cSJed Brown if encoding: 67dfafb49cSJed Brown ret = var.decode(encoding) 68dfafb49cSJed Brown else: 69*93b6d819Srezgarshakeri ret = unicode(var) # noqa: F821 70dfafb49cSJed Brown else: 71*93b6d819Srezgarshakeri ret = unicode(var) # noqa: F821 72dfafb49cSJed Brown else: 73dfafb49cSJed Brown ret = str(var) 74dfafb49cSJed Brown return ret 75dfafb49cSJed Brown 76dfafb49cSJed Brown 77dfafb49cSJed Brownclass TestSuite(object): 78dfafb49cSJed Brown """ 79dfafb49cSJed Brown Suite of test cases. 80dfafb49cSJed Brown Can handle unicode strings or binary strings if their encoding is provided. 81dfafb49cSJed Brown """ 82dfafb49cSJed Brown 83*93b6d819Srezgarshakeri def __init__( 84*93b6d819Srezgarshakeri self, 85*93b6d819Srezgarshakeri name, 86*93b6d819Srezgarshakeri test_cases=None, 87*93b6d819Srezgarshakeri hostname=None, 88*93b6d819Srezgarshakeri id=None, 89*93b6d819Srezgarshakeri package=None, 90*93b6d819Srezgarshakeri timestamp=None, 91*93b6d819Srezgarshakeri properties=None, 92*93b6d819Srezgarshakeri file=None, 93*93b6d819Srezgarshakeri log=None, 94*93b6d819Srezgarshakeri url=None, 95*93b6d819Srezgarshakeri stdout=None, 96*93b6d819Srezgarshakeri stderr=None, 97*93b6d819Srezgarshakeri ): 98dfafb49cSJed Brown self.name = name 99dfafb49cSJed Brown if not test_cases: 100dfafb49cSJed Brown test_cases = [] 101dfafb49cSJed Brown try: 102dfafb49cSJed Brown iter(test_cases) 103dfafb49cSJed Brown except TypeError: 104*93b6d819Srezgarshakeri raise TypeError("test_cases must be a list of test cases") 105dfafb49cSJed Brown self.test_cases = test_cases 106dfafb49cSJed Brown self.timestamp = timestamp 107dfafb49cSJed Brown self.hostname = hostname 108dfafb49cSJed Brown self.id = id 109dfafb49cSJed Brown self.package = package 110dfafb49cSJed Brown self.file = file 111dfafb49cSJed Brown self.log = log 112dfafb49cSJed Brown self.url = url 113dfafb49cSJed Brown self.stdout = stdout 114dfafb49cSJed Brown self.stderr = stderr 115dfafb49cSJed Brown self.properties = properties 116dfafb49cSJed Brown 117dfafb49cSJed Brown def build_xml_doc(self, encoding=None): 118dfafb49cSJed Brown """ 119dfafb49cSJed Brown Builds the XML document for the JUnit test suite. 120dfafb49cSJed Brown Produces clean unicode strings and decodes non-unicode with the help of encoding. 121dfafb49cSJed Brown @param encoding: Used to decode encoded strings. 122dfafb49cSJed Brown @return: XML document with unicode string elements 123dfafb49cSJed Brown """ 124dfafb49cSJed Brown 125dfafb49cSJed Brown # build the test suite element 126dfafb49cSJed Brown test_suite_attributes = dict() 127dfafb49cSJed Brown if any(c.assertions for c in self.test_cases): 128*93b6d819Srezgarshakeri test_suite_attributes["assertions"] = str(sum([int(c.assertions) for c in self.test_cases if c.assertions])) 129*93b6d819Srezgarshakeri test_suite_attributes["disabled"] = str(len([c for c in self.test_cases if not c.is_enabled])) 130*93b6d819Srezgarshakeri test_suite_attributes["errors"] = str(len([c for c in self.test_cases if c.is_error()])) 131*93b6d819Srezgarshakeri test_suite_attributes["failures"] = str(len([c for c in self.test_cases if c.is_failure()])) 132*93b6d819Srezgarshakeri test_suite_attributes["name"] = decode(self.name, encoding) 133*93b6d819Srezgarshakeri test_suite_attributes["skipped"] = str(len([c for c in self.test_cases if c.is_skipped()])) 134*93b6d819Srezgarshakeri test_suite_attributes["tests"] = str(len(self.test_cases)) 135*93b6d819Srezgarshakeri test_suite_attributes["time"] = str(sum(c.elapsed_sec for c in self.test_cases if c.elapsed_sec)) 136dfafb49cSJed Brown 137dfafb49cSJed Brown if self.hostname: 138*93b6d819Srezgarshakeri test_suite_attributes["hostname"] = decode(self.hostname, encoding) 139dfafb49cSJed Brown if self.id: 140*93b6d819Srezgarshakeri test_suite_attributes["id"] = decode(self.id, encoding) 141dfafb49cSJed Brown if self.package: 142*93b6d819Srezgarshakeri test_suite_attributes["package"] = decode(self.package, encoding) 143dfafb49cSJed Brown if self.timestamp: 144*93b6d819Srezgarshakeri test_suite_attributes["timestamp"] = decode(self.timestamp, encoding) 145dfafb49cSJed Brown if self.file: 146*93b6d819Srezgarshakeri test_suite_attributes["file"] = decode(self.file, encoding) 147dfafb49cSJed Brown if self.log: 148*93b6d819Srezgarshakeri test_suite_attributes["log"] = decode(self.log, encoding) 149dfafb49cSJed Brown if self.url: 150*93b6d819Srezgarshakeri test_suite_attributes["url"] = decode(self.url, encoding) 151dfafb49cSJed Brown 152dfafb49cSJed Brown xml_element = ET.Element("testsuite", test_suite_attributes) 153dfafb49cSJed Brown 154dfafb49cSJed Brown # add any properties 155dfafb49cSJed Brown if self.properties: 156dfafb49cSJed Brown props_element = ET.SubElement(xml_element, "properties") 157dfafb49cSJed Brown for k, v in self.properties.items(): 158*93b6d819Srezgarshakeri attrs = {"name": decode(k, encoding), "value": decode(v, encoding)} 159dfafb49cSJed Brown ET.SubElement(props_element, "property", attrs) 160dfafb49cSJed Brown 161dfafb49cSJed Brown # add test suite stdout 162dfafb49cSJed Brown if self.stdout: 163dfafb49cSJed Brown stdout_element = ET.SubElement(xml_element, "system-out") 164dfafb49cSJed Brown stdout_element.text = decode(self.stdout, encoding) 165dfafb49cSJed Brown 166dfafb49cSJed Brown # add test suite stderr 167dfafb49cSJed Brown if self.stderr: 168dfafb49cSJed Brown stderr_element = ET.SubElement(xml_element, "system-err") 169dfafb49cSJed Brown stderr_element.text = decode(self.stderr, encoding) 170dfafb49cSJed Brown 171dfafb49cSJed Brown # test cases 172dfafb49cSJed Brown for case in self.test_cases: 173dfafb49cSJed Brown test_case_attributes = dict() 174*93b6d819Srezgarshakeri test_case_attributes["name"] = decode(case.name, encoding) 175dfafb49cSJed Brown if case.assertions: 176dfafb49cSJed Brown # Number of assertions in the test case 177*93b6d819Srezgarshakeri test_case_attributes["assertions"] = "%d" % case.assertions 178dfafb49cSJed Brown if case.elapsed_sec: 179*93b6d819Srezgarshakeri test_case_attributes["time"] = "%f" % case.elapsed_sec 180dfafb49cSJed Brown if case.timestamp: 181*93b6d819Srezgarshakeri test_case_attributes["timestamp"] = decode(case.timestamp, encoding) 182dfafb49cSJed Brown if case.classname: 183*93b6d819Srezgarshakeri test_case_attributes["classname"] = decode(case.classname, encoding) 184dfafb49cSJed Brown if case.status: 185*93b6d819Srezgarshakeri test_case_attributes["status"] = decode(case.status, encoding) 186dfafb49cSJed Brown if case.category: 187*93b6d819Srezgarshakeri test_case_attributes["class"] = decode(case.category, encoding) 188dfafb49cSJed Brown if case.file: 189*93b6d819Srezgarshakeri test_case_attributes["file"] = decode(case.file, encoding) 190dfafb49cSJed Brown if case.line: 191*93b6d819Srezgarshakeri test_case_attributes["line"] = decode(case.line, encoding) 192dfafb49cSJed Brown if case.log: 193*93b6d819Srezgarshakeri test_case_attributes["log"] = decode(case.log, encoding) 194dfafb49cSJed Brown if case.url: 195*93b6d819Srezgarshakeri test_case_attributes["url"] = decode(case.url, encoding) 196dfafb49cSJed Brown 197*93b6d819Srezgarshakeri test_case_element = ET.SubElement(xml_element, "testcase", test_case_attributes) 198dfafb49cSJed Brown 199dfafb49cSJed Brown # failures 200*93b6d819Srezgarshakeri for failure in case.failures: 201*93b6d819Srezgarshakeri if failure["output"] or failure["message"]: 202*93b6d819Srezgarshakeri attrs = {"type": "failure"} 203*93b6d819Srezgarshakeri if failure["message"]: 204*93b6d819Srezgarshakeri attrs["message"] = decode(failure["message"], encoding) 205*93b6d819Srezgarshakeri if failure["type"]: 206*93b6d819Srezgarshakeri attrs["type"] = decode(failure["type"], encoding) 207dfafb49cSJed Brown failure_element = ET.Element("failure", attrs) 208*93b6d819Srezgarshakeri if failure["output"]: 209*93b6d819Srezgarshakeri failure_element.text = decode(failure["output"], encoding) 210dfafb49cSJed Brown test_case_element.append(failure_element) 211dfafb49cSJed Brown 212dfafb49cSJed Brown # errors 213*93b6d819Srezgarshakeri for error in case.errors: 214*93b6d819Srezgarshakeri if error["message"] or error["output"]: 215*93b6d819Srezgarshakeri attrs = {"type": "error"} 216*93b6d819Srezgarshakeri if error["message"]: 217*93b6d819Srezgarshakeri attrs["message"] = decode(error["message"], encoding) 218*93b6d819Srezgarshakeri if error["type"]: 219*93b6d819Srezgarshakeri attrs["type"] = decode(error["type"], encoding) 220dfafb49cSJed Brown error_element = ET.Element("error", attrs) 221*93b6d819Srezgarshakeri if error["output"]: 222*93b6d819Srezgarshakeri error_element.text = decode(error["output"], encoding) 223dfafb49cSJed Brown test_case_element.append(error_element) 224dfafb49cSJed Brown 225dfafb49cSJed Brown # skippeds 226*93b6d819Srezgarshakeri for skipped in case.skipped: 227*93b6d819Srezgarshakeri attrs = {"type": "skipped"} 228*93b6d819Srezgarshakeri if skipped["message"]: 229*93b6d819Srezgarshakeri attrs["message"] = decode(skipped["message"], encoding) 230dfafb49cSJed Brown skipped_element = ET.Element("skipped", attrs) 231*93b6d819Srezgarshakeri if skipped["output"]: 232*93b6d819Srezgarshakeri skipped_element.text = decode(skipped["output"], encoding) 233dfafb49cSJed Brown test_case_element.append(skipped_element) 234dfafb49cSJed Brown 235dfafb49cSJed Brown # test stdout 236dfafb49cSJed Brown if case.stdout: 237dfafb49cSJed Brown stdout_element = ET.Element("system-out") 238dfafb49cSJed Brown stdout_element.text = decode(case.stdout, encoding) 239dfafb49cSJed Brown test_case_element.append(stdout_element) 240dfafb49cSJed Brown 241dfafb49cSJed Brown # test stderr 242dfafb49cSJed Brown if case.stderr: 243dfafb49cSJed Brown stderr_element = ET.Element("system-err") 244dfafb49cSJed Brown stderr_element.text = decode(case.stderr, encoding) 245dfafb49cSJed Brown test_case_element.append(stderr_element) 246dfafb49cSJed Brown 247dfafb49cSJed Brown return xml_element 248dfafb49cSJed Brown 249dfafb49cSJed Brown @staticmethod 250dfafb49cSJed Brown def to_xml_string(test_suites, prettyprint=True, encoding=None): 251dfafb49cSJed Brown """ 252dfafb49cSJed Brown Returns the string representation of the JUnit XML document. 253dfafb49cSJed Brown @param encoding: The encoding of the input. 254dfafb49cSJed Brown @return: unicode string 255dfafb49cSJed Brown """ 256*93b6d819Srezgarshakeri warnings.warn( 257*93b6d819Srezgarshakeri "Testsuite.to_xml_string is deprecated. It will be removed in version 2.0.0. " 258*93b6d819Srezgarshakeri "Use function to_xml_report_string", 259*93b6d819Srezgarshakeri DeprecationWarning, 260*93b6d819Srezgarshakeri ) 261*93b6d819Srezgarshakeri return to_xml_report_string(test_suites, prettyprint, encoding) 262*93b6d819Srezgarshakeri 263*93b6d819Srezgarshakeri @staticmethod 264*93b6d819Srezgarshakeri def to_file(file_descriptor, test_suites, prettyprint=True, encoding=None): 265*93b6d819Srezgarshakeri """ 266*93b6d819Srezgarshakeri Writes the JUnit XML document to a file. 267*93b6d819Srezgarshakeri """ 268*93b6d819Srezgarshakeri warnings.warn( 269*93b6d819Srezgarshakeri "Testsuite.to_file is deprecated. It will be removed in version 2.0.0. Use function to_xml_report_file", 270*93b6d819Srezgarshakeri DeprecationWarning, 271*93b6d819Srezgarshakeri ) 272*93b6d819Srezgarshakeri to_xml_report_file(file_descriptor, test_suites, prettyprint, encoding) 273*93b6d819Srezgarshakeri 274*93b6d819Srezgarshakeri 275*93b6d819Srezgarshakeridef to_xml_report_string(test_suites, prettyprint=True, encoding=None): 276*93b6d819Srezgarshakeri """ 277*93b6d819Srezgarshakeri Returns the string representation of the JUnit XML document. 278*93b6d819Srezgarshakeri @param encoding: The encoding of the input. 279*93b6d819Srezgarshakeri @return: unicode string 280*93b6d819Srezgarshakeri """ 281dfafb49cSJed Brown 282dfafb49cSJed Brown try: 283dfafb49cSJed Brown iter(test_suites) 284dfafb49cSJed Brown except TypeError: 285*93b6d819Srezgarshakeri raise TypeError("test_suites must be a list of test suites") 286dfafb49cSJed Brown 287dfafb49cSJed Brown xml_element = ET.Element("testsuites") 288dfafb49cSJed Brown attributes = defaultdict(int) 289dfafb49cSJed Brown for ts in test_suites: 290dfafb49cSJed Brown ts_xml = ts.build_xml_doc(encoding=encoding) 291*93b6d819Srezgarshakeri for key in ["disabled", "errors", "failures", "tests"]: 292dfafb49cSJed Brown attributes[key] += int(ts_xml.get(key, 0)) 293*93b6d819Srezgarshakeri for key in ["time"]: 294dfafb49cSJed Brown attributes[key] += float(ts_xml.get(key, 0)) 295dfafb49cSJed Brown xml_element.append(ts_xml) 29682c7dee4SJed Brown for key, value in attributes.items(): 297dfafb49cSJed Brown xml_element.set(key, str(value)) 298dfafb49cSJed Brown 299dfafb49cSJed Brown xml_string = ET.tostring(xml_element, encoding=encoding) 300dfafb49cSJed Brown # is encoded now 301*93b6d819Srezgarshakeri xml_string = _clean_illegal_xml_chars(xml_string.decode(encoding or "utf-8")) 302dfafb49cSJed Brown # is unicode now 303dfafb49cSJed Brown 304dfafb49cSJed Brown if prettyprint: 305dfafb49cSJed Brown # minidom.parseString() works just on correctly encoded binary strings 306*93b6d819Srezgarshakeri xml_string = xml_string.encode(encoding or "utf-8") 307dfafb49cSJed Brown xml_string = xml.dom.minidom.parseString(xml_string) 308dfafb49cSJed Brown # toprettyxml() produces unicode if no encoding is being passed or binary string with an encoding 309dfafb49cSJed Brown xml_string = xml_string.toprettyxml(encoding=encoding) 310dfafb49cSJed Brown if encoding: 311dfafb49cSJed Brown xml_string = xml_string.decode(encoding) 312dfafb49cSJed Brown # is unicode now 313dfafb49cSJed Brown return xml_string 314dfafb49cSJed Brown 315*93b6d819Srezgarshakeri 316*93b6d819Srezgarshakeridef to_xml_report_file(file_descriptor, test_suites, prettyprint=True, encoding=None): 317dfafb49cSJed Brown """ 318dfafb49cSJed Brown Writes the JUnit XML document to a file. 319dfafb49cSJed Brown """ 320*93b6d819Srezgarshakeri xml_string = to_xml_report_string(test_suites, prettyprint=prettyprint, encoding=encoding) 321dfafb49cSJed Brown # has problems with encoded str with non-ASCII (non-default-encoding) characters! 322dfafb49cSJed Brown file_descriptor.write(xml_string) 323dfafb49cSJed Brown 324*93b6d819Srezgarshakeri 325dfafb49cSJed Browndef _clean_illegal_xml_chars(string_to_clean): 326dfafb49cSJed Brown """ 327dfafb49cSJed Brown Removes any illegal unicode characters from the given XML string. 328dfafb49cSJed Brown 329dfafb49cSJed Brown @see: http://stackoverflow.com/questions/1707890/fast-way-to-filter-illegal-xml-unicode-chars-in-python 330dfafb49cSJed Brown """ 331dfafb49cSJed Brown 332dfafb49cSJed Brown illegal_unichrs = [ 333*93b6d819Srezgarshakeri (0x00, 0x08), 334*93b6d819Srezgarshakeri (0x0B, 0x1F), 335*93b6d819Srezgarshakeri (0x7F, 0x84), 336*93b6d819Srezgarshakeri (0x86, 0x9F), 337*93b6d819Srezgarshakeri (0xD800, 0xDFFF), 338*93b6d819Srezgarshakeri (0xFDD0, 0xFDDF), 339*93b6d819Srezgarshakeri (0xFFFE, 0xFFFF), 340*93b6d819Srezgarshakeri (0x1FFFE, 0x1FFFF), 341*93b6d819Srezgarshakeri (0x2FFFE, 0x2FFFF), 342*93b6d819Srezgarshakeri (0x3FFFE, 0x3FFFF), 343*93b6d819Srezgarshakeri (0x4FFFE, 0x4FFFF), 344*93b6d819Srezgarshakeri (0x5FFFE, 0x5FFFF), 345*93b6d819Srezgarshakeri (0x6FFFE, 0x6FFFF), 346*93b6d819Srezgarshakeri (0x7FFFE, 0x7FFFF), 347*93b6d819Srezgarshakeri (0x8FFFE, 0x8FFFF), 348*93b6d819Srezgarshakeri (0x9FFFE, 0x9FFFF), 349*93b6d819Srezgarshakeri (0xAFFFE, 0xAFFFF), 350*93b6d819Srezgarshakeri (0xBFFFE, 0xBFFFF), 351*93b6d819Srezgarshakeri (0xCFFFE, 0xCFFFF), 352*93b6d819Srezgarshakeri (0xDFFFE, 0xDFFFF), 353*93b6d819Srezgarshakeri (0xEFFFE, 0xEFFFF), 354*93b6d819Srezgarshakeri (0xFFFFE, 0xFFFFF), 355*93b6d819Srezgarshakeri (0x10FFFE, 0x10FFFF), 356*93b6d819Srezgarshakeri ] 357dfafb49cSJed Brown 358*93b6d819Srezgarshakeri illegal_ranges = ["%s-%s" % (unichr(low), unichr(high)) for (low, high) in illegal_unichrs if low < sys.maxunicode] 359dfafb49cSJed Brown 360*93b6d819Srezgarshakeri illegal_xml_re = re.compile('[%s]' % ''.join(illegal_ranges)) 361*93b6d819Srezgarshakeri return illegal_xml_re.sub("", string_to_clean) 362dfafb49cSJed Brown 363dfafb49cSJed Brown 364dfafb49cSJed Brownclass TestCase(object): 365dfafb49cSJed Brown """A JUnit test case with a result and possibly some stdout or stderr""" 366dfafb49cSJed Brown 367*93b6d819Srezgarshakeri def __init__( 368*93b6d819Srezgarshakeri self, 369*93b6d819Srezgarshakeri name, 370*93b6d819Srezgarshakeri classname=None, 371*93b6d819Srezgarshakeri elapsed_sec=None, 372*93b6d819Srezgarshakeri stdout=None, 373*93b6d819Srezgarshakeri stderr=None, 374*93b6d819Srezgarshakeri assertions=None, 375*93b6d819Srezgarshakeri timestamp=None, 376*93b6d819Srezgarshakeri status=None, 377*93b6d819Srezgarshakeri category=None, 378*93b6d819Srezgarshakeri file=None, 379*93b6d819Srezgarshakeri line=None, 380*93b6d819Srezgarshakeri log=None, 381*93b6d819Srezgarshakeri url=None, 382*93b6d819Srezgarshakeri allow_multiple_subelements=False, 383*93b6d819Srezgarshakeri ): 384dfafb49cSJed Brown self.name = name 385dfafb49cSJed Brown self.assertions = assertions 386dfafb49cSJed Brown self.elapsed_sec = elapsed_sec 387dfafb49cSJed Brown self.timestamp = timestamp 388dfafb49cSJed Brown self.classname = classname 389dfafb49cSJed Brown self.status = status 390dfafb49cSJed Brown self.category = category 391dfafb49cSJed Brown self.file = file 392dfafb49cSJed Brown self.line = line 393dfafb49cSJed Brown self.log = log 394dfafb49cSJed Brown self.url = url 395dfafb49cSJed Brown self.stdout = stdout 396dfafb49cSJed Brown self.stderr = stderr 397dfafb49cSJed Brown 398dfafb49cSJed Brown self.is_enabled = True 399*93b6d819Srezgarshakeri self.errors = [] 400*93b6d819Srezgarshakeri self.failures = [] 401*93b6d819Srezgarshakeri self.skipped = [] 402*93b6d819Srezgarshakeri self.allow_multiple_subalements = allow_multiple_subelements 403dfafb49cSJed Brown 404dfafb49cSJed Brown def add_error_info(self, message=None, output=None, error_type=None): 405dfafb49cSJed Brown """Adds an error message, output, or both to the test case""" 406*93b6d819Srezgarshakeri error = {} 407*93b6d819Srezgarshakeri error["message"] = message 408*93b6d819Srezgarshakeri error["output"] = output 409*93b6d819Srezgarshakeri error["type"] = error_type 410*93b6d819Srezgarshakeri if self.allow_multiple_subalements: 411*93b6d819Srezgarshakeri if message or output: 412*93b6d819Srezgarshakeri self.errors.append(error) 413*93b6d819Srezgarshakeri elif not len(self.errors): 414*93b6d819Srezgarshakeri self.errors.append(error) 415*93b6d819Srezgarshakeri else: 416dfafb49cSJed Brown if message: 417*93b6d819Srezgarshakeri self.errors[0]["message"] = message 418dfafb49cSJed Brown if output: 419*93b6d819Srezgarshakeri self.errors[0]["output"] = output 420dfafb49cSJed Brown if error_type: 421*93b6d819Srezgarshakeri self.errors[0]["type"] = error_type 422dfafb49cSJed Brown 423dfafb49cSJed Brown def add_failure_info(self, message=None, output=None, failure_type=None): 424dfafb49cSJed Brown """Adds a failure message, output, or both to the test case""" 425*93b6d819Srezgarshakeri failure = {} 426*93b6d819Srezgarshakeri failure["message"] = message 427*93b6d819Srezgarshakeri failure["output"] = output 428*93b6d819Srezgarshakeri failure["type"] = failure_type 429*93b6d819Srezgarshakeri if self.allow_multiple_subalements: 430*93b6d819Srezgarshakeri if message or output: 431*93b6d819Srezgarshakeri self.failures.append(failure) 432*93b6d819Srezgarshakeri elif not len(self.failures): 433*93b6d819Srezgarshakeri self.failures.append(failure) 434*93b6d819Srezgarshakeri else: 435dfafb49cSJed Brown if message: 436*93b6d819Srezgarshakeri self.failures[0]["message"] = message 437dfafb49cSJed Brown if output: 438*93b6d819Srezgarshakeri self.failures[0]["output"] = output 439dfafb49cSJed Brown if failure_type: 440*93b6d819Srezgarshakeri self.failures[0]["type"] = failure_type 441dfafb49cSJed Brown 442dfafb49cSJed Brown def add_skipped_info(self, message=None, output=None): 443dfafb49cSJed Brown """Adds a skipped message, output, or both to the test case""" 444*93b6d819Srezgarshakeri skipped = {} 445*93b6d819Srezgarshakeri skipped["message"] = message 446*93b6d819Srezgarshakeri skipped["output"] = output 447*93b6d819Srezgarshakeri if self.allow_multiple_subalements: 448*93b6d819Srezgarshakeri if message or output: 449*93b6d819Srezgarshakeri self.skipped.append(skipped) 450*93b6d819Srezgarshakeri elif not len(self.skipped): 451*93b6d819Srezgarshakeri self.skipped.append(skipped) 452*93b6d819Srezgarshakeri else: 453dfafb49cSJed Brown if message: 454*93b6d819Srezgarshakeri self.skipped[0]["message"] = message 455dfafb49cSJed Brown if output: 456*93b6d819Srezgarshakeri self.skipped[0]["output"] = output 457dfafb49cSJed Brown 458dfafb49cSJed Brown def is_failure(self): 459dfafb49cSJed Brown """returns true if this test case is a failure""" 460*93b6d819Srezgarshakeri return sum(1 for f in self.failures if f["message"] or f["output"]) > 0 461dfafb49cSJed Brown 462dfafb49cSJed Brown def is_error(self): 463dfafb49cSJed Brown """returns true if this test case is an error""" 464*93b6d819Srezgarshakeri return sum(1 for e in self.errors if e["message"] or e["output"]) > 0 465dfafb49cSJed Brown 466dfafb49cSJed Brown def is_skipped(self): 467dfafb49cSJed Brown """returns true if this test case has been skipped""" 468*93b6d819Srezgarshakeri return len(self.skipped) > 0 469