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