Home | History | Annotate | Download | only in test
      1 #!/usr/bin/env python
      2 #
      3 # Copyright 2006, Google Inc.
      4 # All rights reserved.
      5 #
      6 # Redistribution and use in source and binary forms, with or without
      7 # modification, are permitted provided that the following conditions are
      8 # met:
      9 #
     10 #     * Redistributions of source code must retain the above copyright
     11 # notice, this list of conditions and the following disclaimer.
     12 #     * Redistributions in binary form must reproduce the above
     13 # copyright notice, this list of conditions and the following disclaimer
     14 # in the documentation and/or other materials provided with the
     15 # distribution.
     16 #     * Neither the name of Google Inc. nor the names of its
     17 # contributors may be used to endorse or promote products derived from
     18 # this software without specific prior written permission.
     19 #
     20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     23 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     24 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     25 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     26 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     27 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     28 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     29 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     30 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31 
     32 """Unit test utilities for gtest_xml_output"""
     33 
     34 __author__ = 'eefacm (at] gmail.com (Sean Mcafee)'
     35 
     36 import re
     37 from xml.dom import minidom, Node
     38 
     39 import gtest_test_utils
     40 
     41 
     42 GTEST_OUTPUT_FLAG         = "--gtest_output"
     43 GTEST_DEFAULT_OUTPUT_FILE = "test_detail.xml"
     44 
     45 class GTestXMLTestCase(gtest_test_utils.TestCase):
     46   """
     47   Base class for tests of Google Test's XML output functionality.
     48   """
     49 
     50 
     51   def AssertEquivalentNodes(self, expected_node, actual_node):
     52     """
     53     Asserts that actual_node (a DOM node object) is equivalent to
     54     expected_node (another DOM node object), in that either both of
     55     them are CDATA nodes and have the same value, or both are DOM
     56     elements and actual_node meets all of the following conditions:
     57 
     58     *  It has the same tag name as expected_node.
     59     *  It has the same set of attributes as expected_node, each with
     60        the same value as the corresponding attribute of expected_node.
     61        An exception is any attribute named "time", which needs only be
     62        convertible to a floating-point number.
     63     *  It has an equivalent set of child nodes (including elements and
     64        CDATA sections) as expected_node.  Note that we ignore the
     65        order of the children as they are not guaranteed to be in any
     66        particular order.
     67     """
     68 
     69     if expected_node.nodeType == Node.CDATA_SECTION_NODE:
     70       self.assertEquals(Node.CDATA_SECTION_NODE, actual_node.nodeType)
     71       self.assertEquals(expected_node.nodeValue, actual_node.nodeValue)
     72       return
     73 
     74     self.assertEquals(Node.ELEMENT_NODE, actual_node.nodeType)
     75     self.assertEquals(Node.ELEMENT_NODE, expected_node.nodeType)
     76     self.assertEquals(expected_node.tagName, actual_node.tagName)
     77 
     78     expected_attributes = expected_node.attributes
     79     actual_attributes   = actual_node  .attributes
     80     self.assertEquals(
     81         expected_attributes.length, actual_attributes.length,
     82         "attribute numbers differ in element " + actual_node.tagName)
     83     for i in range(expected_attributes.length):
     84       expected_attr = expected_attributes.item(i)
     85       actual_attr   = actual_attributes.get(expected_attr.name)
     86       self.assert_(
     87           actual_attr is not None,
     88           "expected attribute %s not found in element %s" %
     89           (expected_attr.name, actual_node.tagName))
     90       self.assertEquals(expected_attr.value, actual_attr.value,
     91                         " values of attribute %s in element %s differ" %
     92                         (expected_attr.name, actual_node.tagName))
     93 
     94     expected_children = self._GetChildren(expected_node)
     95     actual_children = self._GetChildren(actual_node)
     96     self.assertEquals(
     97         len(expected_children), len(actual_children),
     98         "number of child elements differ in element " + actual_node.tagName)
     99     for child_id, child in expected_children.iteritems():
    100       self.assert_(child_id in actual_children,
    101                    '<%s> is not in <%s> (in element %s)' %
    102                    (child_id, actual_children, actual_node.tagName))
    103       self.AssertEquivalentNodes(child, actual_children[child_id])
    104 
    105   identifying_attribute = {
    106     "testsuites": "name",
    107     "testsuite": "name",
    108     "testcase":  "name",
    109     "failure":   "message",
    110     }
    111 
    112   def _GetChildren(self, element):
    113     """
    114     Fetches all of the child nodes of element, a DOM Element object.
    115     Returns them as the values of a dictionary keyed by the IDs of the
    116     children.  For <testsuites>, <testsuite> and <testcase> elements, the ID
    117     is the value of their "name" attribute; for <failure> elements, it is
    118     the value of the "message" attribute; CDATA sections and non-whitespace
    119     text nodes are concatenated into a single CDATA section with ID
    120     "detail".  An exception is raised if any element other than the above
    121     four is encountered, if two child elements with the same identifying
    122     attributes are encountered, or if any other type of node is encountered.
    123     """
    124 
    125     children = {}
    126     for child in element.childNodes:
    127       if child.nodeType == Node.ELEMENT_NODE:
    128         self.assert_(child.tagName in self.identifying_attribute,
    129                      "Encountered unknown element <%s>" % child.tagName)
    130         childID = child.getAttribute(self.identifying_attribute[child.tagName])
    131         self.assert_(childID not in children)
    132         children[childID] = child
    133       elif child.nodeType in [Node.TEXT_NODE, Node.CDATA_SECTION_NODE]:
    134         if "detail" not in children:
    135           if (child.nodeType == Node.CDATA_SECTION_NODE or
    136               not child.nodeValue.isspace()):
    137             children["detail"] = child.ownerDocument.createCDATASection(
    138                 child.nodeValue)
    139         else:
    140           children["detail"].nodeValue += child.nodeValue
    141       else:
    142         self.fail("Encountered unexpected node type %d" % child.nodeType)
    143     return children
    144 
    145   def NormalizeXml(self, element):
    146     """
    147     Normalizes Google Test's XML output to eliminate references to transient
    148     information that may change from run to run.
    149 
    150     *  The "time" attribute of <testsuites>, <testsuite> and <testcase>
    151        elements is replaced with a single asterisk, if it contains
    152        only digit characters.
    153     *  The line number reported in the first line of the "message"
    154        attribute of <failure> elements is replaced with a single asterisk.
    155     *  The directory names in file paths are removed.
    156     *  The stack traces are removed.
    157     """
    158 
    159     if element.tagName in ("testsuites", "testsuite", "testcase"):
    160       time = element.getAttributeNode("time")
    161       time.value = re.sub(r"^\d+(\.\d+)?$", "*", time.value)
    162     elif element.tagName == "failure":
    163       for child in element.childNodes:
    164         if child.nodeType == Node.CDATA_SECTION_NODE:
    165           # Removes the source line number.
    166           cdata = re.sub(r"^.*[/\\](.*:)\d+\n", "\\1*\n", child.nodeValue)
    167           # Removes the actual stack trace.
    168           child.nodeValue = re.sub(r"\nStack trace:\n(.|\n)*",
    169                                    "", cdata)
    170     for child in element.childNodes:
    171       if child.nodeType == Node.ELEMENT_NODE:
    172         self.NormalizeXml(child)
    173