Refactor assertEqualXML into a testtools matcher

Not all tests inheriting from TestCase will need to check XML equality.
Moving this functionality to a matcher leaves it up to the test class to
decided whether or not it needs it.

Change-Id: Ib28ec3b5dd96f662ce0cd90c650434b24c63ad6c
Related-Bug: #1226466
This commit is contained in:
David Stanek 2013-09-30 21:16:25 +00:00
parent 0dd5451401
commit 96f1980f1f
5 changed files with 141 additions and 42 deletions

View File

@ -21,14 +21,12 @@ import os
import re
import shutil
import socket
import StringIO
import sys
import time
import warnings
import fixtures
import logging
from lxml import etree
from paste import deploy
import testtools
@ -469,29 +467,6 @@ class TestCase(testtools.TestCase):
self.fail(self._formatMessage(msg, standardMsg))
def assertEqualXML(self, a, b):
"""Parses two XML documents from strings and compares the results.
This provides easy-to-read failures.
"""
parser = etree.XMLParser(remove_blank_text=True)
def canonical_xml(s):
s = s.strip()
fp = StringIO.StringIO()
dom = etree.fromstring(s, parser)
dom.getroottree().write_c14n(fp)
s = fp.getvalue()
dom = etree.fromstring(s, parser)
return etree.tostring(dom, pretty_print=True)
a = canonical_xml(a)
b = canonical_xml(b)
self.assertEqual(a.split('\n'), b.split('\n'))
def skip_if_no_ipv6(self):
try:
s = socket.socket(socket.AF_INET6)

View File

@ -0,0 +1,62 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import six
from lxml import etree
from testtools import matchers
class XMLEquals(object):
"""Parses two XML documents from strings and compares the results.
"""
def __init__(self, expected):
self.expected = expected
def __str__(self):
return "%s(%r)" % (self.__class__.__name__, self.expected)
def match(self, other):
parser = etree.XMLParser(remove_blank_text=True)
def canonical_xml(s):
s = s.strip()
fp = six.StringIO()
dom = etree.fromstring(s, parser)
dom.getroottree().write_c14n(fp)
s = fp.getvalue()
dom = etree.fromstring(s, parser)
return etree.tostring(dom, pretty_print=True)
expected = canonical_xml(self.expected)
other = canonical_xml(other)
if expected == other:
return
return XMLMismatch(expected, other)
class XMLMismatch(matchers.Mismatch):
def __init__(self, expected, other):
self.expected = expected
self.other = other
def describe(self):
return 'expected = %s\nactual = %s' % (self.expected, self.other)

View File

@ -0,0 +1,60 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import textwrap
import testtools
from testtools.tests.matchers import helpers
from keystone.tests import matchers
class TestXMLEquals(testtools.TestCase, helpers.TestMatchersInterface):
matches_xml = """
<?xml version="1.0" encoding="UTF-8"?>
<test xmlns="http://docs.openstack.org/identity/api/v2.0">
<success a="a" b="b"/>
</test>
"""
equivalent_xml = """
<?xml version="1.0" encoding="UTF-8"?>
<test xmlns="http://docs.openstack.org/identity/api/v2.0">
<success b="b" a="a"></success>
</test>
"""
mismatches_xml = """
<?xml version="1.0" encoding="UTF-8"?>
<test xmlns="http://docs.openstack.org/identity/api/v2.0">
<nope_it_fails/>
</test>
"""
mismatches_description = textwrap.dedent("""\
expected = <test xmlns="http://docs.openstack.org/identity/api/v2.0">
<success a="a" b="b"/>
</test>
actual = <test xmlns="http://docs.openstack.org/identity/api/v2.0">
<nope_it_fails/>
</test>
""").lstrip()
matches_matcher = matchers.XMLEquals(matches_xml)
matches_matches = [matches_xml, equivalent_xml]
matches_mismatches = [mismatches_xml]
describe_examples = [
(mismatches_description, mismatches_xml, matches_matcher),
]
str_examples = [('XMLEquals(%r)' % matches_xml, matches_matcher)]

View File

@ -20,22 +20,23 @@ from testtools import matchers
from keystone.common import serializer
from keystone import tests
from keystone.tests import matchers as ksmatchers
class XmlSerializerTestCase(tests.TestCase):
def assertSerializeDeserialize(self, d, xml, xmlns=None):
self.assertEqualXML(
self.assertThat(
serializer.to_xml(copy.deepcopy(d), xmlns),
xml)
ksmatchers.XMLEquals(xml))
self.assertEqual(serializer.from_xml(xml), d)
# operations should be invertible
self.assertEqual(
serializer.from_xml(serializer.to_xml(copy.deepcopy(d), xmlns)),
d)
self.assertEqualXML(
self.assertThat(
serializer.to_xml(serializer.from_xml(xml), xmlns),
xml)
ksmatchers.XMLEquals(xml))
def test_auth_request(self):
d = {
@ -162,7 +163,7 @@ class XmlSerializerTestCase(tests.TestCase):
<policy id="ab12cd"/>
</policies>
"""
self.assertEqualXML(serializer.to_xml(d), xml)
self.assertThat(serializer.to_xml(d), ksmatchers.XMLEquals(xml))
def test_values_list(self):
d = {
@ -183,7 +184,7 @@ class XmlSerializerTestCase(tests.TestCase):
</objects>
"""
self.assertEqualXML(serializer.to_xml(d), xml)
self.assertThat(serializer.to_xml(d), ksmatchers.XMLEquals(xml))
def test_collection_list(self):
d = {
@ -296,7 +297,7 @@ identity-service/2.0/content/" type="text/html"/>
identity-service/2.0/identity-dev-guide-2.0.pdf" type="application/pdf"/>
</object>
"""
self.assertEqualXML(serializer.to_xml(d), xml)
self.assertThat(serializer.to_xml(d), ksmatchers.XMLEquals(xml))
def test_xml_with_namespaced_attribute_to_dict(self):
expected = {

View File

@ -22,6 +22,7 @@ from keystone import controllers
from keystone.openstack.common.fixture import moxstubout
from keystone.openstack.common import jsonutils
from keystone import tests
from keystone.tests import matchers
CONF = config.CONF
@ -341,7 +342,7 @@ vnd.openstack.identity-v3+xml"/>
self.assertEqual(resp.status_int, 300)
data = resp.body
expected = self.VERSIONS_RESPONSE % dict(port=CONF.public_port)
self.assertEqualXML(data, expected)
self.assertThat(data, matchers.XMLEquals(expected))
def test_admin_versions(self):
client = self.client(self.admin_app)
@ -349,7 +350,7 @@ vnd.openstack.identity-v3+xml"/>
self.assertEqual(resp.status_int, 300)
data = resp.body
expected = self.VERSIONS_RESPONSE % dict(port=CONF.admin_port)
self.assertEqualXML(data, expected)
self.assertThat(data, matchers.XMLEquals(expected))
def test_public_version_v2(self):
client = self.client(self.public_app)
@ -357,7 +358,7 @@ vnd.openstack.identity-v3+xml"/>
self.assertEqual(resp.status_int, 200)
data = resp.body
expected = self.v2_VERSION_RESPONSE % dict(port=CONF.public_port)
self.assertEqualXML(data, expected)
self.assertThat(data, matchers.XMLEquals(expected))
def test_admin_version_v2(self):
client = self.client(self.admin_app)
@ -365,7 +366,7 @@ vnd.openstack.identity-v3+xml"/>
self.assertEqual(resp.status_int, 200)
data = resp.body
expected = self.v2_VERSION_RESPONSE % dict(port=CONF.admin_port)
self.assertEqualXML(data, expected)
self.assertThat(data, matchers.XMLEquals(expected))
def test_public_version_v3(self):
client = self.client(self.public_app)
@ -373,7 +374,7 @@ vnd.openstack.identity-v3+xml"/>
self.assertEqual(resp.status_int, 200)
data = resp.body
expected = self.v3_VERSION_RESPONSE % dict(port=CONF.public_port)
self.assertEqualXML(data, expected)
self.assertThat(data, matchers.XMLEquals(expected))
def test_admin_version_v3(self):
client = self.client(self.public_app)
@ -381,7 +382,7 @@ vnd.openstack.identity-v3+xml"/>
self.assertEqual(resp.status_int, 200)
data = resp.body
expected = self.v3_VERSION_RESPONSE % dict(port=CONF.admin_port)
self.assertEqualXML(data, expected)
self.assertThat(data, matchers.XMLEquals(expected))
def test_v2_disabled(self):
self.stubs.Set(controllers, '_VERSIONS', ['v3'])
@ -392,7 +393,7 @@ vnd.openstack.identity-v3+xml"/>
self.assertEqual(resp.status_int, 200)
data = resp.body
expected = self.v3_VERSION_RESPONSE % dict(port=CONF.public_port)
self.assertEqualXML(data, expected)
self.assertThat(data, matchers.XMLEquals(expected))
# only v3 information should be displayed by requests to /
v3_only_response = ((self.DOC_INTRO + '<versions %(namespace)s>' +
@ -404,7 +405,7 @@ vnd.openstack.identity-v3+xml"/>
resp = client.get('/', headers=self.REQUEST_HEADERS)
self.assertEqual(resp.status_int, 300)
data = resp.body
self.assertEqualXML(data, v3_only_response)
self.assertThat(data, matchers.XMLEquals(v3_only_response))
def test_v3_disabled(self):
self.stubs.Set(controllers, '_VERSIONS', ['v2.0'])
@ -415,7 +416,7 @@ vnd.openstack.identity-v3+xml"/>
self.assertEqual(resp.status_int, 200)
data = resp.body
expected = self.v2_VERSION_RESPONSE % dict(port=CONF.public_port)
self.assertEqualXML(data, expected)
self.assertThat(data, matchers.XMLEquals(expected))
# only v2 information should be displayed by requests to /
v2_only_response = ((self.DOC_INTRO + '<versions %(namespace)s>' +
@ -427,4 +428,4 @@ vnd.openstack.identity-v3+xml"/>
resp = client.get('/', headers=self.REQUEST_HEADERS)
self.assertEqual(resp.status_int, 300)
data = resp.body
self.assertEqualXML(data, v2_only_response)
self.assertThat(data, matchers.XMLEquals(v2_only_response))