| #!/usr/bin/env python |
| # |
| # 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""" |
| |
| __author__ = 'eefacm@gmail.com (Sean Mcafee)' |
| |
| import re |
| from xml.dom import minidom, Node |
| |
| import gtest_test_utils |
| |
| |
| GTEST_OUTPUT_FLAG = "--gtest_output" |
| 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 (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. |
| An exception is any attribute named "time", which needs only be |
| convertible to a floating-point number. |
| * 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. |
| """ |
| |
| if expected_node.nodeType == Node.CDATA_SECTION_NODE: |
| self.assertEquals(Node.CDATA_SECTION_NODE, actual_node.nodeType) |
| self.assertEquals(expected_node.nodeValue, actual_node.nodeValue) |
| return |
| |
| self.assertEquals(Node.ELEMENT_NODE, actual_node.nodeType) |
| self.assertEquals(Node.ELEMENT_NODE, expected_node.nodeType) |
| self.assertEquals(expected_node.tagName, actual_node.tagName) |
| |
| expected_attributes = expected_node.attributes |
| actual_attributes = actual_node .attributes |
| self.assertEquals( |
| expected_attributes.length, actual_attributes.length, |
| "attribute numbers differ in element " + actual_node.tagName) |
| for i in range(expected_attributes.length): |
| expected_attr = expected_attributes.item(i) |
| actual_attr = actual_attributes.get(expected_attr.name) |
| self.assert_( |
| actual_attr is not None, |
| "expected attribute %s not found in element %s" % |
| (expected_attr.name, actual_node.tagName)) |
| self.assertEquals(expected_attr.value, actual_attr.value, |
| " values of attribute %s in element %s differ" % |
| (expected_attr.name, actual_node.tagName)) |
| |
| expected_children = self._GetChildren(expected_node) |
| actual_children = self._GetChildren(actual_node) |
| self.assertEquals( |
| len(expected_children), len(actual_children), |
| "number of child elements differ in element " + actual_node.tagName) |
| for child_id, child in expected_children.iteritems(): |
| self.assert_(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", |
| } |
| |
| 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> and <testcase> elements, the ID |
| is the value of their "name" attribute; for <failure> elements, it is |
| the value of the "message" attribute; 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. |
| """ |
| |
| children = {} |
| for child in element.childNodes: |
| if child.nodeType == Node.ELEMENT_NODE: |
| self.assert_(child.tagName in self.identifying_attribute, |
| "Encountered unknown element <%s>" % child.tagName) |
| childID = child.getAttribute(self.identifying_attribute[child.tagName]) |
| self.assert_(childID not in children) |
| children[childID] = 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 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 line number reported in the first line of the "message" |
| attribute of <failure> elements is replaced with a single asterisk. |
| * The directory names in file paths are removed. |
| * The stack traces are removed. |
| """ |
| |
| if element.tagName in ("testsuites", "testsuite", "testcase"): |
| time = element.getAttributeNode("time") |
| time.value = re.sub(r"^\d+(\.\d+)?$", "*", time.value) |
| elif element.tagName == "failure": |
| for child in element.childNodes: |
| if child.nodeType == Node.CDATA_SECTION_NODE: |
| # Removes the source line number. |
| cdata = re.sub(r"^.*[/\\](.*:)\d+\n", "\\1*\n", child.nodeValue) |
| # Removes the actual stack trace. |
| child.nodeValue = re.sub(r"\nStack trace:\n(.|\n)*", |
| "", cdata) |
| for child in element.childNodes: |
| if child.nodeType == Node.ELEMENT_NODE: |
| self.NormalizeXml(child) |