diff options
| author | 0x221E <0x221E@0xinfinity.dev> | 2026-04-12 16:59:40 +0200 |
|---|---|---|
| committer | 0x221E <0x221E@0xinfinity.dev> | 2026-04-12 16:59:40 +0200 |
| commit | a66c7433c2c11b8b6c99142277ed4e16b1a2a465 (patch) | |
| tree | e54bcfb59c303acf6118fd11f06d5c0bd5f24e5d /external/gtest/googletest/test/gtest_xml_test_utils.py | |
Diffstat (limited to 'external/gtest/googletest/test/gtest_xml_test_utils.py')
| -rwxr-xr-x | external/gtest/googletest/test/gtest_xml_test_utils.py | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/external/gtest/googletest/test/gtest_xml_test_utils.py b/external/gtest/googletest/test/gtest_xml_test_utils.py new file mode 100755 index 0000000..74e0f4a --- /dev/null +++ b/external/gtest/googletest/test/gtest_xml_test_utils.py @@ -0,0 +1,242 @@ +# Copyright 2006, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Unit test utilities for gtest_xml_output""" + +import re +from xml.dom import minidom, Node +from googletest.test import gtest_test_utils + +GTEST_DEFAULT_OUTPUT_FILE = 'test_detail.xml' + + +class GTestXMLTestCase(gtest_test_utils.TestCase): + """Base class for tests of Google Test's XML output functionality.""" + + def AssertEquivalentNodes(self, expected_node, actual_node): + """Asserts that actual_node is equivalent to expected_node. + + Asserts that actual_node (a DOM node object) is equivalent to + expected_node (another DOM node object), in that either both of + them are CDATA nodes and have the same value, or both are DOM + elements and actual_node meets all of the following conditions: + + * It has the same tag name as expected_node. + * It has the same set of attributes as expected_node, each with + the same value as the corresponding attribute of expected_node. + Exceptions are any attribute named "time", which needs only be + convertible to a floating-point number and any attribute named + "type_param" which only has to be non-empty. + * It has an equivalent set of child nodes (including elements and + CDATA sections) as expected_node. Note that we ignore the + order of the children as they are not guaranteed to be in any + particular order. + + Args: + expected_node: expected DOM node object + actual_node: actual DOM node object + """ + + if expected_node.nodeType == Node.CDATA_SECTION_NODE: + self.assertEqual(Node.CDATA_SECTION_NODE, actual_node.nodeType) + self.assertEqual(expected_node.nodeValue, actual_node.nodeValue) + return + + self.assertEqual(Node.ELEMENT_NODE, actual_node.nodeType) + self.assertEqual(Node.ELEMENT_NODE, expected_node.nodeType) + self.assertEqual(expected_node.tagName, actual_node.tagName) + + expected_attributes = expected_node.attributes + actual_attributes = actual_node.attributes + self.assertEqual( + expected_attributes.length, + actual_attributes.length, + 'attribute numbers differ in element %s:\nExpected: %r\nActual: %r' + % ( + actual_node.tagName, + expected_attributes.keys(), + actual_attributes.keys(), + ), + ) + for i in range(expected_attributes.length): + expected_attr = expected_attributes.item(i) + actual_attr = actual_attributes.get(expected_attr.name) + self.assertTrue( + actual_attr is not None, + 'expected attribute %s not found in element %s' + % (expected_attr.name, actual_node.tagName), + ) + self.assertEqual( + expected_attr.value, + actual_attr.value, + ' values of attribute %s in element %s differ: %s vs %s' + % ( + expected_attr.name, + actual_node.tagName, + expected_attr.value, + actual_attr.value, + ), + ) + + expected_children = self._GetChildren(expected_node) + actual_children = self._GetChildren(actual_node) + self.assertEqual( + len(expected_children), + len(actual_children), + 'number of child elements differ in element ' + actual_node.tagName, + ) + for child_id, child in expected_children.items(): + self.assertTrue( + child_id in actual_children, + '<%s> is not in <%s> (in element %s)' + % (child_id, actual_children, actual_node.tagName), + ) + self.AssertEquivalentNodes(child, actual_children[child_id]) + + identifying_attribute = { + 'testsuites': 'name', + 'testsuite': 'name', + 'testcase': 'name', + 'failure': 'message', + 'skipped': 'message', + 'property': 'name', + } + + def _GetChildren(self, element): + """Fetches all of the child nodes of element, a DOM Element object. + + Returns them as the values of a dictionary keyed by the IDs of the children. + For <testsuites>, <testsuite>, <testcase>, and <property> elements, the ID + is the value of their "name" attribute; for <failure> elements, it is the + value of the "message" attribute; for <properties> elements, it is the value + of their parent's "name" attribute plus the literal string "properties"; + CDATA sections and non-whitespace text nodes are concatenated into a single + CDATA section with ID "detail". An exception is raised if any element other + than the above four is encountered, if two child elements with the same + identifying attributes are encountered, or if any other type of node is + encountered. + + Args: + element: DOM Element object + + Returns: + Dictionary where keys are the IDs of the children. + """ + + children = {} + for child in element.childNodes: + if child.nodeType == Node.ELEMENT_NODE: + if child.tagName == 'properties': + self.assertTrue( + child.parentNode is not None, + 'Encountered <properties> element without a parent', + ) + child_id = child.parentNode.getAttribute('name') + '-properties' + else: + self.assertTrue( + child.tagName in self.identifying_attribute, + 'Encountered unknown element <%s>' % child.tagName, + ) + child_id = child.getAttribute( + self.identifying_attribute[child.tagName] + ) + self.assertNotIn(child_id, children) + children[child_id] = child + elif child.nodeType in [Node.TEXT_NODE, Node.CDATA_SECTION_NODE]: + if 'detail' not in children: + if ( + child.nodeType == Node.CDATA_SECTION_NODE + or not child.nodeValue.isspace() + ): + children['detail'] = child.ownerDocument.createCDATASection( + child.nodeValue + ) + else: + children['detail'].nodeValue += child.nodeValue + else: + self.fail('Encountered unexpected node type %d' % child.nodeType) + return children + + def NormalizeXml(self, element): + """Normalizes XML that may change from run to run. + + Normalizes Google Test's XML output to eliminate references to transient + information that may change from run to run. + + * The "time" attribute of <testsuites>, <testsuite> and <testcase> + elements is replaced with a single asterisk, if it contains + only digit characters. + * The "timestamp" attribute of <testsuites> elements is replaced with a + single asterisk, if it contains a valid ISO8601 datetime value. + * The "type_param" attribute of <testcase> elements is replaced with a + single asterisk (if it sn non-empty) as it is the type name returned + by the compiler and is platform dependent. + * The line info reported in the first line of the "message" + attribute and CDATA section of <failure> elements is replaced with the + file's basename and a single asterisk for the line number. + * The directory names in file paths are removed. + * The stack traces are removed. + + Args: + element: DOM element to normalize + """ + + if element.tagName == 'testcase': + source_file = element.getAttributeNode('file') + if source_file: + source_file.value = re.sub(r'^.*[/\\](.*)', '\\1', source_file.value) + if element.tagName in ('testsuites', 'testsuite', 'testcase'): + timestamp = element.getAttributeNode('timestamp') + timestamp.value = re.sub( + r'^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\d$', '*', timestamp.value + ) + if element.tagName in ('testsuites', 'testsuite', 'testcase'): + time = element.getAttributeNode('time') + # The value for exact N seconds has a trailing decimal point (e.g., "10." + # instead of "10") + time.value = re.sub(r'^\d+\.(\d+)?$', '*', time.value) + type_param = element.getAttributeNode('type_param') + if type_param and type_param.value: + type_param.value = '*' + elif element.tagName == 'failure' or element.tagName == 'skipped': + source_line_pat = r'^.*[/\\](.*:)\d+\n' + # Replaces the source line information with a normalized form. + message = element.getAttributeNode('message') + message.value = re.sub(source_line_pat, '\\1*\n', message.value) + for child in element.childNodes: + if child.nodeType == Node.CDATA_SECTION_NODE: + # Replaces the source line information with a normalized form. + cdata = re.sub(source_line_pat, '\\1*\n', child.nodeValue) + # Removes the actual stack trace. + child.nodeValue = re.sub( + r'Stack trace:\n(.|\n)*', 'Stack trace:\n*', cdata + ) + for child in element.childNodes: + if child.nodeType == Node.ELEMENT_NODE: + self.NormalizeXml(child) |
