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