15f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)# -*- coding: utf-8 -*-
25f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
35f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)"""unittest-xml-reporting is a PyUnit-based TestRunner that can export test
45f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)results to XML files that can be consumed by a wide range of tools, such as
55f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)build systems, IDEs and Continuous Integration servers.
65f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
75f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)This module provides the XMLTestRunner class, which is heavily based on the
85f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)default TextTestRunner. This makes the XMLTestRunner very simple to use.
95f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)The script below, adapted from the unittest documentation, shows how to use
115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)XMLTestRunner in a very simple way. In fact, the only difference between this
125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)script and the original one is the last line:
135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import random
155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import unittest
165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import xmlrunner
175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class TestSequenceFunctions(unittest.TestCase):
195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def setUp(self):
205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.seq = range(10)
215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def test_shuffle(self):
235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        # make sure the shuffled sequence does not lose any elements
245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        random.shuffle(self.seq)
255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.seq.sort()
265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.assertEqual(self.seq, range(10))
275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def test_choice(self):
295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        element = random.choice(self.seq)
305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.assert_(element in self.seq)
315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def test_sample(self):
335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.assertRaises(ValueError, random.sample, self.seq, 20)
345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        for element in random.sample(self.seq, 5):
355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            self.assert_(element in self.seq)
365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)if __name__ == '__main__':
385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    unittest.main(testRunner=xmlrunner.XMLTestRunner(output='test-reports'))
395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)"""
405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import os
425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import sys
435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import time
445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)from unittest import TestResult, _TextTestResult, TextTestRunner
455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)from cStringIO import StringIO
465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import xml.dom.minidom
475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class XMLDocument(xml.dom.minidom.Document):
505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def createCDATAOrText(self, data):
515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if ']]>' in data:
525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            return self.createTextNode(data)
535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return self.createCDATASection(data)
545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class _TestInfo(object):
575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """This class is used to keep useful information about the execution of a
585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    test method.
595f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """
605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # Possible test outcomes
625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    (SUCCESS, FAILURE, ERROR) = range(3)
635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def __init__(self, test_result, test_method, outcome=SUCCESS, err=None):
655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        "Create a new instance of _TestInfo."
665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.test_result = test_result
675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.test_method = test_method
685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.outcome = outcome
695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.err = err
705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.stdout = test_result.stdout and test_result.stdout.getvalue().strip() or ''
715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.stderr = test_result.stdout and test_result.stderr.getvalue().strip() or ''
725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def get_elapsed_time(self):
745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        """Return the time that shows how long the test method took to
755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        execute.
765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        """
775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return self.test_result.stop_time - self.test_result.start_time
785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
795f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def get_description(self):
805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        "Return a text representation of the test method."
815f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return self.test_result.getDescription(self.test_method)
825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
835f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def get_error_info(self):
845f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        """Return a text representation of an exception thrown by a test
855f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        method.
865f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        """
875f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if not self.err:
885f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            return ''
895f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if sys.version_info < (2,4):
905f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            return self.test_result._exc_info_to_string(self.err)
915f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        else:
925f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            return self.test_result._exc_info_to_string(
935f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                self.err, self.test_method)
945f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
955f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
965f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class _XMLTestResult(_TextTestResult):
975f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """A test result class that can express test results in a XML report.
985f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
995f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    Used by XMLTestRunner.
1005f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """
1015f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1, \
1025f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        elapsed_times=True):
1035f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        "Create a new instance of _XMLTestResult."
1045f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        _TextTestResult.__init__(self, stream, descriptions, verbosity)
1055f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.successes = []
1065f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.callback = None
1075f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.elapsed_times = elapsed_times
1085f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.output_patched = False
1095f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def _prepare_callback(self, test_info, target_list, verbose_str,
1115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        short_str):
1125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        """Append a _TestInfo to the given target list and sets a callback
1135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        method to be called by stopTest method.
1145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        """
1155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        target_list.append(test_info)
1165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        def callback():
1175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            """This callback prints the test method outcome to the stream,
1185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            as well as the elapsed time.
1195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            """
1205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            # Ignore the elapsed times for a more reliable unit testing
1225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            if not self.elapsed_times:
1235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                self.start_time = self.stop_time = 0
1245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            if self.showAll:
1265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                self.stream.writeln('(%.3fs) %s' % \
1275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                    (test_info.get_elapsed_time(), verbose_str))
1285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            elif self.dots:
1295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                self.stream.write(short_str)
1305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.callback = callback
1315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def _patch_standard_output(self):
1335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        """Replace the stdout and stderr streams with string-based streams
1345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        in order to capture the tests' output.
1355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        """
1365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if not self.output_patched:
1375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            (self.old_stdout, self.old_stderr) = (sys.stdout, sys.stderr)
1385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            self.output_patched = True
1395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        (sys.stdout, sys.stderr) = (self.stdout, self.stderr) = \
1405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            (StringIO(), StringIO())
1415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def _restore_standard_output(self):
1435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        "Restore the stdout and stderr streams."
1445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        (sys.stdout, sys.stderr) = (self.old_stdout, self.old_stderr)
1455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.output_patched = False
1465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def startTest(self, test):
1485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        "Called before execute each test method."
1495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self._patch_standard_output()
1505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.start_time = time.time()
1515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        TestResult.startTest(self, test)
1525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if self.showAll:
1545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            self.stream.write('  ' + self.getDescription(test))
1555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            self.stream.write(" ... ")
1565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def stopTest(self, test):
1585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        "Called after execute each test method."
1595f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self._restore_standard_output()
1605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        _TextTestResult.stopTest(self, test)
1615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.stop_time = time.time()
1625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if self.callback and callable(self.callback):
1645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            self.callback()
1655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            self.callback = None
1665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def addSuccess(self, test):
1685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        "Called when a test executes successfully."
1695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self._prepare_callback(_TestInfo(self, test),
1705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                               self.successes, 'OK', '.')
1715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def addFailure(self, test, err):
1735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        "Called when a test method fails."
1745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self._prepare_callback(_TestInfo(self, test, _TestInfo.FAILURE, err),
1755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                               self.failures, 'FAIL', 'F')
1765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def addError(self, test, err):
1785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        "Called when a test method raises an error."
1795f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self._prepare_callback(_TestInfo(self, test, _TestInfo.ERROR, err),
1805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                               self.errors, 'ERROR', 'E')
1815f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def printErrorList(self, flavour, errors):
1835f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        "Write some information about the FAIL or ERROR to the stream."
1845f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        for test_info in errors:
1855f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            if isinstance(test_info, tuple):
1865f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                test_info, exc_info = test_info
1875f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            self.stream.writeln(self.separator1)
1885f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            self.stream.writeln('%s [%.3fs]: %s' % (
1895f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                flavour, test_info.get_elapsed_time(),
1905f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                test_info.get_description()))
1915f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            self.stream.writeln(self.separator2)
1925f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            self.stream.writeln('%s' % test_info.get_error_info())
1935f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1945f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def _get_info_by_testcase(self):
1955f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        """This method organizes test results by TestCase module. This
1965f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        information is used during the report generation, where a XML report
1975f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        will be generated for each TestCase.
1985f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        """
1995f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        tests_by_testcase = {}
2005f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2015f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        for tests in (self.successes, self.failures, self.errors):
2025f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            for test_info in tests:
2035f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                testcase = type(test_info.test_method)
2045f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2055f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                # Ignore module name if it is '__main__'
2065f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                module = testcase.__module__ + '.'
2075f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                if module == '__main__.':
2085f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                    module = ''
2095f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                testcase_name = module + testcase.__name__
2105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                if testcase_name not in tests_by_testcase:
2125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                    tests_by_testcase[testcase_name] = []
2135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                tests_by_testcase[testcase_name].append(test_info)
2145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return tests_by_testcase
2165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def _report_testsuite(suite_name, tests, xml_document):
2185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        "Appends the testsuite section to the XML document."
2195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        testsuite = xml_document.createElement('testsuite')
2205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        xml_document.appendChild(testsuite)
2215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        testsuite.setAttribute('name', str(suite_name))
2235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        testsuite.setAttribute('tests', str(len(tests)))
2245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        testsuite.setAttribute('time', '%.3f' %
2265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            sum([e.get_elapsed_time() for e in tests]))
2275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        failures = len([1 for e in tests if e.outcome == _TestInfo.FAILURE])
2295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        testsuite.setAttribute('failures', str(failures))
2305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        errors = len([1 for e in tests if e.outcome == _TestInfo.ERROR])
2325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        testsuite.setAttribute('errors', str(errors))
2335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return testsuite
2355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    _report_testsuite = staticmethod(_report_testsuite)
2375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def _report_testcase(suite_name, test_result, xml_testsuite, xml_document):
2395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        "Appends a testcase section to the XML document."
2405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        testcase = xml_document.createElement('testcase')
2415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        xml_testsuite.appendChild(testcase)
2425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        testcase.setAttribute('classname', str(suite_name))
2445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        testcase.setAttribute('name', test_result.test_method.shortDescription()
2455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                              or getattr(test_result.test_method, '_testMethodName',
2465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                         str(test_result.test_method)))
2475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        testcase.setAttribute('time', '%.3f' % test_result.get_elapsed_time())
2485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if (test_result.outcome != _TestInfo.SUCCESS):
2505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            elem_name = ('failure', 'error')[test_result.outcome-1]
2515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            failure = xml_document.createElement(elem_name)
2525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            testcase.appendChild(failure)
2535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            failure.setAttribute('type', str(test_result.err[0].__name__))
2555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            failure.setAttribute('message', str(test_result.err[1]))
2565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            error_info = test_result.get_error_info()
2585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            failureText = xml_document.createCDATAOrText(error_info)
2595f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            failure.appendChild(failureText)
2605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    _report_testcase = staticmethod(_report_testcase)
2625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def _report_output(test_runner, xml_testsuite, xml_document, stdout, stderr):
2645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        "Appends the system-out and system-err sections to the XML document."
2655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        systemout = xml_document.createElement('system-out')
2665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        xml_testsuite.appendChild(systemout)
2675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        systemout_text = xml_document.createCDATAOrText(stdout)
2695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        systemout.appendChild(systemout_text)
2705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        systemerr = xml_document.createElement('system-err')
2725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        xml_testsuite.appendChild(systemerr)
2735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        systemerr_text = xml_document.createCDATAOrText(stderr)
2755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        systemerr.appendChild(systemerr_text)
2765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    _report_output = staticmethod(_report_output)
2785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2795f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def generate_reports(self, test_runner):
2805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        "Generates the XML reports to a given XMLTestRunner object."
2815f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        all_results = self._get_info_by_testcase()
2825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2835f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if type(test_runner.output) == str and not \
2845f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            os.path.exists(test_runner.output):
2855f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            os.makedirs(test_runner.output)
2865f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2875f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        for suite, tests in all_results.items():
2885f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            doc = XMLDocument()
2895f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2905f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            # Build the XML file
2915f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            testsuite = _XMLTestResult._report_testsuite(suite, tests, doc)
2925f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            stdout, stderr = [], []
2935f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            for test in tests:
2945f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                _XMLTestResult._report_testcase(suite, test, testsuite, doc)
2955f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                if test.stdout:
2965f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                    stdout.extend(['*****************', test.get_description(), test.stdout])
2975f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                if test.stderr:
2985f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                    stderr.extend(['*****************', test.get_description(), test.stderr])
2995f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            _XMLTestResult._report_output(test_runner, testsuite, doc,
3005f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                          '\n'.join(stdout), '\n'.join(stderr))
3015f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            xml_content = doc.toprettyxml(indent='\t')
3025f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3035f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            if type(test_runner.output) is str:
3045f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                report_file = open('%s%sTEST-%s.xml' % \
3055f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                    (test_runner.output, os.sep, suite), 'w')
3065f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                try:
3075f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                    report_file.write(xml_content)
3085f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                finally:
3095f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                    report_file.close()
3105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            else:
3115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                # Assume that test_runner.output is a stream
3125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                test_runner.output.write(xml_content)
3135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class XMLTestRunner(TextTestRunner):
3165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """A test runner class that outputs the results in JUnit like XML files.
3175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    """
3185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def __init__(self, output='.', stream=sys.stderr, descriptions=True, \
3195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        verbose=False, elapsed_times=True):
3205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        "Create a new instance of XMLTestRunner."
3215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        verbosity = (1, 2)[verbose]
3225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        TextTestRunner.__init__(self, stream, descriptions, verbosity)
3235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.output = output
3245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.elapsed_times = elapsed_times
3255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def _make_result(self):
3275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        """Create the TestResult object which will be used to store
3285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        information about the executed tests.
3295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        """
3305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return _XMLTestResult(self.stream, self.descriptions, \
3315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            self.verbosity, self.elapsed_times)
3325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def run(self, test):
3345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        "Run the given test case or test suite."
3355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        # Prepare the test execution
3365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        result = self._make_result()
3375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        # Print a nice header
3395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.stream.writeln()
3405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.stream.writeln('Running tests...')
3415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.stream.writeln(result.separator2)
3425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        # Execute tests
3445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        start_time = time.time()
3455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        test(result)
3465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        stop_time = time.time()
3475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        time_taken = stop_time - start_time
3485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        # Print results
3505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        result.printErrors()
3515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.stream.writeln(result.separator2)
3525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        run = result.testsRun
3535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.stream.writeln("Ran %d test%s in %.3fs" %
3545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            (run, run != 1 and "s" or "", time_taken))
3555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.stream.writeln()
3565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        # Error traces
3585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if not result.wasSuccessful():
3595f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            self.stream.write("FAILED (")
3605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            failed, errored = (len(result.failures), len(result.errors))
3615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            if failed:
3625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                self.stream.write("failures=%d" % failed)
3635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            if errored:
3645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                if failed:
3655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                    self.stream.write(", ")
3665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                self.stream.write("errors=%d" % errored)
3675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            self.stream.writeln(")")
3685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        else:
3695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            self.stream.writeln("OK")
3705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        # Generate reports
3725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.stream.writeln()
3735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self.stream.writeln('Generating XML reports...')
3745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        result.generate_reports(self)
3755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return result
377