From ad962c05be124991ea0e28979a324bb1a92ee6ad Mon Sep 17 00:00:00 2001 From: Balazs Gibizer Date: Fri, 18 May 2018 16:02:35 +0200 Subject: [PATCH] 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 --- oslo_versionedobjects/fields.py | 8 +++++++- oslo_versionedobjects/tests/test_fields.py | 9 +++++++++ oslo_versionedobjects/tests/test_fixture.py | 14 ++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/oslo_versionedobjects/fields.py b/oslo_versionedobjects/fields.py index a35939e0..406ff8db 100644 --- a/oslo_versionedobjects/fields.py +++ b/oslo_versionedobjects/fields.py @@ -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): diff --git a/oslo_versionedobjects/tests/test_fields.py b/oslo_versionedobjects/tests/test_fields.py index 8ad1b38f..a1702b42 100644 --- a/oslo_versionedobjects/tests/test_fields.py +++ b/oslo_versionedobjects/tests/test_fields.py @@ -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=,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): diff --git a/oslo_versionedobjects/tests/test_fixture.py b/oslo_versionedobjects/tests/test_fixture.py index d4b27021..0a0c2264 100644 --- a/oslo_versionedobjects/tests/test_fixture.py +++ b/oslo_versionedobjects/tests/test_fixture.py @@ -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