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() | .. 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