junitxml: introduce nodereporter and track durations/properties there

This commit is contained in:
Ronny Pfannschmidt 2015-10-10 11:45:56 +02:00
parent 0664ae137c
commit 1f609f96e6
1 changed files with 52 additions and 34 deletions

View File

@ -58,22 +58,48 @@ def bin_xml_escape(arg):
return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg))) return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
class _NodeReporter(object):
def __init__(self, nodeid, xml):
self.id = nodeid
self.duration = 0
self.properties = {}
self.testcase = None
def add_property(self, name, value):
self.properties[str(name)] = bin_xml_escape(value)
def make_properties_node(self):
"""Return a Junit node containing custom properties set for
the current test, if any, and reset the current custom properties.
"""
if self.properties:
return Junit.properties([
Junit.property(name=name, value=value)
for name, value in self.properties.items()
])
@pytest.fixture @pytest.fixture
def record_xml_property(request): def record_xml_property(request):
"""Fixture that adds extra xml properties to the tag for the calling test. """Fixture that adds extra xml properties to the tag for the calling test.
The fixture is callable with (name, value), with value being automatically The fixture is callable with (name, value), with value being automatically
xml-encoded. xml-encoded.
""" """
request.config.warn(
code='C3',
message='record_xml_property is an experimental feature',
fslocation=request.node.location[:2])
xml = getattr(request.config, "_xml", None)
if xml is not None:
nodereporter = xml.nodereporter(request.node.nodeid)
return nodereporter.add_property
else:
def add_property_noop(name, value):
pass
def inner(name, value): return add_property_noop
if hasattr(request.config, "_xml"):
request.config._xml.add_custom_property(name, value)
msg = 'record_xml_property is an experimental feature'
request.config.warn(code='C3',
message=msg,
fslocation=request.node.location[:2])
return inner
def pytest_addoption(parser): def pytest_addoption(parser):
@ -126,12 +152,16 @@ class LogXML(object):
'failure', 'failure',
'skipped', 'skipped',
], 0) ], 0)
self.tests_by_nodeid = {} # nodeid -> Junit.testcase self.nodereporters = {} # nodeid -> Junit.testcase
self.durations = {} # nodeid -> total duration (setup+call+teardown) self.tests_by_nodeid = {}
self.custom_properties = {}
def nodereporter(self, nodeid):
if nodeid in self.nodereporters:
return self.nodereporters[nodeid]
reporter = _NodeReporter(nodeid, self)
self.nodereporters[nodeid] = reporter
return reporter
def add_custom_property(self, name, value):
self.custom_properties[str(name)] = bin_xml_escape(str(value))
def _addtestcase(self, attrs=None, **kw): def _addtestcase(self, attrs=None, **kw):
testcase = Junit.testcase(**(attrs or kw)) testcase = Junit.testcase(**(attrs or kw))
@ -148,6 +178,7 @@ class LogXML(object):
self.append(node) self.append(node)
def _opentestcase(self, report): def _opentestcase(self, report):
reporter = self.nodereporter(report.nodeid)
names = mangle_testnames(report.nodeid.split("::")) names = mangle_testnames(report.nodeid.split("::"))
classnames = names[:-1] classnames = names[:-1]
if self.prefix: if self.prefix:
@ -156,12 +187,13 @@ class LogXML(object):
"classname": ".".join(classnames), "classname": ".".join(classnames),
"name": bin_xml_escape(names[-1]), "name": bin_xml_escape(names[-1]),
"file": report.location[0], "file": report.location[0],
"time": self.durations.get(report.nodeid, 0), "time": reporter.duration,
} }
if report.location[1] is not None: if report.location[1] is not None:
attrs["line"] = report.location[1] attrs["line"] = report.location[1]
testcase = self._addtestcase(attrs) testcase = self._addtestcase(attrs)
custom_properties = self.pop_custom_properties() reporter = self.nodereporter(report.nodeid)
custom_properties = reporter.make_properties_node()
if custom_properties: if custom_properties:
testcase.append(custom_properties) testcase.append(custom_properties)
self.tests_by_nodeid[report.nodeid] = testcase self.tests_by_nodeid[report.nodeid] = testcase
@ -180,20 +212,7 @@ class LogXML(object):
self._add_stats(type(obj).__name__) self._add_stats(type(obj).__name__)
self.tests[-1].append(obj) self.tests[-1].append(obj)
def pop_custom_properties(self):
"""Return a Junit node containing custom properties set for
the current test, if any, and reset the current custom properties.
"""
if self.custom_properties:
result = Junit.properties(
[
Junit.property(name=name,
value=value)
for name, value in self.custom_properties.items()
])
self.custom_properties.clear()
return result
return None
def append_pass(self, report): def append_pass(self, report):
self._add_stats('passed') self._add_stats('passed')
@ -288,13 +307,12 @@ class LogXML(object):
"""accumulates total duration for nodeid from given report and updates """accumulates total duration for nodeid from given report and updates
the Junit.testcase with the new total if already created. the Junit.testcase with the new total if already created.
""" """
total = self.durations.get(report.nodeid, 0.0) reporter = self.nodereporter(report.nodeid)
total += getattr(report, 'duration', 0.0) reporter.duration += getattr(report, 'duration', 0.0)
self.durations[report.nodeid] = total
testcase = self.tests_by_nodeid.get(report.nodeid) testcase = self.tests_by_nodeid.get(report.nodeid)
if testcase is not None: if testcase is not None:
testcase.attr.time = total testcase.attr.time = reporter.duration
def pytest_collectreport(self, report): def pytest_collectreport(self, report):
if not report.passed: if not report.passed: