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