xref: /libCEED/tests/junit-xml/junit_xml/__init__.py (revision 3c60848e00fec9e75c14b7121743301858231cdc)
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