Fix compare_obj() to obey missing/unset fields
When comparing objects to dicts, if a field is unset on both, they should still be counted as equal. If something is unset on one, but not on the other, they should be counted as unequal. In any of these situations, if the field is set in allow_missing, all of the equality checks should just be skipped. Change-Id: I3e5143bc872ab4cb645d09c7e969fd1cf9c7985c Closes-Bug: #1566398
This commit is contained in:
parent
788e3d08f7
commit
52545273d9
|
@ -53,33 +53,46 @@ def compare_obj(test, obj, db_obj, subs=None, allow_missing=None,
|
|||
:param comparators: Map of comparator functions to use for certain fields
|
||||
"""
|
||||
|
||||
if subs is None:
|
||||
subs = {}
|
||||
if allow_missing is None:
|
||||
allow_missing = []
|
||||
if comparators is None:
|
||||
comparators = {}
|
||||
subs = subs or {}
|
||||
allow_missing = allow_missing or []
|
||||
comparators = comparators or {}
|
||||
|
||||
for key in obj.fields:
|
||||
# We'll raise a NotImplementedError if we try to compare against
|
||||
# against something that isn't set in the object, but is not
|
||||
# in the allow_missing. This will replace that exception
|
||||
# with an AssertionError (because that is a better way of saying
|
||||
# "these objects arent the same").
|
||||
if not obj.obj_attr_is_set(key):
|
||||
if key in allow_missing:
|
||||
continue
|
||||
else:
|
||||
raise AssertionError(("%s is not set on the object, so "
|
||||
"the objects are not equal") % key)
|
||||
if key in allow_missing and not obj.obj_attr_is_set(key):
|
||||
continue
|
||||
obj_val = getattr(obj, key)
|
||||
db_key = subs.get(key, key)
|
||||
|
||||
# If this is an allow_missing key and it's missing in either obj or
|
||||
# db_obj, just skip it
|
||||
if key in allow_missing:
|
||||
if key not in obj or db_key not in db_obj:
|
||||
continue
|
||||
|
||||
# If the value isn't set on the object, and also isn't set on the
|
||||
# db_obj, we'll skip the value check, unset in both is equal
|
||||
if not obj.obj_attr_is_set(key) and db_key not in db_obj:
|
||||
continue
|
||||
# If it's set on the object and not on the db_obj, they aren't equal
|
||||
elif obj.obj_attr_is_set(key) and db_key not in db_obj:
|
||||
raise AssertionError(("%s (db_key: %s) is set on the object, but "
|
||||
"not on the db_obj, so the objects are not "
|
||||
"equal")
|
||||
% (key, db_key))
|
||||
# If it's set on the db_obj and not the object, they aren't equal
|
||||
elif not obj.obj_attr_is_set(key) and db_key in db_obj:
|
||||
raise AssertionError(("%s (db_key: %s) is set on the db_obj, but "
|
||||
"not on the object, so the objects are not "
|
||||
"equal")
|
||||
% (key, db_key))
|
||||
|
||||
# All of the checks above have safeguarded us, so we know we will
|
||||
# get an obj_val and db_val without issue
|
||||
obj_val = getattr(obj, key)
|
||||
db_val = db_obj[db_key]
|
||||
if isinstance(obj_val, datetime.datetime):
|
||||
obj_val = obj_val.replace(tzinfo=None)
|
||||
|
||||
if isinstance(db_val, datetime.datetime):
|
||||
db_val = obj_val.replace(tzinfo=None)
|
||||
|
||||
if key in comparators:
|
||||
comparator = comparators[key]
|
||||
comparator(db_val, obj_val)
|
||||
|
|
|
@ -80,12 +80,72 @@ class TestObjectComparators(test.TestCase):
|
|||
self.assertIn(call, actual_calls)
|
||||
|
||||
def test_compare_obj_with_unset(self):
|
||||
# If the object has nothing set, and also the db object has the same
|
||||
# thing not set, it's OK.
|
||||
mock_test = mock.Mock()
|
||||
mock_test.assertEqual = mock.Mock()
|
||||
my_obj = self.MyComparedObject()
|
||||
my_db_obj = {}
|
||||
|
||||
self.assertRaises(AssertionError, fixture.compare_obj,
|
||||
mock_test, my_obj, my_db_obj)
|
||||
fixture.compare_obj(mock_test, my_obj, my_db_obj)
|
||||
|
||||
self.assertFalse(mock_test.assertEqual.called, "assertEqual should "
|
||||
"not have been called, there is nothing to compare.")
|
||||
|
||||
def test_compare_obj_with_unset_in_obj(self):
|
||||
# If the db dict has something set, but the object doesn't, that's !=
|
||||
mock_test = mock.Mock()
|
||||
mock_test.assertEqual = mock.Mock()
|
||||
my_obj = self.MyComparedObject(foo=1)
|
||||
my_db_obj = {'foo': 1, 'bar': 2}
|
||||
|
||||
self.assertRaises(AssertionError, fixture.compare_obj, mock_test,
|
||||
my_obj, my_db_obj)
|
||||
|
||||
def test_compare_obj_with_unset_in_db_dict(self):
|
||||
# If the object has something set, but the db dict doesn't, that's !=
|
||||
mock_test = mock.Mock()
|
||||
mock_test.assertEqual = mock.Mock()
|
||||
my_obj = self.MyComparedObject(foo=1, bar=2)
|
||||
my_db_obj = {'foo': 1}
|
||||
|
||||
self.assertRaises(AssertionError, fixture.compare_obj, mock_test,
|
||||
my_obj, my_db_obj)
|
||||
|
||||
def test_compare_obj_with_unset_in_obj_ignored(self):
|
||||
# If the db dict has something set, but the object doesn't, but we
|
||||
# ignore that key, we are equal
|
||||
my_obj = self.MyComparedObject(foo=1)
|
||||
my_db_obj = {'foo': 1, 'bar': 2}
|
||||
ignore = ['bar']
|
||||
|
||||
fixture.compare_obj(self, my_obj, my_db_obj, allow_missing=ignore)
|
||||
|
||||
def test_compare_obj_with_unset_in_db_dict_ignored(self):
|
||||
# If the object has something set, but the db dict doesn't, but we
|
||||
# ignore that key, we are equal
|
||||
my_obj = self.MyComparedObject(foo=1, bar=2)
|
||||
my_db_obj = {'foo': 1}
|
||||
ignore = ['bar']
|
||||
|
||||
fixture.compare_obj(self, my_obj, my_db_obj, allow_missing=ignore)
|
||||
|
||||
def test_compare_obj_with_allow_missing_unequal(self):
|
||||
# If the tested key is in allow_missing, but both the obj and db_obj
|
||||
# have the value set, we should still check it for equality
|
||||
mock_test = mock.Mock()
|
||||
mock_test.assertEqual = mock.Mock()
|
||||
my_obj = self.MyComparedObject(foo=1, bar=2)
|
||||
my_db_obj = {'foo': 1, 'bar': 1}
|
||||
ignore = ['bar']
|
||||
|
||||
fixture.compare_obj(mock_test, my_obj, my_db_obj,
|
||||
allow_missing=ignore)
|
||||
|
||||
expected_calls = [(1, 1), (1, 2)]
|
||||
actual_calls = [c[0] for c in mock_test.assertEqual.call_args_list]
|
||||
for call in expected_calls:
|
||||
self.assertIn(call, actual_calls)
|
||||
|
||||
def test_compare_obj_with_subs(self):
|
||||
mock_test = mock.Mock()
|
||||
|
|
Loading…
Reference in New Issue