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()
|
.. autofunction:: _pytest.junitxml.record_property()
|
||||||
|
|
||||||
|
|
||||||
|
record_testsuite_property
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
**Tutorial**: :ref:`record_testsuite_property example`.
|
||||||
|
|
||||||
|
.. autofunction:: _pytest.junitxml.record_testsuite_property()
|
||||||
|
|
||||||
caplog
|
caplog
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -458,13 +458,6 @@ instead, configure the ``junit_duration_report`` option like this:
|
||||||
record_property
|
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
|
If you want to log additional information for a test, you can use the
|
||||||
``record_property`` fixture:
|
``record_property`` fixture:
|
||||||
|
|
||||||
|
@ -522,9 +515,7 @@ Will result in:
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
``record_property`` is an experimental feature and may change in the future.
|
Please note that using this feature will break schema verifications for the latest JUnitXML schema.
|
||||||
|
|
||||||
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
|
record_xml_attribute
|
||||||
|
@ -587,43 +578,45 @@ Instead, this will add an attribute ``assertions="REQ-1234"`` inside the generat
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</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
|
record_testsuite_property
|
||||||
to all testcases you can use ``LogXML.add_global_properties``
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. 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
|
.. code-block:: python
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
def log_global_env_facts(f):
|
def log_global_env_facts(record_testsuite_property):
|
||||||
|
record_testsuite_property("ARCH", "PPC")
|
||||||
if pytest.config.pluginmanager.hasplugin("junitxml"):
|
record_testsuite_property("STORAGE_TYPE", "CEPH")
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class TestMe(object):
|
class TestMe(object):
|
||||||
def test_foo(self):
|
def test_foo(self):
|
||||||
assert True
|
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
|
.. 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>
|
<properties>
|
||||||
<property name="ARCH" value="PPC"/>
|
<property name="ARCH" value="PPC"/>
|
||||||
<property name="STORAGE_TYPE" value="CEPH"/>
|
<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"/>
|
<testcase classname="test_me.TestMe" file="test_me.py" line="16" name="test_foo" time="0.000243663787842"/>
|
||||||
</testsuite>
|
</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
|
Creating resultlog format files
|
||||||
----------------------------------------------------
|
----------------------------------------------------
|
||||||
|
|
|
@ -345,6 +345,45 @@ def record_xml_attribute(request):
|
||||||
return attr_func
|
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):
|
def pytest_addoption(parser):
|
||||||
group = parser.getgroup("terminal reporting")
|
group = parser.getgroup("terminal reporting")
|
||||||
group.addoption(
|
group.addoption(
|
||||||
|
@ -444,6 +483,7 @@ class LogXML(object):
|
||||||
self.node_reporters = {} # nodeid -> _NodeReporter
|
self.node_reporters = {} # nodeid -> _NodeReporter
|
||||||
self.node_reporters_ordered = []
|
self.node_reporters_ordered = []
|
||||||
self.global_properties = []
|
self.global_properties = []
|
||||||
|
|
||||||
# List of reports that failed on call but teardown is pending.
|
# List of reports that failed on call but teardown is pending.
|
||||||
self.open_reports = []
|
self.open_reports = []
|
||||||
self.cnt_double_fail_tests = 0
|
self.cnt_double_fail_tests = 0
|
||||||
|
@ -632,7 +672,9 @@ class LogXML(object):
|
||||||
terminalreporter.write_sep("-", "generated xml file: %s" % (self.logfile))
|
terminalreporter.write_sep("-", "generated xml file: %s" % (self.logfile))
|
||||||
|
|
||||||
def add_global_property(self, name, value):
|
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):
|
def _get_global_properties_node(self):
|
||||||
"""Return a Junit node containing custom properties, if any.
|
"""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"
|
), "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", ""])
|
@pytest.mark.parametrize("suite_name", ["my_suite", ""])
|
||||||
def test_set_suite_name(testdir, suite_name):
|
def test_set_suite_name(testdir, suite_name):
|
||||||
if suite_name:
|
if suite_name:
|
||||||
|
|
Loading…
Reference in New Issue