113481Sgiacomo.travaglini@arm.com#!/usr/bin/env python
213481Sgiacomo.travaglini@arm.com#
313481Sgiacomo.travaglini@arm.com# Copyright 2006, Google Inc.
413481Sgiacomo.travaglini@arm.com# All rights reserved.
513481Sgiacomo.travaglini@arm.com#
613481Sgiacomo.travaglini@arm.com# Redistribution and use in source and binary forms, with or without
713481Sgiacomo.travaglini@arm.com# modification, are permitted provided that the following conditions are
813481Sgiacomo.travaglini@arm.com# met:
913481Sgiacomo.travaglini@arm.com#
1013481Sgiacomo.travaglini@arm.com#     * Redistributions of source code must retain the above copyright
1113481Sgiacomo.travaglini@arm.com# notice, this list of conditions and the following disclaimer.
1213481Sgiacomo.travaglini@arm.com#     * Redistributions in binary form must reproduce the above
1313481Sgiacomo.travaglini@arm.com# copyright notice, this list of conditions and the following disclaimer
1413481Sgiacomo.travaglini@arm.com# in the documentation and/or other materials provided with the
1513481Sgiacomo.travaglini@arm.com# distribution.
1613481Sgiacomo.travaglini@arm.com#     * Neither the name of Google Inc. nor the names of its
1713481Sgiacomo.travaglini@arm.com# contributors may be used to endorse or promote products derived from
1813481Sgiacomo.travaglini@arm.com# this software without specific prior written permission.
1913481Sgiacomo.travaglini@arm.com#
2013481Sgiacomo.travaglini@arm.com# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
2113481Sgiacomo.travaglini@arm.com# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2213481Sgiacomo.travaglini@arm.com# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2313481Sgiacomo.travaglini@arm.com# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
2413481Sgiacomo.travaglini@arm.com# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2513481Sgiacomo.travaglini@arm.com# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2613481Sgiacomo.travaglini@arm.com# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2713481Sgiacomo.travaglini@arm.com# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2813481Sgiacomo.travaglini@arm.com# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2913481Sgiacomo.travaglini@arm.com# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3013481Sgiacomo.travaglini@arm.com# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3113481Sgiacomo.travaglini@arm.com
3213481Sgiacomo.travaglini@arm.com"""Unit test utilities for gtest_xml_output"""
3313481Sgiacomo.travaglini@arm.com
3413481Sgiacomo.travaglini@arm.com__author__ = 'eefacm@gmail.com (Sean Mcafee)'
3513481Sgiacomo.travaglini@arm.com
3613481Sgiacomo.travaglini@arm.comimport re
3713481Sgiacomo.travaglini@arm.comfrom xml.dom import minidom, Node
3813481Sgiacomo.travaglini@arm.com
3913481Sgiacomo.travaglini@arm.comimport gtest_test_utils
4013481Sgiacomo.travaglini@arm.com
4113481Sgiacomo.travaglini@arm.com
4213481Sgiacomo.travaglini@arm.comGTEST_OUTPUT_FLAG         = '--gtest_output'
4313481Sgiacomo.travaglini@arm.comGTEST_DEFAULT_OUTPUT_FILE = 'test_detail.xml'
4413481Sgiacomo.travaglini@arm.com
4513481Sgiacomo.travaglini@arm.comclass GTestXMLTestCase(gtest_test_utils.TestCase):
4613481Sgiacomo.travaglini@arm.com  """
4713481Sgiacomo.travaglini@arm.com  Base class for tests of Google Test's XML output functionality.
4813481Sgiacomo.travaglini@arm.com  """
4913481Sgiacomo.travaglini@arm.com
5013481Sgiacomo.travaglini@arm.com
5113481Sgiacomo.travaglini@arm.com  def AssertEquivalentNodes(self, expected_node, actual_node):
5213481Sgiacomo.travaglini@arm.com    """
5313481Sgiacomo.travaglini@arm.com    Asserts that actual_node (a DOM node object) is equivalent to
5413481Sgiacomo.travaglini@arm.com    expected_node (another DOM node object), in that either both of
5513481Sgiacomo.travaglini@arm.com    them are CDATA nodes and have the same value, or both are DOM
5613481Sgiacomo.travaglini@arm.com    elements and actual_node meets all of the following conditions:
5713481Sgiacomo.travaglini@arm.com
5813481Sgiacomo.travaglini@arm.com    *  It has the same tag name as expected_node.
5913481Sgiacomo.travaglini@arm.com    *  It has the same set of attributes as expected_node, each with
6013481Sgiacomo.travaglini@arm.com       the same value as the corresponding attribute of expected_node.
6113481Sgiacomo.travaglini@arm.com       Exceptions are any attribute named "time", which needs only be
6213481Sgiacomo.travaglini@arm.com       convertible to a floating-point number and any attribute named
6313481Sgiacomo.travaglini@arm.com       "type_param" which only has to be non-empty.
6413481Sgiacomo.travaglini@arm.com    *  It has an equivalent set of child nodes (including elements and
6513481Sgiacomo.travaglini@arm.com       CDATA sections) as expected_node.  Note that we ignore the
6613481Sgiacomo.travaglini@arm.com       order of the children as they are not guaranteed to be in any
6713481Sgiacomo.travaglini@arm.com       particular order.
6813481Sgiacomo.travaglini@arm.com    """
6913481Sgiacomo.travaglini@arm.com
7013481Sgiacomo.travaglini@arm.com    if expected_node.nodeType == Node.CDATA_SECTION_NODE:
7113481Sgiacomo.travaglini@arm.com      self.assertEquals(Node.CDATA_SECTION_NODE, actual_node.nodeType)
7213481Sgiacomo.travaglini@arm.com      self.assertEquals(expected_node.nodeValue, actual_node.nodeValue)
7313481Sgiacomo.travaglini@arm.com      return
7413481Sgiacomo.travaglini@arm.com
7513481Sgiacomo.travaglini@arm.com    self.assertEquals(Node.ELEMENT_NODE, actual_node.nodeType)
7613481Sgiacomo.travaglini@arm.com    self.assertEquals(Node.ELEMENT_NODE, expected_node.nodeType)
7713481Sgiacomo.travaglini@arm.com    self.assertEquals(expected_node.tagName, actual_node.tagName)
7813481Sgiacomo.travaglini@arm.com
7913481Sgiacomo.travaglini@arm.com    expected_attributes = expected_node.attributes
8013481Sgiacomo.travaglini@arm.com    actual_attributes   = actual_node  .attributes
8113481Sgiacomo.travaglini@arm.com    self.assertEquals(
8213481Sgiacomo.travaglini@arm.com        expected_attributes.length, actual_attributes.length,
8313481Sgiacomo.travaglini@arm.com        'attribute numbers differ in element %s:\nExpected: %r\nActual: %r' % (
8413481Sgiacomo.travaglini@arm.com            actual_node.tagName, expected_attributes.keys(),
8513481Sgiacomo.travaglini@arm.com            actual_attributes.keys()))
8613481Sgiacomo.travaglini@arm.com    for i in range(expected_attributes.length):
8713481Sgiacomo.travaglini@arm.com      expected_attr = expected_attributes.item(i)
8813481Sgiacomo.travaglini@arm.com      actual_attr   = actual_attributes.get(expected_attr.name)
8913481Sgiacomo.travaglini@arm.com      self.assert_(
9013481Sgiacomo.travaglini@arm.com          actual_attr is not None,
9113481Sgiacomo.travaglini@arm.com          'expected attribute %s not found in element %s' %
9213481Sgiacomo.travaglini@arm.com          (expected_attr.name, actual_node.tagName))
9313481Sgiacomo.travaglini@arm.com      self.assertEquals(
9413481Sgiacomo.travaglini@arm.com          expected_attr.value, actual_attr.value,
9513481Sgiacomo.travaglini@arm.com          ' values of attribute %s in element %s differ: %s vs %s' %
9613481Sgiacomo.travaglini@arm.com          (expected_attr.name, actual_node.tagName,
9713481Sgiacomo.travaglini@arm.com           expected_attr.value, actual_attr.value))
9813481Sgiacomo.travaglini@arm.com
9913481Sgiacomo.travaglini@arm.com    expected_children = self._GetChildren(expected_node)
10013481Sgiacomo.travaglini@arm.com    actual_children = self._GetChildren(actual_node)
10113481Sgiacomo.travaglini@arm.com    self.assertEquals(
10213481Sgiacomo.travaglini@arm.com        len(expected_children), len(actual_children),
10313481Sgiacomo.travaglini@arm.com        'number of child elements differ in element ' + actual_node.tagName)
10413481Sgiacomo.travaglini@arm.com    for child_id, child in expected_children.items():
10513481Sgiacomo.travaglini@arm.com      self.assert_(child_id in actual_children,
10613481Sgiacomo.travaglini@arm.com                   '<%s> is not in <%s> (in element %s)' %
10713481Sgiacomo.travaglini@arm.com                   (child_id, actual_children, actual_node.tagName))
10813481Sgiacomo.travaglini@arm.com      self.AssertEquivalentNodes(child, actual_children[child_id])
10913481Sgiacomo.travaglini@arm.com
11013481Sgiacomo.travaglini@arm.com  identifying_attribute = {
11113481Sgiacomo.travaglini@arm.com    'testsuites': 'name',
11213481Sgiacomo.travaglini@arm.com    'testsuite': 'name',
11313481Sgiacomo.travaglini@arm.com    'testcase':  'name',
11413481Sgiacomo.travaglini@arm.com    'failure':   'message',
11513481Sgiacomo.travaglini@arm.com    }
11613481Sgiacomo.travaglini@arm.com
11713481Sgiacomo.travaglini@arm.com  def _GetChildren(self, element):
11813481Sgiacomo.travaglini@arm.com    """
11913481Sgiacomo.travaglini@arm.com    Fetches all of the child nodes of element, a DOM Element object.
12013481Sgiacomo.travaglini@arm.com    Returns them as the values of a dictionary keyed by the IDs of the
12113481Sgiacomo.travaglini@arm.com    children.  For <testsuites>, <testsuite> and <testcase> elements, the ID
12213481Sgiacomo.travaglini@arm.com    is the value of their "name" attribute; for <failure> elements, it is
12313481Sgiacomo.travaglini@arm.com    the value of the "message" attribute; CDATA sections and non-whitespace
12413481Sgiacomo.travaglini@arm.com    text nodes are concatenated into a single CDATA section with ID
12513481Sgiacomo.travaglini@arm.com    "detail".  An exception is raised if any element other than the above
12613481Sgiacomo.travaglini@arm.com    four is encountered, if two child elements with the same identifying
12713481Sgiacomo.travaglini@arm.com    attributes are encountered, or if any other type of node is encountered.
12813481Sgiacomo.travaglini@arm.com    """
12913481Sgiacomo.travaglini@arm.com
13013481Sgiacomo.travaglini@arm.com    children = {}
13113481Sgiacomo.travaglini@arm.com    for child in element.childNodes:
13213481Sgiacomo.travaglini@arm.com      if child.nodeType == Node.ELEMENT_NODE:
13313481Sgiacomo.travaglini@arm.com        self.assert_(child.tagName in self.identifying_attribute,
13413481Sgiacomo.travaglini@arm.com                     'Encountered unknown element <%s>' % child.tagName)
13513481Sgiacomo.travaglini@arm.com        childID = child.getAttribute(self.identifying_attribute[child.tagName])
13613481Sgiacomo.travaglini@arm.com        self.assert_(childID not in children)
13713481Sgiacomo.travaglini@arm.com        children[childID] = child
13813481Sgiacomo.travaglini@arm.com      elif child.nodeType in [Node.TEXT_NODE, Node.CDATA_SECTION_NODE]:
13913481Sgiacomo.travaglini@arm.com        if 'detail' not in children:
14013481Sgiacomo.travaglini@arm.com          if (child.nodeType == Node.CDATA_SECTION_NODE or
14113481Sgiacomo.travaglini@arm.com              not child.nodeValue.isspace()):
14213481Sgiacomo.travaglini@arm.com            children['detail'] = child.ownerDocument.createCDATASection(
14313481Sgiacomo.travaglini@arm.com                child.nodeValue)
14413481Sgiacomo.travaglini@arm.com        else:
14513481Sgiacomo.travaglini@arm.com          children['detail'].nodeValue += child.nodeValue
14613481Sgiacomo.travaglini@arm.com      else:
14713481Sgiacomo.travaglini@arm.com        self.fail('Encountered unexpected node type %d' % child.nodeType)
14813481Sgiacomo.travaglini@arm.com    return children
14913481Sgiacomo.travaglini@arm.com
15013481Sgiacomo.travaglini@arm.com  def NormalizeXml(self, element):
15113481Sgiacomo.travaglini@arm.com    """
15213481Sgiacomo.travaglini@arm.com    Normalizes Google Test's XML output to eliminate references to transient
15313481Sgiacomo.travaglini@arm.com    information that may change from run to run.
15413481Sgiacomo.travaglini@arm.com
15513481Sgiacomo.travaglini@arm.com    *  The "time" attribute of <testsuites>, <testsuite> and <testcase>
15613481Sgiacomo.travaglini@arm.com       elements is replaced with a single asterisk, if it contains
15713481Sgiacomo.travaglini@arm.com       only digit characters.
15813481Sgiacomo.travaglini@arm.com    *  The "timestamp" attribute of <testsuites> elements is replaced with a
15913481Sgiacomo.travaglini@arm.com       single asterisk, if it contains a valid ISO8601 datetime value.
16013481Sgiacomo.travaglini@arm.com    *  The "type_param" attribute of <testcase> elements is replaced with a
16113481Sgiacomo.travaglini@arm.com       single asterisk (if it sn non-empty) as it is the type name returned
16213481Sgiacomo.travaglini@arm.com       by the compiler and is platform dependent.
16313481Sgiacomo.travaglini@arm.com    *  The line info reported in the first line of the "message"
16413481Sgiacomo.travaglini@arm.com       attribute and CDATA section of <failure> elements is replaced with the
16513481Sgiacomo.travaglini@arm.com       file's basename and a single asterisk for the line number.
16613481Sgiacomo.travaglini@arm.com    *  The directory names in file paths are removed.
16713481Sgiacomo.travaglini@arm.com    *  The stack traces are removed.
16813481Sgiacomo.travaglini@arm.com    """
16913481Sgiacomo.travaglini@arm.com
17013481Sgiacomo.travaglini@arm.com    if element.tagName == 'testsuites':
17113481Sgiacomo.travaglini@arm.com      timestamp = element.getAttributeNode('timestamp')
17213481Sgiacomo.travaglini@arm.com      timestamp.value = re.sub(r'^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d$',
17313481Sgiacomo.travaglini@arm.com                               '*', timestamp.value)
17413481Sgiacomo.travaglini@arm.com    if element.tagName in ('testsuites', 'testsuite', 'testcase'):
17513481Sgiacomo.travaglini@arm.com      time = element.getAttributeNode('time')
17613481Sgiacomo.travaglini@arm.com      time.value = re.sub(r'^\d+(\.\d+)?$', '*', time.value)
17713481Sgiacomo.travaglini@arm.com      type_param = element.getAttributeNode('type_param')
17813481Sgiacomo.travaglini@arm.com      if type_param and type_param.value:
17913481Sgiacomo.travaglini@arm.com        type_param.value = '*'
18013481Sgiacomo.travaglini@arm.com    elif element.tagName == 'failure':
18113481Sgiacomo.travaglini@arm.com      source_line_pat = r'^.*[/\\](.*:)\d+\n'
18213481Sgiacomo.travaglini@arm.com      # Replaces the source line information with a normalized form.
18313481Sgiacomo.travaglini@arm.com      message = element.getAttributeNode('message')
18413481Sgiacomo.travaglini@arm.com      message.value = re.sub(source_line_pat, '\\1*\n', message.value)
18513481Sgiacomo.travaglini@arm.com      for child in element.childNodes:
18613481Sgiacomo.travaglini@arm.com        if child.nodeType == Node.CDATA_SECTION_NODE:
18713481Sgiacomo.travaglini@arm.com          # Replaces the source line information with a normalized form.
18813481Sgiacomo.travaglini@arm.com          cdata = re.sub(source_line_pat, '\\1*\n', child.nodeValue)
18913481Sgiacomo.travaglini@arm.com          # Removes the actual stack trace.
19013481Sgiacomo.travaglini@arm.com          child.nodeValue = re.sub(r'\nStack trace:\n(.|\n)*',
19113481Sgiacomo.travaglini@arm.com                                   '', cdata)
19213481Sgiacomo.travaglini@arm.com    for child in element.childNodes:
19313481Sgiacomo.travaglini@arm.com      if child.nodeType == Node.ELEMENT_NODE:
19413481Sgiacomo.travaglini@arm.com        self.NormalizeXml(child)
195