diff options
author | Jenkins <jenkins@review.openstack.org> | 2014-09-24 09:44:26 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2014-09-24 09:44:26 +0000 |
commit | ede83ef8d07546bf065b78ff7465f3e59735c834 (patch) | |
tree | 06d93e6ed87aa6bbc5004c90fdafe80378feb437 | |
parent | 878f12e16017cf328bc91fe75b5db680e37df583 (diff) | |
parent | 18d48431aa65f64136d5308808de4f278606c07f (diff) | |
download | keystone-ede83ef8d07546bf065b78ff7465f3e59735c834.tar.gz |
Merge "Fixes an issue with the XMLEquals matcher" into stable/icehouse
-rw-r--r-- | keystone/tests/matchers.py | 63 | ||||
-rw-r--r-- | keystone/tests/test_matchers.py | 63 |
2 files changed, 80 insertions, 46 deletions
diff --git a/keystone/tests/matchers.py b/keystone/tests/matchers.py index f904d339c..6076f348e 100644 --- a/keystone/tests/matchers.py +++ b/keystone/tests/matchers.py @@ -12,8 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. -import six - from lxml import etree from testtools import matchers @@ -30,24 +28,50 @@ class XMLEquals(object): return "%s(%r)" % (self.__class__.__name__, self.expected) def match(self, other): - parser = etree.XMLParser(remove_blank_text=True) + def xml_element_equals(expected_doc, observed_doc): + """Tests whether two XML documents are equivalent. + + This is a recursive algorithm that operates on each element in + the hierarchy. Siblings are sorted before being checked to + account for two semantically equivalent documents where siblings + appear in different document order. + + The sorting algorithm is a little weak in that it could fail for + documents where siblings at a given level are the same, but have + different children. + + """ + + if expected_doc.tag != observed_doc.tag: + return False - def canonical_xml(s): - s = s.strip() + if expected_doc.attrib != observed_doc.attrib: + return False - fp = six.StringIO() - dom = etree.fromstring(s, parser) - dom.getroottree().write_c14n(fp) - s = fp.getvalue() + def _sorted_children(doc): + return sorted(doc.getchildren(), key=lambda el: el.tag) - dom = etree.fromstring(s, parser) - return etree.tostring(dom, pretty_print=True) + expected_children = _sorted_children(expected_doc) + observed_children = _sorted_children(observed_doc) - expected = canonical_xml(self.expected) - other = canonical_xml(other) - if expected == other: + if len(expected_children) != len(observed_children): + return False + + for expected_el, observed_el in zip(expected_children, + observed_children): + if not xml_element_equals(expected_el, observed_el): + return False + + return True + + parser = etree.XMLParser(remove_blank_text=True) + expected_doc = etree.fromstring(self.expected.strip(), parser) + observed_doc = etree.fromstring(other.strip(), parser) + + if xml_element_equals(expected_doc, observed_doc): return - return XMLMismatch(expected, other) + + return XMLMismatch(self.expected, other) class XMLMismatch(matchers.Mismatch): @@ -57,4 +81,11 @@ class XMLMismatch(matchers.Mismatch): self.other = other def describe(self): - return 'expected = %s\nactual = %s' % (self.expected, self.other) + def pretty_xml(xml): + parser = etree.XMLParser(remove_blank_text=True) + doc = etree.fromstring(xml.strip(), parser) + return (etree.tostring(doc, encoding='utf-8', pretty_print=True) + .decode('utf-8')) + + return 'expected =\n%s\nactual =\n%s' % ( + pretty_xml(self.expected), pretty_xml(self.other)) diff --git a/keystone/tests/test_matchers.py b/keystone/tests/test_matchers.py index b1c324a2d..e9413a1f6 100644 --- a/keystone/tests/test_matchers.py +++ b/keystone/tests/test_matchers.py @@ -12,42 +12,45 @@ # License for the specific language governing permissions and limitations # under the License. -import textwrap - -import testtools from testtools.tests.matchers import helpers +from keystone import tests 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> +class TestXMLEquals(tests.BaseTestCase, helpers.TestMatchersInterface): + matches_xml = b"""\ +<?xml version="1.0" encoding="UTF-8"?> +<test xmlns="http://docs.openstack.org/identity/api/v2.0"> + <first z="0" y="1" x="2"/> + <second a="a" b="b"></second> +</test> +""" + equivalent_xml = b"""\ +<?xml version="1.0" encoding="UTF-8"?> +<test xmlns="http://docs.openstack.org/identity/api/v2.0"> + <second a="a" b="b"/> + <first z="0" y="1" x="2"></first> +</test> +""" + mismatches_xml = b"""\ +<?xml version="1.0" encoding="UTF-8"?> +<test xmlns="http://docs.openstack.org/identity/api/v2.0"> + <nope_it_fails/> +</test> +""" + mismatches_description = """\ +expected = +<test xmlns="http://docs.openstack.org/identity/api/v2.0"> + <first z="0" y="1" x="2"/> + <second a="a" b="b"/> +</test> - actual = <test xmlns="http://docs.openstack.org/identity/api/v2.0"> - <nope_it_fails/> - </test> - """).lstrip() +actual = +<test xmlns="http://docs.openstack.org/identity/api/v2.0"> + <nope_it_fails/> +</test> +""" matches_matcher = matchers.XMLEquals(matches_xml) matches_matches = [matches_xml, equivalent_xml] |