junitxml: introduce nodereporter and track durations/properties there
This commit is contained in:
parent
0664ae137c
commit
1f609f96e6
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue