OLD | NEW |
| (Empty) |
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 " + actual_node.tagName) | |
84 for i in range(expected_attributes.length): | |
85 expected_attr = expected_attributes.item(i) | |
86 actual_attr = actual_attributes.get(expected_attr.name) | |
87 self.assert_( | |
88 actual_attr is not None, | |
89 "expected attribute %s not found in element %s" % | |
90 (expected_attr.name, actual_node.tagName)) | |
91 self.assertEquals(expected_attr.value, actual_attr.value, | |
92 " values of attribute %s in element %s differ" % | |
93 (expected_attr.name, actual_node.tagName)) | |
94 | |
95 expected_children = self._GetChildren(expected_node) | |
96 actual_children = self._GetChildren(actual_node) | |
97 self.assertEquals( | |
98 len(expected_children), len(actual_children), | |
99 "number of child elements differ in element " + actual_node.tagName) | |
100 for child_id, child in expected_children.iteritems(): | |
101 self.assert_(child_id in actual_children, | |
102 '<%s> is not in <%s> (in element %s)' % | |
103 (child_id, actual_children, actual_node.tagName)) | |
104 self.AssertEquivalentNodes(child, actual_children[child_id]) | |
105 | |
106 identifying_attribute = { | |
107 "testsuites": "name", | |
108 "testsuite": "name", | |
109 "testcase": "name", | |
110 "failure": "message", | |
111 } | |
112 | |
113 def _GetChildren(self, element): | |
114 """ | |
115 Fetches all of the child nodes of element, a DOM Element object. | |
116 Returns them as the values of a dictionary keyed by the IDs of the | |
117 children. For <testsuites>, <testsuite> and <testcase> elements, the ID | |
118 is the value of their "name" attribute; for <failure> elements, it is | |
119 the value of the "message" attribute; CDATA sections and non-whitespace | |
120 text nodes are concatenated into a single CDATA section with ID | |
121 "detail". An exception is raised if any element other than the above | |
122 four is encountered, if two child elements with the same identifying | |
123 attributes are encountered, or if any other type of node is encountered. | |
124 """ | |
125 | |
126 children = {} | |
127 for child in element.childNodes: | |
128 if child.nodeType == Node.ELEMENT_NODE: | |
129 self.assert_(child.tagName in self.identifying_attribute, | |
130 "Encountered unknown element <%s>" % child.tagName) | |
131 childID = child.getAttribute(self.identifying_attribute[child.tagName]) | |
132 self.assert_(childID not in children) | |
133 children[childID] = child | |
134 elif child.nodeType in [Node.TEXT_NODE, Node.CDATA_SECTION_NODE]: | |
135 if "detail" not in children: | |
136 if (child.nodeType == Node.CDATA_SECTION_NODE or | |
137 not child.nodeValue.isspace()): | |
138 children["detail"] = child.ownerDocument.createCDATASection( | |
139 child.nodeValue) | |
140 else: | |
141 children["detail"].nodeValue += child.nodeValue | |
142 else: | |
143 self.fail("Encountered unexpected node type %d" % child.nodeType) | |
144 return children | |
145 | |
146 def NormalizeXml(self, element): | |
147 """ | |
148 Normalizes Google Test's XML output to eliminate references to transient | |
149 information that may change from run to run. | |
150 | |
151 * The "time" attribute of <testsuites>, <testsuite> and <testcase> | |
152 elements is replaced with a single asterisk, if it contains | |
153 only digit characters. | |
154 * The "type_param" attribute of <testcase> elements is replaced with a | |
155 single asterisk (if it sn non-empty) as it is the type name returned | |
156 by the compiler and is platform dependent. | |
157 * The line number reported in the first line of the "message" | |
158 attribute of <failure> elements is replaced with a single asterisk. | |
159 * The directory names in file paths are removed. | |
160 * The stack traces are removed. | |
161 """ | |
162 | |
163 if element.tagName in ("testsuites", "testsuite", "testcase"): | |
164 time = element.getAttributeNode("time") | |
165 time.value = re.sub(r"^\d+(\.\d+)?$", "*", time.value) | |
166 type_param = element.getAttributeNode("type_param") | |
167 if type_param and type_param.value: | |
168 type_param.value = "*" | |
169 elif element.tagName == "failure": | |
170 for child in element.childNodes: | |
171 if child.nodeType == Node.CDATA_SECTION_NODE: | |
172 # Removes the source line number. | |
173 cdata = re.sub(r"^.*[/\\](.*:)\d+\n", "\\1*\n", child.nodeValue) | |
174 # Removes the actual stack trace. | |
175 child.nodeValue = re.sub(r"\nStack trace:\n(.|\n)*", | |
176 "", cdata) | |
177 for child in element.childNodes: | |
178 if child.nodeType == Node.ELEMENT_NODE: | |
179 self.NormalizeXml(child) | |
OLD | NEW |