Merge pull request #3132 from raphaelcastaneda/feature/add-record-xml-attribute
implement #3130 - add record_xml_attribute fixture
This commit is contained in:
commit
49773b573f
1
AUTHORS
1
AUTHORS
|
@ -154,6 +154,7 @@ Punyashloka Biswal
|
||||||
Quentin Pradet
|
Quentin Pradet
|
||||||
Ralf Schmitt
|
Ralf Schmitt
|
||||||
Ran Benita
|
Ran Benita
|
||||||
|
Raphael Castaneda
|
||||||
Raphael Pierzina
|
Raphael Pierzina
|
||||||
Raquel Alegre
|
Raquel Alegre
|
||||||
Ravi Chandra
|
Ravi Chandra
|
||||||
|
|
|
@ -85,6 +85,9 @@ class _NodeReporter(object):
|
||||||
def add_property(self, name, value):
|
def add_property(self, name, value):
|
||||||
self.properties.append((str(name), bin_xml_escape(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):
|
def make_properties_node(self):
|
||||||
"""Return a Junit node containing custom properties, if any.
|
"""Return a Junit node containing custom properties, if any.
|
||||||
"""
|
"""
|
||||||
|
@ -98,6 +101,7 @@ class _NodeReporter(object):
|
||||||
def record_testreport(self, testreport):
|
def record_testreport(self, testreport):
|
||||||
assert not self.testcase
|
assert not self.testcase
|
||||||
names = mangle_test_address(testreport.nodeid)
|
names = mangle_test_address(testreport.nodeid)
|
||||||
|
existing_attrs = self.attrs
|
||||||
classnames = names[:-1]
|
classnames = names[:-1]
|
||||||
if self.xml.prefix:
|
if self.xml.prefix:
|
||||||
classnames.insert(0, self.xml.prefix)
|
classnames.insert(0, self.xml.prefix)
|
||||||
|
@ -111,6 +115,7 @@ class _NodeReporter(object):
|
||||||
if hasattr(testreport, "url"):
|
if hasattr(testreport, "url"):
|
||||||
attrs["url"] = testreport.url
|
attrs["url"] = testreport.url
|
||||||
self.attrs = attrs
|
self.attrs = attrs
|
||||||
|
self.attrs.update(existing_attrs) # restore any user-defined attributes
|
||||||
|
|
||||||
def to_xml(self):
|
def to_xml(self):
|
||||||
testcase = Junit.testcase(time=self.duration, **self.attrs)
|
testcase = Junit.testcase(time=self.duration, **self.attrs)
|
||||||
|
@ -211,6 +216,27 @@ def record_xml_property(request):
|
||||||
return add_property_noop
|
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):
|
def pytest_addoption(parser):
|
||||||
group = parser.getgroup("terminal reporting")
|
group = parser.getgroup("terminal reporting")
|
||||||
group.addoption(
|
group.addoption(
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
New fixture ``record_xml_attribute`` that allows modifying and inserting attributes on the ``<testcase>`` xml node in JUnit reports.
|
|
@ -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.
|
Also please note that using this feature will break any schema verification.
|
||||||
This might be a problem when used with some CI servers.
|
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
|
||||||
|
|
||||||
|
<testcase classname="custom_classname" file="test_function.py" line="0" name="test_function" time="0.003" assertions="REQ-1234">
|
||||||
|
<system-out>
|
||||||
|
hello world
|
||||||
|
</system-out>
|
||||||
|
</testcase>
|
||||||
|
|
||||||
|
.. 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
|
||||||
|
|
||||||
|
<xs:element name="testcase">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element ref="skipped" minOccurs="0" maxOccurs="1"/>
|
||||||
|
<xs:element ref="error" minOccurs="0" maxOccurs="unbounded"/>
|
||||||
|
<xs:element ref="failure" minOccurs="0" maxOccurs="unbounded"/>
|
||||||
|
<xs:element ref="system-out" minOccurs="0" maxOccurs="unbounded"/>
|
||||||
|
<xs:element ref="system-err" minOccurs="0" maxOccurs="unbounded"/>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute name="name" type="xs:string" use="required"/>
|
||||||
|
<xs:attribute name="assertions" type="xs:string" use="optional"/>
|
||||||
|
<xs:attribute name="time" type="xs:string" use="optional"/>
|
||||||
|
<xs:attribute name="classname" type="xs:string" use="optional"/>
|
||||||
|
<xs:attribute name="status" type="xs:string" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
|
||||||
LogXML: add_global_property
|
LogXML: add_global_property
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -879,6 +879,27 @@ def test_record_property_same_name(testdir):
|
||||||
pnodes[1].assert_attr(name="foo", value="baz")
|
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):
|
def test_random_report_log_xdist(testdir):
|
||||||
"""xdist calls pytest_runtest_logreport as they are executed by the slaves,
|
"""xdist calls pytest_runtest_logreport as they are executed by the slaves,
|
||||||
with nodes from several nodes overlapping, so junitxml must cope with that
|
with nodes from several nodes overlapping, so junitxml must cope with that
|
||||||
|
|
Loading…
Reference in New Issue