From 24212fd97f6f0720f35a5c944bbf7a92f9abdf79 Mon Sep 17 00:00:00 2001 From: David Diaz Date: Wed, 19 Aug 2015 15:36:42 -0600 Subject: [PATCH 1/5] 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") From 2ddbac1f98f0a10000a390eb34ea8af5fa6c6bc4 Mon Sep 17 00:00:00 2001 From: David Diaz Date: Fri, 21 Aug 2015 14:31:20 -0600 Subject: [PATCH 2/5] Correcting implementation based on pull request feed back --- AUTHORS | 1 + CHANGELOG | 3 +++ _pytest/junitxml.py | 20 ++++++++------------ doc/en/usage.rst | 2 ++ testing/test_junitxml.py | 5 ++--- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/AUTHORS b/AUTHORS index 2fdc39fc2..5d9a162eb 100644 --- a/AUTHORS +++ b/AUTHORS @@ -60,3 +60,4 @@ Samuele Pedroni Tom Viner Trevor Bekolay Wouter van Ackooy +David Díaz-Barquero diff --git a/CHANGELOG b/CHANGELOG index 7d96418c8..b66eeb95f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -138,6 +138,9 @@ - fix issue890: changed extension of all documentation files from ``txt`` to ``rst``. Thanks to Abhijeet for the PR. +- implement feature on issue951: support to log additional information on xml + output. + 2.7.3 (compared to 2.7.2) ----------------------------- diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index 0c79d2951..c481fc1f0 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -9,6 +9,7 @@ import os import re import sys import time +import pytest # Python 2.X and 3.X compatibility if sys.version_info[0] < 3: @@ -53,12 +54,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) +@pytest.fixture +def record_xml_property(request): + if hasattr(request.config, "_xml"): + return request.config._xml.record_property + def dummy(*args, **kwargs): + pass + return dummy def pytest_addoption(parser): group = parser.getgroup("terminal reporting") @@ -76,18 +78,12 @@ 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 != '()'] names[0] = names[0].replace("/", '.') diff --git a/doc/en/usage.rst b/doc/en/usage.rst index baa625bdb..6c79d5eb0 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -162,6 +162,8 @@ record_property("key", value):: pytest.record_property("example_key", 1) ... +Warning: using this feature will break any schema verification. + Creating resultlog format files ---------------------------------------------------- diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 2ff9ca6ed..706b4e639 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -554,9 +554,8 @@ def test_unicode_issue368(testdir): def test_record_property(testdir): testdir.makepyfile(""" - from pytest import record_property - def test_record(): - record_property("foo", "<1"); + def test_record(record_xml_property): + record_xml_property("foo", "<1"); """) result, dom = runandparse(testdir) node = dom.getElementsByTagName("testsuite")[0] From 44d9365da045d9a7c494cb5edf000a90a25f8da2 Mon Sep 17 00:00:00 2001 From: David Diaz Date: Fri, 21 Aug 2015 15:21:12 -0600 Subject: [PATCH 3/5] Add warning of "preliminary feature" to record_xml_property --- doc/en/usage.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/en/usage.rst b/doc/en/usage.rst index 6c79d5eb0..c88b2b3c2 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -162,7 +162,9 @@ record_property("key", value):: pytest.record_property("example_key", 1) ... -Warning: using this feature will break any schema verification. +Warning: + - This is a preliminary feature. + - Using this feature will break any schema verification. Creating resultlog format files ---------------------------------------------------- From a20c6d072d70c535ed1f116fc04016c834ea9c14 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 23 Aug 2015 11:20:34 -0300 Subject: [PATCH 4/5] Fix getdoctarget to ignore comment lines --- doc/en/_getdoctarget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/_getdoctarget.py b/doc/en/_getdoctarget.py index 70427f745..20e487bb7 100755 --- a/doc/en/_getdoctarget.py +++ b/doc/en/_getdoctarget.py @@ -6,7 +6,7 @@ def get_version_string(): fn = py.path.local(__file__).join("..", "..", "..", "_pytest", "__init__.py") for line in fn.readlines(): - if "version" in line: + if "version" in line and not line.strip().startswith('#'): return eval(line.split("=")[-1]) def get_minor_version_string(): From 70da93145d1b821380315c39be1c052a4c8d3c54 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 23 Aug 2015 11:45:39 -0300 Subject: [PATCH 5/5] Improve docs and using warning system for record_xml_property fixture --- CHANGELOG | 4 ++-- _pytest/junitxml.py | 18 ++++++++++++------ doc/en/usage.rst | 37 +++++++++++++++++++++++++++---------- testing/test_junitxml.py | 3 ++- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b66eeb95f..23a67e5e5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -138,8 +138,8 @@ - fix issue890: changed extension of all documentation files from ``txt`` to ``rst``. Thanks to Abhijeet for the PR. -- implement feature on issue951: support to log additional information on xml - output. +- 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) ----------------------------- diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index c481fc1f0..8b75b139a 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -56,11 +56,17 @@ def bin_xml_escape(arg): @pytest.fixture def record_xml_property(request): - if hasattr(request.config, "_xml"): - return request.config._xml.record_property - def dummy(*args, **kwargs): - pass - return dummy + """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): group = parser.getgroup("terminal reporting") @@ -99,7 +105,7 @@ class LogXML(object): self.failed = self.errors = 0 self.custom_properties = {} - def record_property(self, name, value): + def add_custom_property(self, name, value): self.custom_properties[str(name)] = bin_xml_escape(str(value)) def _opentestcase(self, report): diff --git a/doc/en/usage.rst b/doc/en/usage.rst index c88b2b3c2..85478d51c 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -153,18 +153,35 @@ 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):: +record_xml_property +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - import pytest - def test_function(): - ... - pytest.record_property("example_key", 1) - ... +.. versionadded:: 2.8 -Warning: - - This is a preliminary feature. - - Using this feature will break any schema verification. +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 + + + +.. 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 ---------------------------------------------------- diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 706b4e639..9e819f756 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -557,7 +557,8 @@ def test_record_property(testdir): def test_record(record_xml_property): record_xml_property("foo", "<1"); """) - result, dom = runandparse(testdir) + 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*')