Introduce record_testsuite_property fixture
This exposes the functionality introduced in fa6acdc
as a session-scoped fixture.
Plugins that want to remain compatible with the `xunit2`
standard should use this fixture instead of `record_property`.
Fix #5202
This commit is contained in:
parent
3a4a815c41
commit
73bbff2b74
|
@ -0,0 +1,5 @@
|
|||
New ``record_testsuite_property`` session-scoped fixture allows users to log ``<property>`` tags at the ``testsuite``
|
||||
level with the ``junitxml`` plugin.
|
||||
|
||||
The generated XML is compatible with the latest xunit standard, contrary to
|
||||
the properties recorded by ``record_property`` and ``record_xml_attribute``.
|
|
@ -424,6 +424,14 @@ record_property
|
|||
|
||||
.. autofunction:: _pytest.junitxml.record_property()
|
||||
|
||||
|
||||
record_testsuite_property
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`record_testsuite_property example`.
|
||||
|
||||
.. autofunction:: _pytest.junitxml.record_testsuite_property()
|
||||
|
||||
caplog
|
||||
~~~~~~
|
||||
|
||||
|
|
|
@ -458,13 +458,6 @@ instead, configure the ``junit_duration_report`` option like this:
|
|||
record_property
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
|
||||
|
||||
Fixture renamed from ``record_xml_property`` to ``record_property`` as user
|
||||
properties are now available to all reporters.
|
||||
``record_xml_property`` is now deprecated.
|
||||
|
||||
If you want to log additional information for a test, you can use the
|
||||
``record_property`` fixture:
|
||||
|
||||
|
@ -522,9 +515,7 @@ Will result in:
|
|||
|
||||
.. warning::
|
||||
|
||||
``record_property`` is an experimental feature and may change in the future.
|
||||
|
||||
Also please note that using this feature will break any schema verification.
|
||||
Please note that using this feature will break schema verifications for the latest JUnitXML schema.
|
||||
This might be a problem when used with some CI servers.
|
||||
|
||||
record_xml_attribute
|
||||
|
@ -587,43 +578,45 @@ Instead, this will add an attribute ``assertions="REQ-1234"`` inside the generat
|
|||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
LogXML: add_global_property
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
.. warning::
|
||||
|
||||
Please note that using this feature will break schema verifications for the latest JUnitXML schema.
|
||||
This might be a problem when used with some CI servers.
|
||||
|
||||
.. _record_testsuite_property example:
|
||||
|
||||
If you want to add a properties node in the testsuite level, which may contains properties that are relevant
|
||||
to all testcases you can use ``LogXML.add_global_properties``
|
||||
record_testsuite_property
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. versionadded:: 4.5
|
||||
|
||||
If you want to add a properties node at the test-suite level, which may contains properties
|
||||
that are relevant to all tests, you can use the ``record_testsuite_property`` session-scoped fixture:
|
||||
|
||||
The ``record_testsuite_property`` session-scoped fixture can be used to add properties relevant
|
||||
to all tests.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def log_global_env_facts(f):
|
||||
|
||||
if pytest.config.pluginmanager.hasplugin("junitxml"):
|
||||
my_junit = getattr(pytest.config, "_xml", None)
|
||||
|
||||
my_junit.add_global_property("ARCH", "PPC")
|
||||
my_junit.add_global_property("STORAGE_TYPE", "CEPH")
|
||||
|
||||
|
||||
@pytest.mark.usefixtures(log_global_env_facts.__name__)
|
||||
def start_and_prepare_env():
|
||||
pass
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def log_global_env_facts(record_testsuite_property):
|
||||
record_testsuite_property("ARCH", "PPC")
|
||||
record_testsuite_property("STORAGE_TYPE", "CEPH")
|
||||
|
||||
|
||||
class TestMe(object):
|
||||
def test_foo(self):
|
||||
assert True
|
||||
|
||||
This will add a property node below the testsuite node to the generated xml:
|
||||
The fixture is a callable which receives ``name`` and ``value`` of a ``<property>`` tag
|
||||
added at the test-suite level of the generated xml:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<testsuite errors="0" failures="0" name="pytest" skips="0" tests="1" time="0.006">
|
||||
<testsuite errors="0" failures="0" name="pytest" skipped="0" tests="1" time="0.006">
|
||||
<properties>
|
||||
<property name="ARCH" value="PPC"/>
|
||||
<property name="STORAGE_TYPE" value="CEPH"/>
|
||||
|
@ -631,11 +624,11 @@ This will add a property node below the testsuite node to the generated xml:
|
|||
<testcase classname="test_me.TestMe" file="test_me.py" line="16" name="test_foo" time="0.000243663787842"/>
|
||||
</testsuite>
|
||||
|
||||
.. warning::
|
||||
``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped.
|
||||
|
||||
The generated XML is compatible with the latest ``xunit`` standard, contrary to `record_property`_
|
||||
and `record_xml_attribute`_.
|
||||
|
||||
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.
|
||||
|
||||
Creating resultlog format files
|
||||
----------------------------------------------------
|
||||
|
|
|
@ -345,6 +345,45 @@ def record_xml_attribute(request):
|
|||
return attr_func
|
||||
|
||||
|
||||
def _check_record_param_type(param, v):
|
||||
"""Used by record_testsuite_property to check that the given parameter name is of the proper
|
||||
type"""
|
||||
__tracebackhide__ = True
|
||||
if not isinstance(v, six.string_types):
|
||||
msg = "{param} parameter needs to be a string, but {g} given"
|
||||
raise TypeError(msg.format(param=param, g=type(v).__name__))
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def record_testsuite_property(request):
|
||||
"""
|
||||
Records a new ``<property>`` tag as child of the root ``<testsuite>``. This is suitable to
|
||||
writing global information regarding the entire test suite, and is compatible with ``xunit2`` JUnit family.
|
||||
|
||||
This is a ``session``-scoped fixture which is called with ``(name, value)``. Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_foo(record_testsuite_property):
|
||||
record_testsuite_property("ARCH", "PPC")
|
||||
record_testsuite_property("STORAGE_TYPE", "CEPH")
|
||||
|
||||
``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped.
|
||||
"""
|
||||
|
||||
__tracebackhide__ = True
|
||||
|
||||
def record_func(name, value):
|
||||
"""noop function in case --junitxml was not passed in the command-line"""
|
||||
__tracebackhide__ = True
|
||||
_check_record_param_type("name", name)
|
||||
|
||||
xml = getattr(request.config, "_xml", None)
|
||||
if xml is not None:
|
||||
record_func = xml.add_global_property # noqa
|
||||
return record_func
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("terminal reporting")
|
||||
group.addoption(
|
||||
|
@ -444,6 +483,7 @@ class LogXML(object):
|
|||
self.node_reporters = {} # nodeid -> _NodeReporter
|
||||
self.node_reporters_ordered = []
|
||||
self.global_properties = []
|
||||
|
||||
# List of reports that failed on call but teardown is pending.
|
||||
self.open_reports = []
|
||||
self.cnt_double_fail_tests = 0
|
||||
|
@ -632,7 +672,9 @@ class LogXML(object):
|
|||
terminalreporter.write_sep("-", "generated xml file: %s" % (self.logfile))
|
||||
|
||||
def add_global_property(self, name, value):
|
||||
self.global_properties.append((str(name), bin_xml_escape(value)))
|
||||
__tracebackhide__ = True
|
||||
_check_record_param_type("name", name)
|
||||
self.global_properties.append((name, bin_xml_escape(value)))
|
||||
|
||||
def _get_global_properties_node(self):
|
||||
"""Return a Junit node containing custom properties, if any.
|
||||
|
|
|
@ -1243,6 +1243,53 @@ def test_url_property(testdir):
|
|||
), "The URL did not get written to the xml"
|
||||
|
||||
|
||||
def test_record_testsuite_property(testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_func1(record_testsuite_property):
|
||||
record_testsuite_property("stats", "all good")
|
||||
|
||||
def test_func2(record_testsuite_property):
|
||||
record_testsuite_property("stats", 10)
|
||||
"""
|
||||
)
|
||||
result, dom = runandparse(testdir)
|
||||
assert result.ret == 0
|
||||
node = dom.find_first_by_tag("testsuite")
|
||||
properties_node = node.find_first_by_tag("properties")
|
||||
p1_node = properties_node.find_nth_by_tag("property", 0)
|
||||
p2_node = properties_node.find_nth_by_tag("property", 1)
|
||||
p1_node.assert_attr(name="stats", value="all good")
|
||||
p2_node.assert_attr(name="stats", value="10")
|
||||
|
||||
|
||||
def test_record_testsuite_property_junit_disabled(testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_func1(record_testsuite_property):
|
||||
record_testsuite_property("stats", "all good")
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("junit", [True, False])
|
||||
def test_record_testsuite_property_type_checking(testdir, junit):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_func1(record_testsuite_property):
|
||||
record_testsuite_property(1, 2)
|
||||
"""
|
||||
)
|
||||
args = ("--junitxml=tests.xml",) if junit else ()
|
||||
result = testdir.runpytest(*args)
|
||||
assert result.ret == 1
|
||||
result.stdout.fnmatch_lines(
|
||||
["*TypeError: name parameter needs to be a string, but int given"]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("suite_name", ["my_suite", ""])
|
||||
def test_set_suite_name(testdir, suite_name):
|
||||
if suite_name:
|
||||
|
|
Loading…
Reference in New Issue