diff --git a/keystoneauth1/tests/unit/matchers.py b/keystoneauth1/tests/unit/matchers.py new file mode 100644 index 00000000..6076f348 --- /dev/null +++ b/keystoneauth1/tests/unit/matchers.py @@ -0,0 +1,91 @@ +# 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. + +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): + 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 + + if expected_doc.attrib != observed_doc.attrib: + return False + + def _sorted_children(doc): + return sorted(doc.getchildren(), key=lambda el: el.tag) + + expected_children = _sorted_children(expected_doc) + observed_children = _sorted_children(observed_doc) + + 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(self.expected, other) + + +class XMLMismatch(matchers.Mismatch): + + def __init__(self, expected, other): + self.expected = expected + self.other = other + + def describe(self): + 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/keystoneauth1/tests/unit/test_matchers.py b/keystoneauth1/tests/unit/test_matchers.py new file mode 100644 index 00000000..01e92785 --- /dev/null +++ b/keystoneauth1/tests/unit/test_matchers.py @@ -0,0 +1,57 @@ +# 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 testtools +from testtools.tests.matchers import helpers + +from keystoneauth1.tests.unit import matchers + + +class TestXMLEquals(testtools.TestCase, helpers.TestMatchersInterface): + matches_xml = b""" + + + + +""" + equivalent_xml = b""" + + + + +""" + mismatches_xml = b""" + + + +""" + mismatches_description = """expected = + + + + + +actual = + + + +""" + + 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)]