From cce91c2ab77925e8759767f94d36b23defd3876c Mon Sep 17 00:00:00 2001 From: Frank Niessink Date: Fri, 15 Sep 2017 16:08:48 +0200 Subject: [PATCH 1/2] Add a ISO-8601 timestamp to every testsuite and testcase in the XML-report. --- tests/builder_test.py | 6 ++++++ xmlrunner/builder.py | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/tests/builder_test.py b/tests/builder_test.py index cd2cf87..52ef4e9 100644 --- a/tests/builder_test.py +++ b/tests/builder_test.py @@ -83,6 +83,12 @@ def test_add_time_attribute_on_end_context(self): element.attributes['time'].value + def test_add_timestamp_attribute_on_end_context(self): + self.root.begin('testsuite', 'name') + element = self.root.end() + + element.attributes['timestamp'].value + class TestXMLBuilderTest(unittest.TestCase): """TestXMLBuilder test cases. diff --git a/xmlrunner/builder.py b/xmlrunner/builder.py index d4c19e6..dc35bc3 100644 --- a/xmlrunner/builder.py +++ b/xmlrunner/builder.py @@ -1,5 +1,6 @@ import re import sys +import datetime import time import six @@ -80,6 +81,7 @@ def end(self): """ self._stop_time = time.time() self.element.setAttribute('time', self.elapsed_time()) + self.element.setAttribute('timestamp', self.timestamp()) self._set_result_counters() return self.element @@ -121,6 +123,11 @@ def elapsed_time(self): """ return format(self._stop_time - self._start_time, '.3f') + def timestamp(self): + """Returns the time the context ended as ISO-8601-formatted timestamp. + """ + return datetime.datetime.fromtimestamp(self._stop_time).replace(microsecond=0).isoformat() + class TestXMLBuilder(object): """This class encapsulates most rules needed to create a XML test report From 05a94fc8447d4945576739cd06d0d881ee08b70c Mon Sep 17 00:00:00 2001 From: Frank Niessink Date: Mon, 25 Sep 2017 15:02:49 +0200 Subject: [PATCH 2/2] Add a timestamp attribute to every testsuite and testcase element in the XML file. --- xmlrunner/result.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/xmlrunner/result.py b/xmlrunner/result.py index 427a42b..a679db8 100644 --- a/xmlrunner/result.py +++ b/xmlrunner/result.py @@ -1,6 +1,7 @@ import os import sys +import datetime import time import traceback import six @@ -16,28 +17,28 @@ # http://stackoverflow.com/questions/1707890/fast-way-to-filter-illegal-xml-unicode-chars-in-python _illegal_unichrs = [ - (0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F), - (0x7F, 0x84), (0x86, 0x9F), + (0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F), + (0x7F, 0x84), (0x86, 0x9F), (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF), -] -if sys.maxunicode >= 0x10000: # not narrow build +] +if sys.maxunicode >= 0x10000: # not narrow build _illegal_unichrs.extend([ - (0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), - (0x3FFFE, 0x3FFFF), (0x4FFFE, 0x4FFFF), - (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF), - (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), - (0x9FFFE, 0x9FFFF), (0xAFFFE, 0xAFFFF), - (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF), - (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), + (0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), + (0x3FFFE, 0x3FFFF), (0x4FFFE, 0x4FFFF), + (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF), + (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), + (0x9FFFE, 0x9FFFF), (0xAFFFE, 0xAFFFF), + (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF), + (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF), (0x10FFFE, 0x10FFFF), - ]) + ]) _illegal_ranges = [ "%s-%s" % (six.unichr(low), six.unichr(high)) for (low, high) in _illegal_unichrs ] -INVALID_XML_1_0_UNICODE_RE = re.compile(u'[%s]' % u''.join(_illegal_ranges)) +INVALID_XML_1_0_UNICODE_RE = re.compile(u'[%s]' % u''.join(_illegal_ranges)) STDOUT_LINE = '\nStdout:\n%s' @@ -94,6 +95,7 @@ def __init__(self, test_result, test_method, outcome=SUCCESS, err=None, subTest= self.test_result = test_result self.outcome = outcome self.elapsed_time = 0 + self.timestamp = datetime.datetime.min.replace(microsecond=0).isoformat() if err: if self.outcome != _TestInfo.SKIP: self.test_exception_name = safe_unicode(err[0].__name__) @@ -124,6 +126,8 @@ def test_finished(self): """ self.elapsed_time = \ self.test_result.stop_time - self.test_result.start_time + timestamp = datetime.datetime.fromtimestamp(self.test_result.stop_time) + self.timestamp = timestamp.replace(microsecond=0).isoformat() def get_description(self): """ @@ -343,6 +347,10 @@ def _report_testsuite(suite_name, tests, xml_document, parentElement, testsuite.setAttribute( 'time', '%.3f' % sum(map(lambda e: e.elapsed_time, tests)) ) + if tests: + testsuite.setAttribute( + 'timestamp', max(map(lambda e: e.timestamp, tests)) + ) failures = filter(lambda e: e.outcome == e.FAILURE, tests) testsuite.setAttribute('failures', str(len(list(failures)))) @@ -351,7 +359,7 @@ def _report_testsuite(suite_name, tests, xml_document, parentElement, skips = filter(lambda e: e.outcome == _TestInfo.SKIP, tests) testsuite.setAttribute('skipped', str(len(list(skips)))) - + _XMLTestResult._report_testsuite_properties( testsuite, xml_document, properties) @@ -420,6 +428,7 @@ def _report_testcase(test_result, xml_testsuite, xml_document): 'name', _XMLTestResult._test_method_name(test_result.test_id) ) testcase.setAttribute('time', '%.3f' % test_result.elapsed_time) + testcase.setAttribute('timestamp', test_result.timestamp) if (test_result.outcome != test_result.SUCCESS): elem_name = ('failure', 'error', 'skipped')[test_result.outcome-1]