Make the hash of a defaulted set field stable

The string representation of an empty set is different between py27 and
py35. The ObjectVersionChecker uses the string represenation of the
default value of the fields as input for the hash of the object. This
makes hash of an object with a set field defaulted to an empty set
unstable.

This patch enhances the repr generation of the Field object to avoid the
unstable hash situation.

Change-Id: Ie9519c1893175614d60af97b635e6ff57f2b0d7d
Closes-Bug: #1771804
This commit is contained in:
Balazs Gibizer 2018-05-18 16:02:35 +02:00
parent 337cd36459
commit ad962c05be
3 changed files with 30 additions and 1 deletions

View File

@ -144,8 +144,14 @@ class Field(object):
self._read_only = read_only
def __repr__(self):
if isinstance(self._default, set):
# make a py27 and py35 compatible representation. See bug 1771804
default = 'set([%s])' % ','.join(sorted([six.text_type(v)
for v in self._default]))
else:
default = str(self._default)
return '%s(default=%s,nullable=%s)' % (self._type.__class__.__name__,
self._default, self._nullable)
default, self._nullable)
@property
def nullable(self):

View File

@ -813,6 +813,15 @@ class TestSetOfIntegers(TestField):
def test_stringify(self):
self.assertEqual('set([1,2])', self.field.stringify(set([1, 2])))
def test_repr(self):
self.assertEqual("Set(default=<class 'oslo_versionedobjects.fields."
"UnspecifiedDefault'>,nullable=False)",
repr(self.field))
self.assertEqual("Set(default=set([]),nullable=False)",
repr(fields.SetOfIntegersField(default=set())))
self.assertEqual("Set(default=set([1,a]),nullable=False)",
repr(fields.SetOfIntegersField(default={1, 'a'})))
class TestListOfSetsOfIntegers(TestField):
def setUp(self):

View File

@ -601,6 +601,20 @@ class TestObjectVersionChecker(test.TestCase):
self.assertEqual(expected_fp, fp, "_get_fingerprint() did not "
"generate a correct fingerprint.")
def test_get_fingerprint_with_defaulted_set(self):
class ClassWithDefaultedSetField(base.VersionedObject):
VERSION = 1.0
fields = {
'empty_default': fields.SetOfIntegersField(default=set()),
'non_empty_default': fields.SetOfIntegersField(default={1, 2})
}
self._add_class(self.obj_classes, ClassWithDefaultedSetField)
# it is expected that this hash is stable across python versions
expected = '1.0-bcc44920f2f727eca463c6eb4fe8445b'
actual = self.ovc._get_fingerprint(ClassWithDefaultedSetField.__name__)
self.assertEqual(expected, actual)
def test_get_dependencies(self):
# Make sure _get_dependencies() generates a correct tree when parsing
# an object