Add MatchesDictExceptForKeys custom matcher
Add a custom matcher that compares two dictionaries, except for
keys matching an exclusion list.
The matcher returns with a mismatch object design to provide a
useful message in case of failed assertion, which pinpoints the
differences between the two dicts.
Verified via unit tests.
Partially implements: bp/tempest-client-scenarios
Change-Id: Ibdd34fa1f095ee2ee33e11a8e23e654fb6d12b89
diff --git a/tempest/common/custom_matchers.py b/tempest/common/custom_matchers.py
index 996c365..7348a7d 100644
--- a/tempest/common/custom_matchers.py
+++ b/tempest/common/custom_matchers.py
@@ -13,6 +13,9 @@
# under the License.
import re
+from unittest import util
+
+from testtools import helpers
class ExistsAllResponseHeaders(object):
@@ -172,3 +175,54 @@
def get_details(self):
return {}
+
+
+class MatchesDictExceptForKeys(object):
+ """Matches two dictionaries. Verifies all items are equals except for those
+ identified by a list of keys.
+ """
+
+ def __init__(self, expected, excluded_keys=None):
+ self.expected = expected
+ self.excluded_keys = excluded_keys if excluded_keys is not None else []
+
+ def match(self, actual):
+ filtered_expected = helpers.dict_subtract(self.expected,
+ self.excluded_keys)
+ filtered_actual = helpers.dict_subtract(actual,
+ self.excluded_keys)
+ if filtered_actual != filtered_expected:
+ return DictMismatch(filtered_expected, filtered_actual)
+
+
+class DictMismatch(object):
+ """Mismatch between two dicts describes deltas"""
+
+ def __init__(self, expected, actual):
+ self.expected = expected
+ self.actual = actual
+ self.intersect = set(self.expected) & set(self.actual)
+ self.symmetric_diff = set(self.expected) ^ set(self.actual)
+
+ def describe(self):
+ msg = ""
+ if self.symmetric_diff:
+ only_expected = helpers.dict_subtract(self.expected, self.actual)
+ only_actual = helpers.dict_subtract(self.actual, self.expected)
+ if only_expected:
+ msg += "Only in expected:\n %s\n" % \
+ util.safe_repr(only_expected)
+ if only_actual:
+ msg += "Only in actual:\n %s\n" % \
+ util.safe_repr(only_actual)
+ diff_set = set(o for o in self.intersect if
+ self.expected[o] != self.actual[o])
+ if diff_set:
+ msg += "Differences:\n"
+ for o in diff_set:
+ msg += " %s: expected %s, actual %s\n" % (
+ o, self.expected[o], self.actual[o])
+ return msg
+
+ def get_details(self):
+ return {}
diff --git a/tempest/tests/common/test_custom_matchers.py b/tempest/tests/common/test_custom_matchers.py
new file mode 100644
index 0000000..57217e3
--- /dev/null
+++ b/tempest/tests/common/test_custom_matchers.py
@@ -0,0 +1,66 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# 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 tempest.common import custom_matchers
+from tempest.tests import base
+
+from testtools.tests.matchers import helpers
+
+
+class TestMatchesDictExceptForKeys(base.TestCase,
+ helpers.TestMatchersInterface):
+
+ matches_matcher = custom_matchers.MatchesDictExceptForKeys(
+ {'a': 1, 'b': 2, 'c': 3, 'd': 4}, ['c', 'd'])
+ matches_matches = [
+ {'a': 1, 'b': 2, 'c': 3, 'd': 4},
+ {'a': 1, 'b': 2, 'c': 5},
+ {'a': 1, 'b': 2},
+ ]
+ matches_mismatches = [
+ {},
+ {'foo': 1},
+ {'a': 1, 'b': 3},
+ {'a': 1, 'b': 2, 'foo': 1},
+ {'a': 1, 'b': None, 'foo': 1},
+ ]
+
+ str_examples = []
+ describe_examples = [
+ ("Only in expected:\n"
+ " {'a': 1, 'b': 2}\n",
+ {},
+ matches_matcher),
+ ("Only in expected:\n"
+ " {'a': 1, 'b': 2}\n"
+ "Only in actual:\n"
+ " {'foo': 1}\n",
+ {'foo': 1},
+ matches_matcher),
+ ("Differences:\n"
+ " b: expected 2, actual 3\n",
+ {'a': 1, 'b': 3},
+ matches_matcher),
+ ("Only in actual:\n"
+ " {'foo': 1}\n",
+ {'a': 1, 'b': 2, 'foo': 1},
+ matches_matcher),
+ ("Only in actual:\n"
+ " {'foo': 1}\n"
+ "Differences:\n"
+ " b: expected 2, actual None\n",
+ {'a': 1, 'b': None, 'foo': 1},
+ matches_matcher)
+ ]
\ No newline at end of file