You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							194 lines
						
					
					
						
							8.7 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							194 lines
						
					
					
						
							8.7 KiB
						
					
					
				| #!/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. | |
|        Exceptions are any attribute named "time", which needs only be | |
|        convertible to a floating-point number and any attribute named | |
|        "type_param" which only has to be non-empty. | |
|     *  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 %s:\nExpected: %r\nActual: %r' % ( | |
|             actual_node.tagName, expected_attributes.keys(), | |
|             actual_attributes.keys())) | |
|     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: %s vs %s' % | |
|           (expected_attr.name, actual_node.tagName, | |
|            expected_attr.value, actual_attr.value)) | |
| 
 | |
|     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 "timestamp" attribute of <testsuites> elements is replaced with a | |
|        single asterisk, if it contains a valid ISO8601 datetime value. | |
|     *  The "type_param" attribute of <testcase> elements is replaced with a | |
|        single asterisk (if it sn non-empty) as it is the type name returned | |
|        by the compiler and is platform dependent. | |
|     *  The line info reported in the first line of the "message" | |
|        attribute and CDATA section of <failure> elements is replaced with the | |
|        file's basename and a single asterisk for the line number. | |
|     *  The directory names in file paths are removed. | |
|     *  The stack traces are removed. | |
|     """ | |
| 
 | |
|     if element.tagName == 'testsuites': | |
|       timestamp = element.getAttributeNode('timestamp') | |
|       timestamp.value = re.sub(r'^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d$', | |
|                                '*', timestamp.value) | |
|     if element.tagName in ('testsuites', 'testsuite', 'testcase'): | |
|       time = element.getAttributeNode('time') | |
|       time.value = re.sub(r'^\d+(\.\d+)?$', '*', time.value) | |
|       type_param = element.getAttributeNode('type_param') | |
|       if type_param and type_param.value: | |
|         type_param.value = '*' | |
|     elif element.tagName == 'failure': | |
|       source_line_pat = r'^.*[/\\](.*:)\d+\n' | |
|       # Replaces the source line information with a normalized form. | |
|       message = element.getAttributeNode('message') | |
|       message.value = re.sub(source_line_pat, '\\1*\n', message.value) | |
|       for child in element.childNodes: | |
|         if child.nodeType == Node.CDATA_SECTION_NODE: | |
|           # Replaces the source line information with a normalized form. | |
|           cdata = re.sub(source_line_pat, '\\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)
 |