]>
Commit | Line | Data |
---|---|---|
31f18b77 FG |
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@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 | Exceptions are any attribute named "time", which needs only be | |
62 | convertible to a floating-point number and any attribute named | |
63 | "type_param" which only has to be non-empty. | |
64 | * It has an equivalent set of child nodes (including elements and | |
65 | CDATA sections) as expected_node. Note that we ignore the | |
66 | order of the children as they are not guaranteed to be in any | |
67 | particular order. | |
68 | """ | |
69 | ||
70 | if expected_node.nodeType == Node.CDATA_SECTION_NODE: | |
71 | self.assertEquals(Node.CDATA_SECTION_NODE, actual_node.nodeType) | |
72 | self.assertEquals(expected_node.nodeValue, actual_node.nodeValue) | |
73 | return | |
74 | ||
75 | self.assertEquals(Node.ELEMENT_NODE, actual_node.nodeType) | |
76 | self.assertEquals(Node.ELEMENT_NODE, expected_node.nodeType) | |
77 | self.assertEquals(expected_node.tagName, actual_node.tagName) | |
78 | ||
79 | expected_attributes = expected_node.attributes | |
80 | actual_attributes = actual_node .attributes | |
81 | self.assertEquals( | |
82 | expected_attributes.length, actual_attributes.length, | |
83 | 'attribute numbers differ in element %s:\nExpected: %r\nActual: %r' % ( | |
84 | actual_node.tagName, expected_attributes.keys(), | |
85 | actual_attributes.keys())) | |
86 | for i in range(expected_attributes.length): | |
87 | expected_attr = expected_attributes.item(i) | |
88 | actual_attr = actual_attributes.get(expected_attr.name) | |
89 | self.assert_( | |
90 | actual_attr is not None, | |
91 | 'expected attribute %s not found in element %s' % | |
92 | (expected_attr.name, actual_node.tagName)) | |
93 | self.assertEquals( | |
94 | expected_attr.value, actual_attr.value, | |
95 | ' values of attribute %s in element %s differ: %s vs %s' % | |
96 | (expected_attr.name, actual_node.tagName, | |
97 | expected_attr.value, actual_attr.value)) | |
98 | ||
99 | expected_children = self._GetChildren(expected_node) | |
100 | actual_children = self._GetChildren(actual_node) | |
101 | self.assertEquals( | |
102 | len(expected_children), len(actual_children), | |
103 | 'number of child elements differ in element ' + actual_node.tagName) | |
104 | for child_id, child in expected_children.items(): | |
105 | self.assert_(child_id in actual_children, | |
106 | '<%s> is not in <%s> (in element %s)' % | |
107 | (child_id, actual_children, actual_node.tagName)) | |
108 | self.AssertEquivalentNodes(child, actual_children[child_id]) | |
109 | ||
110 | identifying_attribute = { | |
111 | 'testsuites': 'name', | |
112 | 'testsuite': 'name', | |
113 | 'testcase': 'name', | |
114 | 'failure': 'message', | |
115 | } | |
116 | ||
117 | def _GetChildren(self, element): | |
118 | """ | |
119 | Fetches all of the child nodes of element, a DOM Element object. | |
120 | Returns them as the values of a dictionary keyed by the IDs of the | |
121 | children. For <testsuites>, <testsuite> and <testcase> elements, the ID | |
122 | is the value of their "name" attribute; for <failure> elements, it is | |
123 | the value of the "message" attribute; CDATA sections and non-whitespace | |
124 | text nodes are concatenated into a single CDATA section with ID | |
125 | "detail". An exception is raised if any element other than the above | |
126 | four is encountered, if two child elements with the same identifying | |
127 | attributes are encountered, or if any other type of node is encountered. | |
128 | """ | |
129 | ||
130 | children = {} | |
131 | for child in element.childNodes: | |
132 | if child.nodeType == Node.ELEMENT_NODE: | |
133 | self.assert_(child.tagName in self.identifying_attribute, | |
134 | 'Encountered unknown element <%s>' % child.tagName) | |
135 | childID = child.getAttribute(self.identifying_attribute[child.tagName]) | |
136 | self.assert_(childID not in children) | |
137 | children[childID] = child | |
138 | elif child.nodeType in [Node.TEXT_NODE, Node.CDATA_SECTION_NODE]: | |
139 | if 'detail' not in children: | |
140 | if (child.nodeType == Node.CDATA_SECTION_NODE or | |
141 | not child.nodeValue.isspace()): | |
142 | children['detail'] = child.ownerDocument.createCDATASection( | |
143 | child.nodeValue) | |
144 | else: | |
145 | children['detail'].nodeValue += child.nodeValue | |
146 | else: | |
147 | self.fail('Encountered unexpected node type %d' % child.nodeType) | |
148 | return children | |
149 | ||
150 | def NormalizeXml(self, element): | |
151 | """ | |
152 | Normalizes Google Test's XML output to eliminate references to transient | |
153 | information that may change from run to run. | |
154 | ||
155 | * The "time" attribute of <testsuites>, <testsuite> and <testcase> | |
156 | elements is replaced with a single asterisk, if it contains | |
157 | only digit characters. | |
158 | * The "timestamp" attribute of <testsuites> elements is replaced with a | |
159 | single asterisk, if it contains a valid ISO8601 datetime value. | |
160 | * The "type_param" attribute of <testcase> elements is replaced with a | |
161 | single asterisk (if it sn non-empty) as it is the type name returned | |
162 | by the compiler and is platform dependent. | |
163 | * The line info reported in the first line of the "message" | |
164 | attribute and CDATA section of <failure> elements is replaced with the | |
165 | file's basename and a single asterisk for the line number. | |
166 | * The directory names in file paths are removed. | |
167 | * The stack traces are removed. | |
168 | """ | |
169 | ||
170 | if element.tagName == 'testsuites': | |
171 | timestamp = element.getAttributeNode('timestamp') | |
172 | timestamp.value = re.sub(r'^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d$', | |
173 | '*', timestamp.value) | |
174 | if element.tagName in ('testsuites', 'testsuite', 'testcase'): | |
175 | time = element.getAttributeNode('time') | |
176 | time.value = re.sub(r'^\d+(\.\d+)?$', '*', time.value) | |
177 | type_param = element.getAttributeNode('type_param') | |
178 | if type_param and type_param.value: | |
179 | type_param.value = '*' | |
180 | elif element.tagName == 'failure': | |
181 | source_line_pat = r'^.*[/\\](.*:)\d+\n' | |
182 | # Replaces the source line information with a normalized form. | |
183 | message = element.getAttributeNode('message') | |
184 | message.value = re.sub(source_line_pat, '\\1*\n', message.value) | |
185 | for child in element.childNodes: | |
186 | if child.nodeType == Node.CDATA_SECTION_NODE: | |
187 | # Replaces the source line information with a normalized form. | |
188 | cdata = re.sub(source_line_pat, '\\1*\n', child.nodeValue) | |
189 | # Removes the actual stack trace. | |
190 | child.nodeValue = re.sub(r'\nStack trace:\n(.|\n)*', | |
191 | '', cdata) | |
192 | for child in element.childNodes: | |
193 | if child.nodeType == Node.ELEMENT_NODE: | |
194 | self.NormalizeXml(child) |