00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032 """Unit test utilities for gtest_xml_output"""
00033
00034 __author__ = 'eefacm@gmail.com (Sean Mcafee)'
00035
00036 import re
00037 from xml.dom import minidom, Node
00038
00039 import gtest_test_utils
00040
00041
00042 GTEST_OUTPUT_FLAG = '--gtest_output'
00043 GTEST_DEFAULT_OUTPUT_FILE = 'test_detail.xml'
00044
00045 class GTestXMLTestCase(gtest_test_utils.TestCase):
00046 """
00047 Base class for tests of Google Test's XML output functionality.
00048 """
00049
00050
00051 def AssertEquivalentNodes(self, expected_node, actual_node):
00052 """
00053 Asserts that actual_node (a DOM node object) is equivalent to
00054 expected_node (another DOM node object), in that either both of
00055 them are CDATA nodes and have the same value, or both are DOM
00056 elements and actual_node meets all of the following conditions:
00057
00058 * It has the same tag name as expected_node.
00059 * It has the same set of attributes as expected_node, each with
00060 the same value as the corresponding attribute of expected_node.
00061 Exceptions are any attribute named "time", which needs only be
00062 convertible to a floating-point number and any attribute named
00063 "type_param" which only has to be non-empty.
00064 * It has an equivalent set of child nodes (including elements and
00065 CDATA sections) as expected_node. Note that we ignore the
00066 order of the children as they are not guaranteed to be in any
00067 particular order.
00068 """
00069
00070 if expected_node.nodeType == Node.CDATA_SECTION_NODE:
00071 self.assertEquals(Node.CDATA_SECTION_NODE, actual_node.nodeType)
00072 self.assertEquals(expected_node.nodeValue, actual_node.nodeValue)
00073 return
00074
00075 self.assertEquals(Node.ELEMENT_NODE, actual_node.nodeType)
00076 self.assertEquals(Node.ELEMENT_NODE, expected_node.nodeType)
00077 self.assertEquals(expected_node.tagName, actual_node.tagName)
00078
00079 expected_attributes = expected_node.attributes
00080 actual_attributes = actual_node .attributes
00081 self.assertEquals(
00082 expected_attributes.length, actual_attributes.length,
00083 'attribute numbers differ in element %s:\nExpected: %r\nActual: %r' % (
00084 actual_node.tagName, expected_attributes.keys(),
00085 actual_attributes.keys()))
00086 for i in range(expected_attributes.length):
00087 expected_attr = expected_attributes.item(i)
00088 actual_attr = actual_attributes.get(expected_attr.name)
00089 self.assert_(
00090 actual_attr is not None,
00091 'expected attribute %s not found in element %s' %
00092 (expected_attr.name, actual_node.tagName))
00093 self.assertEquals(
00094 expected_attr.value, actual_attr.value,
00095 ' values of attribute %s in element %s differ: %s vs %s' %
00096 (expected_attr.name, actual_node.tagName,
00097 expected_attr.value, actual_attr.value))
00098
00099 expected_children = self._GetChildren(expected_node)
00100 actual_children = self._GetChildren(actual_node)
00101 self.assertEquals(
00102 len(expected_children), len(actual_children),
00103 'number of child elements differ in element ' + actual_node.tagName)
00104 for child_id, child in expected_children.iteritems():
00105 self.assert_(child_id in actual_children,
00106 '<%s> is not in <%s> (in element %s)' %
00107 (child_id, actual_children, actual_node.tagName))
00108 self.AssertEquivalentNodes(child, actual_children[child_id])
00109
00110 identifying_attribute = {
00111 'testsuites': 'name',
00112 'testsuite': 'name',
00113 'testcase': 'name',
00114 'failure': 'message',
00115 }
00116
00117 def _GetChildren(self, element):
00118 """
00119 Fetches all of the child nodes of element, a DOM Element object.
00120 Returns them as the values of a dictionary keyed by the IDs of the
00121 children. For <testsuites>, <testsuite> and <testcase> elements, the ID
00122 is the value of their "name" attribute; for <failure> elements, it is
00123 the value of the "message" attribute; CDATA sections and non-whitespace
00124 text nodes are concatenated into a single CDATA section with ID
00125 "detail". An exception is raised if any element other than the above
00126 four is encountered, if two child elements with the same identifying
00127 attributes are encountered, or if any other type of node is encountered.
00128 """
00129
00130 children = {}
00131 for child in element.childNodes:
00132 if child.nodeType == Node.ELEMENT_NODE:
00133 self.assert_(child.tagName in self.identifying_attribute,
00134 'Encountered unknown element <%s>' % child.tagName)
00135 childID = child.getAttribute(self.identifying_attribute[child.tagName])
00136 self.assert_(childID not in children)
00137 children[childID] = child
00138 elif child.nodeType in [Node.TEXT_NODE, Node.CDATA_SECTION_NODE]:
00139 if 'detail' not in children:
00140 if (child.nodeType == Node.CDATA_SECTION_NODE or
00141 not child.nodeValue.isspace()):
00142 children['detail'] = child.ownerDocument.createCDATASection(
00143 child.nodeValue)
00144 else:
00145 children['detail'].nodeValue += child.nodeValue
00146 else:
00147 self.fail('Encountered unexpected node type %d' % child.nodeType)
00148 return children
00149
00150 def NormalizeXml(self, element):
00151 """
00152 Normalizes Google Test's XML output to eliminate references to transient
00153 information that may change from run to run.
00154
00155 * The "time" attribute of <testsuites>, <testsuite> and <testcase>
00156 elements is replaced with a single asterisk, if it contains
00157 only digit characters.
00158 * The "timestamp" attribute of <testsuites> elements is replaced with a
00159 single asterisk, if it contains a valid ISO8601 datetime value.
00160 * The "type_param" attribute of <testcase> elements is replaced with a
00161 single asterisk (if it sn non-empty) as it is the type name returned
00162 by the compiler and is platform dependent.
00163 * The line info reported in the first line of the "message"
00164 attribute and CDATA section of <failure> elements is replaced with the
00165 file's basename and a single asterisk for the line number.
00166 * The directory names in file paths are removed.
00167 * The stack traces are removed.
00168 """
00169
00170 if element.tagName == 'testsuites':
00171 timestamp = element.getAttributeNode('timestamp')
00172 timestamp.value = re.sub(r'^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d$',
00173 '*', timestamp.value)
00174 if element.tagName in ('testsuites', 'testsuite', 'testcase'):
00175 time = element.getAttributeNode('time')
00176 time.value = re.sub(r'^\d+(\.\d+)?$', '*', time.value)
00177 type_param = element.getAttributeNode('type_param')
00178 if type_param and type_param.value:
00179 type_param.value = '*'
00180 elif element.tagName == 'failure':
00181 source_line_pat = r'^.*[/\\](.*:)\d+\n'
00182
00183 message = element.getAttributeNode('message')
00184 message.value = re.sub(source_line_pat, '\\1*\n', message.value)
00185 for child in element.childNodes:
00186 if child.nodeType == Node.CDATA_SECTION_NODE:
00187
00188 cdata = re.sub(source_line_pat, '\\1*\n', child.nodeValue)
00189
00190 child.nodeValue = re.sub(r'\nStack trace:\n(.|\n)*',
00191 '', cdata)
00192 for child in element.childNodes:
00193 if child.nodeType == Node.ELEMENT_NODE:
00194 self.NormalizeXml(child)