From a5e60b6a2d7f8044a08334cd4ca1a9873489528e Mon Sep 17 00:00:00 2001 From: Raphael Castaneda Date: Thu, 18 Jan 2018 16:06:42 -0800 Subject: [PATCH] implement #3130 - adding record_xml_attribute fixture update incorrect expected attribute value in test_record_attribute attr names must be strings Update CHANGELOG formatting update usage documentation Fix versionadded for record_xml_attribute Indent the xml schema properly inside the warning box in the docs --- AUTHORS | 1 + _pytest/junitxml.py | 26 +++++++++++++++++ changelog/3130.feature | 1 + doc/en/usage.rst | 60 ++++++++++++++++++++++++++++++++++++++++ testing/test_junitxml.py | 21 ++++++++++++++ 5 files changed, 109 insertions(+) create mode 100644 changelog/3130.feature diff --git a/AUTHORS b/AUTHORS index 862378be9..502cfff7e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -149,6 +149,7 @@ Punyashloka Biswal Quentin Pradet Ralf Schmitt Ran Benita +Raphael Castaneda Raphael Pierzina Raquel Alegre Ravi Chandra diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index 7fb40dc35..e929eeba8 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -85,6 +85,9 @@ class _NodeReporter(object): def add_property(self, name, value): self.properties.append((str(name), bin_xml_escape(value))) + def add_attribute(self, name, value): + self.attrs[str(name)] = bin_xml_escape(value) + def make_properties_node(self): """Return a Junit node containing custom properties, if any. """ @@ -98,6 +101,7 @@ class _NodeReporter(object): def record_testreport(self, testreport): assert not self.testcase names = mangle_test_address(testreport.nodeid) + existing_attrs = self.attrs classnames = names[:-1] if self.xml.prefix: classnames.insert(0, self.xml.prefix) @@ -111,6 +115,7 @@ class _NodeReporter(object): if hasattr(testreport, "url"): attrs["url"] = testreport.url self.attrs = attrs + self.attrs.update(existing_attrs) # restore any user-defined attributes def to_xml(self): testcase = Junit.testcase(time=self.duration, **self.attrs) @@ -211,6 +216,27 @@ def record_xml_property(request): return add_property_noop +@pytest.fixture +def record_xml_attribute(request): + """Add extra xml attributes to the tag for the calling test. + The fixture is callable with ``(name, value)``, with value being automatically + xml-encoded + """ + request.node.warn( + code='C3', + message='record_xml_attribute is an experimental feature', + ) + xml = getattr(request.config, "_xml", None) + if xml is not None: + node_reporter = xml.node_reporter(request.node.nodeid) + return node_reporter.add_attribute + else: + def add_attr_noop(name, value): + pass + + return add_attr_noop + + def pytest_addoption(parser): group = parser.getgroup("terminal reporting") group.addoption( diff --git a/changelog/3130.feature b/changelog/3130.feature new file mode 100644 index 000000000..af2c23588 --- /dev/null +++ b/changelog/3130.feature @@ -0,0 +1 @@ +New fixture ``record_xml_attribute`` that allows modifying and inserting attributes on the ```` xml node in JUnit reports. diff --git a/doc/en/usage.rst b/doc/en/usage.rst index 6091db8be..9b9552bc6 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -256,6 +256,66 @@ This will add an extra property ``example_key="1"`` to the generated Also please note that using this feature will break any schema verification. This might be a problem when used with some CI servers. +record_xml_attribute +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. versionadded:: 3.4 + +To add an additional xml attribute to a testcase element, you can use +``record_xml_attribute`` fixture. This can also be used to override existing values: + +.. code-block:: python + + def test_function(record_xml_attribute): + record_xml_attribute("assertions", "REQ-1234") + record_xml_attribute("classname", "custom_classname") + print('hello world') + assert True + +Unlike ``record_xml_property``, this will not add a new child element. +Instead, this will add an attribute ``assertions="REQ-1234"`` inside the generated +``testcase`` tag and override the default ``classname`` with ``"classname=custom_classname"``: + +.. code-block:: xml + + + + hello world + + + +.. warning:: + + ``record_xml_attribute`` 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. + + Using this over ``record_xml_property`` can help when using ci tools to parse the xml report. + However, some parsers are quite strict about the elements and attributes that are allowed. + Many tools use an xsd schema (like the example below) to validate incoming xml. + Make sure you are using attribute names that are allowed by your parser. + + Below is the Scheme used by Jenkins to validate the XML report: + + .. code-block:: xml + + + + + + + + + + + + + + + + + + LogXML: add_global_property ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index b604c02a3..49318ef76 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -879,6 +879,27 @@ def test_record_property_same_name(testdir): pnodes[1].assert_attr(name="foo", value="baz") +def test_record_attribute(testdir): + testdir.makepyfile(""" + import pytest + + @pytest.fixture + def other(record_xml_attribute): + record_xml_attribute("bar", 1) + def test_record(record_xml_attribute, other): + record_xml_attribute("foo", "<1"); + """) + result, dom = runandparse(testdir, '-rw') + node = dom.find_first_by_tag("testsuite") + tnode = node.find_first_by_tag("testcase") + tnode.assert_attr(bar="1") + tnode.assert_attr(foo="<1") + result.stdout.fnmatch_lines([ + 'test_record_attribute.py::test_record', + '*record_xml_attribute*experimental*', + ]) + + def test_random_report_log_xdist(testdir): """xdist calls pytest_runtest_logreport as they are executed by the slaves, with nodes from several nodes overlapping, so junitxml must cope with that