Introduce record_testsuite_property fixture (#5205)
Introduce record_testsuite_property fixture
This commit is contained in:
		
						commit
						184ef92f0b
					
				|  | @ -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