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