xref: /honee/tests/junit-xml/junit_xml/__init__.py (revision 0006be33bc3a095c7a40443db044019362bd3e1c)
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