Skip to content

Commit 484c355

Browse files
authored
Merge pull request #157 from ICTU/master
Add an ISO-8601 timestamp to every testsuite and testcase in the XML-report
2 parents 1ebec08 + 05a94fc commit 484c355

File tree

3 files changed

+36
-14
lines changed

3 files changed

+36
-14
lines changed

tests/builder_test.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ def test_add_time_attribute_on_end_context(self):
8383

8484
element.attributes['time'].value
8585

86+
def test_add_timestamp_attribute_on_end_context(self):
87+
self.root.begin('testsuite', 'name')
88+
element = self.root.end()
89+
90+
element.attributes['timestamp'].value
91+
8692

8793
class TestXMLBuilderTest(unittest.TestCase):
8894
"""TestXMLBuilder test cases.

xmlrunner/builder.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import re
22
import sys
3+
import datetime
34
import time
45
import six
56

@@ -80,6 +81,7 @@ def end(self):
8081
"""
8182
self._stop_time = time.time()
8283
self.element.setAttribute('time', self.elapsed_time())
84+
self.element.setAttribute('timestamp', self.timestamp())
8385
self._set_result_counters()
8486
return self.element
8587

@@ -121,6 +123,11 @@ def elapsed_time(self):
121123
"""
122124
return format(self._stop_time - self._start_time, '.3f')
123125

126+
def timestamp(self):
127+
"""Returns the time the context ended as ISO-8601-formatted timestamp.
128+
"""
129+
return datetime.datetime.fromtimestamp(self._stop_time).replace(microsecond=0).isoformat()
130+
124131

125132
class TestXMLBuilder(object):
126133
"""This class encapsulates most rules needed to create a XML test report

xmlrunner/result.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
import os
33
import sys
4+
import datetime
45
import time
56
import traceback
67
import six
@@ -16,28 +17,28 @@
1617
# http://stackoverflow.com/questions/1707890/fast-way-to-filter-illegal-xml-unicode-chars-in-python
1718

1819
_illegal_unichrs = [
19-
(0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F),
20-
(0x7F, 0x84), (0x86, 0x9F),
20+
(0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F),
21+
(0x7F, 0x84), (0x86, 0x9F),
2122
(0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF),
22-
]
23-
if sys.maxunicode >= 0x10000: # not narrow build
23+
]
24+
if sys.maxunicode >= 0x10000: # not narrow build
2425
_illegal_unichrs.extend([
25-
(0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF),
26-
(0x3FFFE, 0x3FFFF), (0x4FFFE, 0x4FFFF),
27-
(0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF),
28-
(0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF),
29-
(0x9FFFE, 0x9FFFF), (0xAFFFE, 0xAFFFF),
30-
(0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF),
31-
(0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF),
26+
(0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF),
27+
(0x3FFFE, 0x3FFFF), (0x4FFFE, 0x4FFFF),
28+
(0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF),
29+
(0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF),
30+
(0x9FFFE, 0x9FFFF), (0xAFFFE, 0xAFFFF),
31+
(0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF),
32+
(0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF),
3233
(0xFFFFE, 0xFFFFF), (0x10FFFE, 0x10FFFF),
33-
])
34+
])
3435

3536
_illegal_ranges = [
3637
"%s-%s" % (six.unichr(low), six.unichr(high))
3738
for (low, high) in _illegal_unichrs
3839
]
3940

40-
INVALID_XML_1_0_UNICODE_RE = re.compile(u'[%s]' % u''.join(_illegal_ranges))
41+
INVALID_XML_1_0_UNICODE_RE = re.compile(u'[%s]' % u''.join(_illegal_ranges))
4142

4243

4344
STDOUT_LINE = '\nStdout:\n%s'
@@ -94,6 +95,7 @@ def __init__(self, test_result, test_method, outcome=SUCCESS, err=None, subTest=
9495
self.test_result = test_result
9596
self.outcome = outcome
9697
self.elapsed_time = 0
98+
self.timestamp = datetime.datetime.min.replace(microsecond=0).isoformat()
9799
if err:
98100
if self.outcome != _TestInfo.SKIP:
99101
self.test_exception_name = safe_unicode(err[0].__name__)
@@ -124,6 +126,8 @@ def test_finished(self):
124126
"""
125127
self.elapsed_time = \
126128
self.test_result.stop_time - self.test_result.start_time
129+
timestamp = datetime.datetime.fromtimestamp(self.test_result.stop_time)
130+
self.timestamp = timestamp.replace(microsecond=0).isoformat()
127131

128132
def get_description(self):
129133
"""
@@ -343,6 +347,10 @@ def _report_testsuite(suite_name, tests, xml_document, parentElement,
343347
testsuite.setAttribute(
344348
'time', '%.3f' % sum(map(lambda e: e.elapsed_time, tests))
345349
)
350+
if tests:
351+
testsuite.setAttribute(
352+
'timestamp', max(map(lambda e: e.timestamp, tests))
353+
)
346354
failures = filter(lambda e: e.outcome == e.FAILURE, tests)
347355
testsuite.setAttribute('failures', str(len(list(failures))))
348356

@@ -351,7 +359,7 @@ def _report_testsuite(suite_name, tests, xml_document, parentElement,
351359

352360
skips = filter(lambda e: e.outcome == _TestInfo.SKIP, tests)
353361
testsuite.setAttribute('skipped', str(len(list(skips))))
354-
362+
355363
_XMLTestResult._report_testsuite_properties(
356364
testsuite, xml_document, properties)
357365

@@ -420,6 +428,7 @@ def _report_testcase(test_result, xml_testsuite, xml_document):
420428
'name', _XMLTestResult._test_method_name(test_result.test_id)
421429
)
422430
testcase.setAttribute('time', '%.3f' % test_result.elapsed_time)
431+
testcase.setAttribute('timestamp', test_result.timestamp)
423432

424433
if (test_result.outcome != test_result.SUCCESS):
425434
elem_name = ('failure', 'error', 'skipped')[test_result.outcome-1]

0 commit comments

Comments
 (0)