Merge pull request #956 from nicoddemus/record-xml-property

add preliminary support for extended junit xml properties
This commit is contained in:
Ronny Pfannschmidt 2015-08-24 09:13:04 +02:00
commit b25e41e348
6 changed files with 69 additions and 3 deletions

View File

@ -61,3 +61,4 @@ Samuele Pedroni
Tom Viner Tom Viner
Trevor Bekolay Trevor Bekolay
Wouter van Ackooy Wouter van Ackooy
David Díaz-Barquero

View File

@ -147,6 +147,9 @@
- fix issue890: changed extension of all documentation files from ``txt`` to - fix issue890: changed extension of all documentation files from ``txt`` to
``rst``. Thanks to Abhijeet for the PR. ``rst``. Thanks to Abhijeet for the PR.
- issue951: add new record_xml_property fixture, that supports logging
additional information on xml output. Thanks David Diaz for the PR.
2.7.3 (compared to 2.7.2) 2.7.3 (compared to 2.7.2)
----------------------------- -----------------------------

View File

@ -9,6 +9,7 @@ import os
import re import re
import sys import sys
import time import time
import pytest
# Python 2.X and 3.X compatibility # Python 2.X and 3.X compatibility
if sys.version_info[0] < 3: if sys.version_info[0] < 3:
@ -53,6 +54,20 @@ def bin_xml_escape(arg):
return unicode('#x%04X') % i return unicode('#x%04X') % i
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)))
@pytest.fixture
def record_xml_property(request):
"""Fixture that adds extra xml properties to the tag for the calling test.
The fixture is callable with (name, value), with value being automatically
xml-encoded.
"""
def inner(name, value):
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):
group = parser.getgroup("terminal reporting") group = parser.getgroup("terminal reporting")
group.addoption('--junitxml', '--junit-xml', action="store", group.addoption('--junitxml', '--junit-xml', action="store",
@ -75,7 +90,6 @@ def pytest_unconfigure(config):
del config._xml del config._xml
config.pluginmanager.unregister(xml) config.pluginmanager.unregister(xml)
def mangle_testnames(names): def mangle_testnames(names):
names = [x.replace(".py", "") for x in names if x != '()'] names = [x.replace(".py", "") for x in names if x != '()']
names[0] = names[0].replace("/", '.') names[0] = names[0].replace("/", '.')
@ -89,6 +103,10 @@ class LogXML(object):
self.tests = [] self.tests = []
self.passed = self.skipped = 0 self.passed = self.skipped = 0
self.failed = self.errors = 0 self.failed = self.errors = 0
self.custom_properties = {}
def add_custom_property(self, name, value):
self.custom_properties[str(name)] = bin_xml_escape(str(value))
def _opentestcase(self, report): def _opentestcase(self, report):
names = mangle_testnames(report.nodeid.split("::")) names = mangle_testnames(report.nodeid.split("::"))
@ -118,6 +136,10 @@ class LogXML(object):
def append(self, obj): def append(self, obj):
self.tests[-1].append(obj) self.tests[-1].append(obj)
def append_custom_properties(self):
self.tests[-1].attr.__dict__.update(self.custom_properties)
self.custom_properties.clear()
def append_pass(self, report): def append_pass(self, report):
self.passed += 1 self.passed += 1
self._write_captured_output(report) self._write_captured_output(report)
@ -179,6 +201,7 @@ class LogXML(object):
if report.when == "setup": if report.when == "setup":
self._opentestcase(report) self._opentestcase(report)
self.tests[-1].attr.time += getattr(report, 'duration', 0) self.tests[-1].attr.time += getattr(report, 'duration', 0)
self.append_custom_properties()
if report.passed: if report.passed:
if report.when == "call": # ignore setup/teardown if report.when == "call": # ignore setup/teardown
self.append_pass(report) self.append_pass(report)

View File

@ -6,7 +6,7 @@ def get_version_string():
fn = py.path.local(__file__).join("..", "..", "..", fn = py.path.local(__file__).join("..", "..", "..",
"_pytest", "__init__.py") "_pytest", "__init__.py")
for line in fn.readlines(): for line in fn.readlines():
if "version" in line: if "version" in line and not line.strip().startswith('#'):
return eval(line.split("=")[-1]) return eval(line.split("=")[-1])
def get_minor_version_string(): def get_minor_version_string():

View File

@ -153,6 +153,36 @@ integration servers, use this invocation::
to create an XML file at ``path``. to create an XML file at ``path``.
record_xml_property
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. versionadded:: 2.8
If you want to log additional information for a test, you can use the
``record_xml_property`` fixture:
.. code-block:: python
def test_function(record_xml_property):
record_xml_property("example_key", 1)
assert 0
This will add an extra property ``example_key="1"`` to the generated
``testcase`` tag:
.. code-block:: xml
<testcase classname="test_function" example_key="1" file="test_function.py" line="0" name="test_function" time="0.0009">
.. warning::
This is an experimental feature, and its interface might be replaced
by something more powerful and general in future versions. The
functionality per-se will be kept, however.
Also please note that using this feature will break any schema verification.
This might be a problem when used with some CI servers.
Creating resultlog format files Creating resultlog format files
---------------------------------------------------- ----------------------------------------------------

View File

@ -553,4 +553,13 @@ def test_unicode_issue368(testdir):
log.append_skipped(report) log.append_skipped(report)
log.pytest_sessionfinish() log.pytest_sessionfinish()
def test_record_property(testdir):
testdir.makepyfile("""
def test_record(record_xml_property):
record_xml_property("foo", "<1");
""")
result, dom = runandparse(testdir, '-rw')
node = dom.getElementsByTagName("testsuite")[0]
tnode = node.getElementsByTagName("testcase")[0]
assert_attr(tnode, foo="<1")
result.stdout.fnmatch_lines('*C3*test_record_property.py*experimental*')