From 24212fd97f6f0720f35a5c944bbf7a92f9abdf79 Mon Sep 17 00:00:00 2001 From: David Diaz Date: Wed, 19 Aug 2015 15:36:42 -0600 Subject: [PATCH] Add support to record custom properties on xml output --- _pytest/junitxml.py | 21 +++++++++++++++++++++ doc/en/usage.rst | 9 +++++++++ testing/test_junitxml.py | 11 ++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index c12fa084a..0c79d2951 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -53,6 +53,13 @@ def bin_xml_escape(arg): return unicode('#x%04X') % i return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg))) +def record_property(name, value): + if hasattr(record_property, 'binding'): + record_property.binding(name, value) + +def pytest_namespace(): + return dict(record_property=record_property) + def pytest_addoption(parser): group = parser.getgroup("terminal reporting") group.addoption('--junitxml', '--junit-xml', action="store", @@ -69,12 +76,17 @@ def pytest_configure(config): config._xml = LogXML(xmlpath, config.option.junitprefix) config.pluginmanager.register(config._xml) + def binding(name, value): + config._xml.record_property(name, value) + record_property.binding = binding + def pytest_unconfigure(config): xml = getattr(config, '_xml', None) if xml: del config._xml config.pluginmanager.unregister(xml) + del record_property.binding def mangle_testnames(names): names = [x.replace(".py", "") for x in names if x != '()'] @@ -89,6 +101,10 @@ class LogXML(object): self.tests = [] self.passed = self.skipped = 0 self.failed = self.errors = 0 + self.custom_properties = {} + + def record_property(self, name, value): + self.custom_properties[str(name)] = bin_xml_escape(str(value)) def _opentestcase(self, report): names = mangle_testnames(report.nodeid.split("::")) @@ -118,6 +134,10 @@ class LogXML(object): def append(self, 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): self.passed += 1 self._write_captured_output(report) @@ -179,6 +199,7 @@ class LogXML(object): if report.when == "setup": self._opentestcase(report) self.tests[-1].attr.time += getattr(report, 'duration', 0) + self.append_custom_properties() if report.passed: if report.when == "call": # ignore setup/teardown self.append_pass(report) diff --git a/doc/en/usage.rst b/doc/en/usage.rst index 9984a2ac9..baa625bdb 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -153,6 +153,15 @@ integration servers, use this invocation:: to create an XML file at ``path``. +If you want to log additional information for a test, you can use +record_property("key", value):: + + import pytest + def test_function(): + ... + pytest.record_property("example_key", 1) + ... + Creating resultlog format files ---------------------------------------------------- diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index f8acd1576..2ff9ca6ed 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -552,4 +552,13 @@ def test_unicode_issue368(testdir): log.append_skipped(report) log.pytest_sessionfinish() - +def test_record_property(testdir): + testdir.makepyfile(""" + from pytest import record_property + def test_record(): + record_property("foo", "<1"); + """) + result, dom = runandparse(testdir) + node = dom.getElementsByTagName("testsuite")[0] + tnode = node.getElementsByTagName("testcase")[0] + assert_attr(tnode, foo="<1")