"Fossies" - the Fresh Open Source Software Archive

Member "masakari-9.0.0/masakari/tests/unit/objects/test_objects.py" (13 May 2020, 33197 Bytes) of package /linux/misc/openstack/masakari-9.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "test_objects.py": 8.0.0_vs_9.0.0.

    1 #    Copyright 2016 NTT DATA
    2 #    All Rights Reserved.
    3 #
    4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    5 #    not use this file except in compliance with the License. You may obtain
    6 #    a copy of the License at
    7 #
    8 #         http://www.apache.org/licenses/LICENSE-2.0
    9 #
   10 #    Unless required by applicable law or agreed to in writing, software
   11 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   13 #    License for the specific language governing permissions and limitations
   14 #    under the License.
   15 
   16 import copy
   17 import datetime
   18 import inspect
   19 import os
   20 import pprint
   21 from unittest import mock
   22 
   23 from oslo_versionedobjects import exception as ovo_exc
   24 from oslo_versionedobjects import fixture
   25 
   26 from masakari import objects
   27 from masakari.objects import base
   28 from masakari.objects import fields
   29 from masakari.objects import segment
   30 from masakari import test
   31 from masakari.tests.unit.objects import fake_args
   32 
   33 
   34 class MyOwnedObject(base.MasakariPersistentObject, base.MasakariObject):
   35     VERSION = '1.0'
   36     fields = {'baz': fields.IntegerField()}
   37 
   38 
   39 class MyObj(base.MasakariPersistentObject, base.MasakariObject,
   40             base.MasakariObjectDictCompat):
   41     VERSION = '1.6'
   42     fields = {'foo': fields.IntegerField(default=1),
   43               'bar': fields.StringField(),
   44               'missing': fields.StringField(),
   45               'readonly': fields.IntegerField(read_only=True),
   46               'rel_object': fields.ObjectField('MyOwnedObject', nullable=True),
   47               'rel_objects': fields.ListOfObjectsField('MyOwnedObject',
   48                                                        nullable=True),
   49               'mutable_default': fields.ListOfStringsField(default=[]),
   50               }
   51 
   52     @staticmethod
   53     def _from_db_object(context, obj, db_obj):
   54         self = MyObj()
   55         self.foo = db_obj['foo']
   56         self.bar = db_obj['bar']
   57         self.missing = db_obj['missing']
   58         self.readonly = 1
   59         self._context = context
   60         return self
   61 
   62     def obj_load_attr(self, attrname):
   63         setattr(self, attrname, 'loaded!')
   64 
   65     @base.remotable_classmethod
   66     def query(cls, context):
   67         obj = cls(context=context, foo=1, bar='bar')
   68         obj.obj_reset_changes()
   69         return obj
   70 
   71     @base.remotable
   72     def marco(self):
   73         return 'polo'
   74 
   75     @base.remotable
   76     def _update_test(self):
   77         self.bar = 'updated'
   78 
   79     @base.remotable
   80     def save(self):
   81         self.obj_reset_changes()
   82 
   83     @base.remotable
   84     def refresh(self):
   85         self.foo = 321
   86         self.bar = 'refreshed'
   87         self.obj_reset_changes()
   88 
   89     @base.remotable
   90     def modify_save_modify(self):
   91         self.bar = 'meow'
   92         self.save()
   93         self.foo = 42
   94         self.rel_object = MyOwnedObject(baz=42)
   95 
   96     def obj_make_compatible(self, primitive, target_version):
   97         super(MyObj, self).obj_make_compatible(primitive, target_version)
   98         if target_version == '1.0' and 'bar' in primitive:
   99             primitive['bar'] = 'old%s' % primitive['bar']
  100 
  101 
  102 class TestObjMakeList(test.NoDBTestCase):
  103 
  104     def test_obj_make_list(self):
  105         class MyList(base.ObjectListBase, base.MasakariObject):
  106             fields = {
  107                 'objects': fields.ListOfObjectsField('MyObj'),
  108             }
  109 
  110         db_objs = [{'foo': 1, 'bar': 'baz', 'missing': 'banana'},
  111                    {'foo': 2, 'bar': 'bat', 'missing': 'apple'},
  112                    ]
  113         mylist = base.obj_make_list('ctxt', MyList(), MyObj, db_objs)
  114         self.assertEqual(2, len(mylist))
  115         self.assertEqual('ctxt', mylist._context)
  116         for index, item in enumerate(mylist):
  117             self.assertEqual(db_objs[index]['foo'], item.foo)
  118             self.assertEqual(db_objs[index]['bar'], item.bar)
  119             self.assertEqual(db_objs[index]['missing'], item.missing)
  120 
  121 
  122 def compare_obj(test, obj, db_obj, subs=None, allow_missing=None,
  123                 comparators=None):
  124     """Compare a MasakariObject and a dict-like database object.
  125 
  126     This automatically converts TZ-aware datetimes and iterates over
  127     the fields of the object.
  128 
  129     :param:test: The TestCase doing the comparison
  130     :param:obj: The MasakariObject to examine
  131     :param:db_obj: The dict-like database object to use as reference
  132     :param:subs: A dict of objkey=dbkey field substitutions
  133     :param:allow_missing: A list of fields that may not be in db_obj
  134     :param:comparators: Map of comparator functions to use for certain fields
  135     """
  136 
  137     if subs is None:
  138         subs = {}
  139     if allow_missing is None:
  140         allow_missing = []
  141     if comparators is None:
  142         comparators = {}
  143 
  144     for key in obj.fields:
  145         if key in allow_missing and not obj.obj_attr_is_set(key):
  146             continue
  147         obj_val = getattr(obj, key)
  148         db_key = subs.get(key, key)
  149         db_val = db_obj[db_key]
  150         if isinstance(obj_val, datetime.datetime):
  151             obj_val = obj_val.replace(tzinfo=None)
  152 
  153         if key in comparators:
  154             comparator = comparators[key]
  155             comparator(db_val, obj_val)
  156         else:
  157             test.assertEqual(db_val, obj_val)
  158 
  159 
  160 class _BaseTestCase(test.TestCase):
  161     def setUp(self):
  162         super(_BaseTestCase, self).setUp()
  163         self.user_id = 'fake-user'
  164         self.project_id = 'fake-project'
  165         self.context = 'masakari-context'
  166 
  167         base.MasakariObjectRegistry.register(MyObj)
  168         base.MasakariObjectRegistry.register(MyOwnedObject)
  169 
  170     def compare_obj(self, obj, db_obj, subs=None, allow_missing=None,
  171                     comparators=None):
  172         compare_obj(self, obj, db_obj, subs=subs, allow_missing=allow_missing,
  173                     comparators=comparators)
  174 
  175     def str_comparator(self, expected, obj_val):
  176         """Compare an object field to a string in the db by performing
  177         a simple coercion on the object field value.
  178         """
  179         self.assertEqual(expected, str(obj_val))
  180 
  181 
  182 class _LocalTest(_BaseTestCase):
  183     def setUp(self):
  184         super(_LocalTest, self).setUp()
  185 
  186 
  187 class _TestObject(object):
  188     def test_object_attrs_in_init(self):
  189         # Now check the test one in this file. Should be newest version
  190         self.assertEqual('1.6', objects.MyObj.VERSION)
  191 
  192     def test_hydration_type_error(self):
  193         primitive = {'masakari_object.name': 'MyObj',
  194                      'masakari_object.namespace': 'masakari',
  195                      'masakari_object.version': '1.5',
  196                      'masakari_object.data': {'foo': 'a'}}
  197         self.assertRaises(ValueError, MyObj.obj_from_primitive, primitive)
  198 
  199     def test_hydration(self):
  200         primitive = {'masakari_object.name': 'MyObj',
  201                      'masakari_object.namespace': 'masakari',
  202                      'masakari_object.version': '1.5',
  203                      'masakari_object.data': {'foo': 1}}
  204         real_method = MyObj._obj_from_primitive
  205 
  206         def _obj_from_primitive(*args):
  207             return real_method(*args)
  208 
  209         with mock.patch.object(MyObj, '_obj_from_primitive') as ofp:
  210             ofp.side_effect = _obj_from_primitive
  211             obj = MyObj.obj_from_primitive(primitive)
  212             ofp.assert_called_once_with(None, '1.5', primitive)
  213         self.assertEqual(obj.foo, 1)
  214 
  215     def test_hydration_version_different(self):
  216         primitive = {'masakari_object.name': 'MyObj',
  217                      'masakari_object.namespace': 'masakari',
  218                      'masakari_object.version': '1.2',
  219                      'masakari_object.data': {'foo': 1}}
  220         obj = MyObj.obj_from_primitive(primitive)
  221         self.assertEqual(obj.foo, 1)
  222         self.assertEqual('1.2', obj.VERSION)
  223 
  224     def test_hydration_bad_ns(self):
  225         primitive = {'masakari_object.name': 'MyObj',
  226                      'masakari_object.namespace': 'foo',
  227                      'masakari_object.version': '1.5',
  228                      'masakari_object.data': {'foo': 1}}
  229         self.assertRaises(ovo_exc.UnsupportedObjectError,
  230                           MyObj.obj_from_primitive, primitive)
  231 
  232     def test_hydration_additional_unexpected_stuff(self):
  233         primitive = {'masakari_object.name': 'MyObj',
  234                      'masakari_object.namespace': 'masakari',
  235                      'masakari_object.version': '1.5.1',
  236                      'masakari_object.data': {
  237                          'foo': 1,
  238                          'unexpected_thing': 'foobar'}}
  239         obj = MyObj.obj_from_primitive(primitive)
  240         self.assertEqual(1, obj.foo)
  241         self.assertFalse(hasattr(obj, 'unexpected_thing'))
  242         self.assertEqual('1.5.1', obj.VERSION)
  243 
  244     def test_dehydration(self):
  245         expected = {'masakari_object.name': 'MyObj',
  246                     'masakari_object.namespace': 'masakari',
  247                     'masakari_object.version': '1.6',
  248                     'masakari_object.data': {'foo': 1}}
  249         obj = MyObj(foo=1)
  250         obj.obj_reset_changes()
  251         self.assertEqual(obj.obj_to_primitive(), expected)
  252 
  253     def test_object_property(self):
  254         obj = MyObj(foo=1)
  255         self.assertEqual(obj.foo, 1)
  256 
  257     def test_object_property_type_error(self):
  258         obj = MyObj()
  259 
  260         def fail():
  261             obj.foo = 'a'
  262         self.assertRaises(ValueError, fail)
  263 
  264     def test_load(self):
  265         obj = MyObj()
  266         self.assertEqual(obj.bar, 'loaded!')
  267 
  268     def test_load_in_base(self):
  269         @base.MasakariObjectRegistry.register_if(False)
  270         class Foo(base.MasakariObject):
  271             fields = {'foobar': fields.IntegerField()}
  272         obj = Foo()
  273         with self.assertRaisesRegex(NotImplementedError, ".*foobar.*"):
  274             obj.foobar
  275 
  276     def test_loaded_in_primitive(self):
  277         obj = MyObj(foo=1)
  278         obj.obj_reset_changes()
  279         self.assertEqual(obj.bar, 'loaded!')
  280         expected = {'masakari_object.name': 'MyObj',
  281                     'masakari_object.namespace': 'masakari',
  282                     'masakari_object.version': '1.6',
  283                     'masakari_object.changes': ['bar'],
  284                     'masakari_object.data': {'foo': 1,
  285                                          'bar': 'loaded!'}}
  286         self.assertEqual(obj.obj_to_primitive(), expected)
  287 
  288     def test_changes_in_primitive(self):
  289         obj = MyObj(foo=123)
  290         self.assertEqual(obj.obj_what_changed(), set(['foo']))
  291         primitive = obj.obj_to_primitive()
  292         self.assertIn('masakari_object.changes', primitive)
  293         obj2 = MyObj.obj_from_primitive(primitive)
  294         self.assertEqual(obj2.obj_what_changed(), set(['foo']))
  295         obj2.obj_reset_changes()
  296         self.assertEqual(obj2.obj_what_changed(), set())
  297 
  298     def test_orphaned_object(self):
  299         obj = MyObj.query(self.context)
  300         obj._context = None
  301         self.assertRaises(ovo_exc.OrphanedObjectError,
  302                           obj._update_test)
  303 
  304     def test_changed_1(self):
  305         obj = MyObj.query(self.context)
  306         obj.foo = 123
  307         self.assertEqual(obj.obj_what_changed(), set(['foo']))
  308         obj._update_test()
  309         self.assertEqual(obj.obj_what_changed(), set(['foo', 'bar']))
  310         self.assertEqual(obj.foo, 123)
  311 
  312     def test_changed_2(self):
  313         obj = MyObj.query(self.context)
  314         obj.foo = 123
  315         self.assertEqual(obj.obj_what_changed(), set(['foo']))
  316         obj.save()
  317         self.assertEqual(obj.obj_what_changed(), set([]))
  318         self.assertEqual(obj.foo, 123)
  319 
  320     def test_changed_3(self):
  321         obj = MyObj.query(self.context)
  322         obj.foo = 123
  323         self.assertEqual(obj.obj_what_changed(), set(['foo']))
  324         obj.refresh()
  325         self.assertEqual(obj.obj_what_changed(), set([]))
  326         self.assertEqual(obj.foo, 321)
  327         self.assertEqual(obj.bar, 'refreshed')
  328 
  329     def test_changed_4(self):
  330         obj = MyObj.query(self.context)
  331         obj.bar = 'something'
  332         self.assertEqual(obj.obj_what_changed(), set(['bar']))
  333         obj.modify_save_modify()
  334         self.assertEqual(obj.obj_what_changed(), set(['foo', 'rel_object']))
  335         self.assertEqual(obj.foo, 42)
  336         self.assertEqual(obj.bar, 'meow')
  337         self.assertIsInstance(obj.rel_object, MyOwnedObject)
  338 
  339     def test_changed_with_sub_object(self):
  340         @base.MasakariObjectRegistry.register_if(False)
  341         class ParentObject(base.MasakariObject):
  342             fields = {'foo': fields.IntegerField(),
  343                       'bar': fields.ObjectField('MyObj'),
  344                       }
  345         obj = ParentObject()
  346         self.assertEqual(set(), obj.obj_what_changed())
  347         obj.foo = 1
  348         self.assertEqual(set(['foo']), obj.obj_what_changed())
  349         bar = MyObj()
  350         obj.bar = bar
  351         self.assertEqual(set(['foo', 'bar']), obj.obj_what_changed())
  352         obj.obj_reset_changes()
  353         self.assertEqual(set(), obj.obj_what_changed())
  354         bar.foo = 1
  355         self.assertEqual(set(['bar']), obj.obj_what_changed())
  356 
  357     def test_static_result(self):
  358         obj = MyObj.query(self.context)
  359         self.assertEqual(obj.bar, 'bar')
  360         result = obj.marco()
  361         self.assertEqual(result, 'polo')
  362 
  363     def test_updates(self):
  364         obj = MyObj.query(self.context)
  365         self.assertEqual(obj.foo, 1)
  366         obj._update_test()
  367         self.assertEqual(obj.bar, 'updated')
  368 
  369     def test_contains(self):
  370         obj = MyObj()
  371         self.assertNotIn('foo', obj)
  372         obj.foo = 1
  373         self.assertIn('foo', obj)
  374         self.assertNotIn('does_not_exist', obj)
  375 
  376     def test_obj_attr_is_set(self):
  377         obj = MyObj(foo=1)
  378         self.assertTrue(obj.obj_attr_is_set('foo'))
  379         self.assertFalse(obj.obj_attr_is_set('bar'))
  380         self.assertRaises(AttributeError, obj.obj_attr_is_set, 'bang')
  381 
  382     def test_obj_reset_changes_recursive(self):
  383         obj = MyObj(rel_object=MyOwnedObject(baz=123),
  384                     rel_objects=[MyOwnedObject(baz=456)])
  385         self.assertEqual(set(['rel_object', 'rel_objects']),
  386                          obj.obj_what_changed())
  387         obj.obj_reset_changes()
  388         self.assertEqual(set(['rel_object']), obj.obj_what_changed())
  389         self.assertEqual(set(['baz']), obj.rel_object.obj_what_changed())
  390         self.assertEqual(set(['baz']), obj.rel_objects[0].obj_what_changed())
  391         obj.obj_reset_changes(recursive=True, fields=['foo'])
  392         self.assertEqual(set(['rel_object']), obj.obj_what_changed())
  393         self.assertEqual(set(['baz']), obj.rel_object.obj_what_changed())
  394         self.assertEqual(set(['baz']), obj.rel_objects[0].obj_what_changed())
  395         obj.obj_reset_changes(recursive=True)
  396         self.assertEqual(set([]), obj.rel_object.obj_what_changed())
  397         self.assertEqual(set([]), obj.obj_what_changed())
  398 
  399     def test_get(self):
  400         obj = MyObj(foo=1)
  401         # Foo has value, should not get the default
  402         self.assertEqual(obj.get('foo', 2), 1)
  403         # Foo has value, should return the value without error
  404         self.assertEqual(obj.get('foo'), 1)
  405         # Bar is not loaded, so we should get the default
  406         self.assertEqual(obj.get('bar', 'not-loaded'), 'not-loaded')
  407         # Bar without a default should lazy-load
  408         self.assertEqual(obj.get('bar'), 'loaded!')
  409         # Bar now has a default, but loaded value should be returned
  410         self.assertEqual(obj.get('bar', 'not-loaded'), 'loaded!')
  411         # Invalid attribute should raise AttributeError
  412         self.assertRaises(AttributeError, obj.get, 'nothing')
  413         # ...even with a default
  414         self.assertRaises(AttributeError, obj.get, 'nothing', 3)
  415 
  416     def test_get_changes(self):
  417         obj = MyObj()
  418         self.assertEqual({}, obj.obj_get_changes())
  419         obj.foo = 123
  420         self.assertEqual({'foo': 123}, obj.obj_get_changes())
  421         obj.bar = 'test'
  422         self.assertEqual({'foo': 123, 'bar': 'test'}, obj.obj_get_changes())
  423         obj.obj_reset_changes()
  424         self.assertEqual({}, obj.obj_get_changes())
  425 
  426     def test_obj_fields(self):
  427         @base.MasakariObjectRegistry.register_if(False)
  428         class TestObj(base.MasakariObject):
  429             fields = {'foo': fields.IntegerField()}
  430             obj_extra_fields = ['bar']
  431 
  432             @property
  433             def bar(self):
  434                 return 'this is bar'
  435 
  436         obj = TestObj()
  437         self.assertEqual(['foo', 'bar'], obj.obj_fields)
  438 
  439     def test_obj_constructor(self):
  440         obj = MyObj(context=self.context, foo=123, bar='abc')
  441         self.assertEqual(123, obj.foo)
  442         self.assertEqual('abc', obj.bar)
  443         self.assertEqual(set(['foo', 'bar']), obj.obj_what_changed())
  444 
  445     def test_obj_read_only(self):
  446         obj = MyObj(context=self.context, foo=123, bar='abc')
  447         obj.readonly = 1
  448         self.assertRaises(ovo_exc.ReadOnlyFieldError, setattr,
  449                           obj, 'readonly', 2)
  450 
  451     def test_obj_mutable_default(self):
  452         obj = MyObj(context=self.context, foo=123, bar='abc')
  453         obj.mutable_default = None
  454         obj.mutable_default.append('s1')
  455         self.assertEqual(obj.mutable_default, ['s1'])
  456 
  457         obj1 = MyObj(context=self.context, foo=123, bar='abc')
  458         obj1.mutable_default = None
  459         obj1.mutable_default.append('s2')
  460         self.assertEqual(obj1.mutable_default, ['s2'])
  461 
  462     def test_obj_mutable_default_set_default(self):
  463         obj1 = MyObj(context=self.context, foo=123, bar='abc')
  464         obj1.obj_set_defaults('mutable_default')
  465         self.assertEqual(obj1.mutable_default, [])
  466         obj1.mutable_default.append('s1')
  467         self.assertEqual(obj1.mutable_default, ['s1'])
  468 
  469         obj2 = MyObj(context=self.context, foo=123, bar='abc')
  470         obj2.obj_set_defaults('mutable_default')
  471         self.assertEqual(obj2.mutable_default, [])
  472         obj2.mutable_default.append('s2')
  473         self.assertEqual(obj2.mutable_default, ['s2'])
  474 
  475     def test_obj_repr(self):
  476         obj = MyObj(foo=123)
  477         self.assertEqual('MyObj(bar=<?>,created_at=<?>,deleted=<?>,'
  478                          'deleted_at=<?>,foo=123,missing=<?>,'
  479                          'mutable_default=<?>,readonly=<?>,rel_object=<?>,'
  480                          'rel_objects=<?>,updated_at=<?>)',
  481                          repr(obj))
  482 
  483     def test_obj_make_obj_compatible(self):
  484         subobj = MyOwnedObject(baz=1)
  485         subobj.VERSION = '1.2'
  486         obj = MyObj(rel_object=subobj)
  487         obj.obj_relationships = {
  488             'rel_object': [('1.5', '1.1'), ('1.7', '1.2')],
  489         }
  490         orig_primitive = obj.obj_to_primitive()['masakari_object.data']
  491         with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat:
  492             primitive = copy.deepcopy(orig_primitive)
  493             obj._obj_make_obj_compatible(primitive, '1.8', 'rel_object')
  494             self.assertFalse(mock_compat.called)
  495 
  496         with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat:
  497             primitive = copy.deepcopy(orig_primitive)
  498             obj._obj_make_obj_compatible(primitive, '1.7', 'rel_object')
  499             mock_compat.assert_called_once_with(
  500                 primitive['rel_object']['masakari_object.data'], '1.2')
  501 
  502         with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat:
  503             primitive = copy.deepcopy(orig_primitive)
  504             obj._obj_make_obj_compatible(primitive, '1.6', 'rel_object')
  505             mock_compat.assert_called_once_with(
  506                 primitive['rel_object']['masakari_object.data'], '1.1')
  507             self.assertEqual('1.1', primitive[
  508                 'rel_object']['masakari_object.version'])
  509 
  510         with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat:
  511             primitive = copy.deepcopy(orig_primitive)
  512             obj._obj_make_obj_compatible(primitive, '1.5', 'rel_object')
  513             mock_compat.assert_called_once_with(
  514                 primitive['rel_object']['masakari_object.data'], '1.1')
  515             self.assertEqual('1.1', primitive[
  516                 'rel_object']['masakari_object.version'])
  517 
  518         with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat:
  519             primitive = copy.deepcopy(orig_primitive)
  520             obj._obj_make_obj_compatible(primitive, '1.4', 'rel_object')
  521             self.assertFalse(mock_compat.called)
  522             self.assertNotIn('rel_object', primitive)
  523 
  524     def test_obj_make_compatible_hits_sub_objects(self):
  525         subobj = MyOwnedObject(baz=1)
  526         obj = MyObj(foo=123, rel_object=subobj)
  527         obj.obj_relationships = {'rel_object': [('1.0', '1.0')]}
  528         with mock.patch.object(obj, '_obj_make_obj_compatible') as mock_compat:
  529             obj.obj_make_compatible({'rel_object': 'foo'}, '1.10')
  530             mock_compat.assert_called_once_with({'rel_object': 'foo'}, '1.10',
  531                                                 'rel_object')
  532 
  533     def test_obj_make_compatible_skips_unset_sub_objects(self):
  534         obj = MyObj(foo=123)
  535         obj.obj_relationships = {'rel_object': [('1.0', '1.0')]}
  536         with mock.patch.object(obj, '_obj_make_obj_compatible') as mock_compat:
  537             obj.obj_make_compatible({'rel_object': 'foo'}, '1.10')
  538             self.assertFalse(mock_compat.called)
  539 
  540     def test_obj_make_compatible_doesnt_skip_falsey_sub_objects(self):
  541         @base.MasakariObjectRegistry.register_if(False)
  542         class MyList(base.ObjectListBase, base.MasakariObject):
  543             VERSION = '1.2'
  544             fields = {'objects': fields.ListOfObjectsField('MyObjElement')}
  545             obj_relationships = {
  546                 'objects': [('1.1', '1.1'), ('1.2', '1.2')],
  547             }
  548 
  549         mylist = MyList(objects=[])
  550 
  551         @base.MasakariObjectRegistry.register_if(False)
  552         class MyOwner(base.MasakariObject):
  553             VERSION = '1.2'
  554             fields = {'mylist': fields.ObjectField('MyList')}
  555             obj_relationships = {
  556                 'mylist': [('1.1', '1.1')],
  557             }
  558 
  559         myowner = MyOwner(mylist=mylist)
  560         primitive = myowner.obj_to_primitive('1.1')
  561         self.assertIn('mylist', primitive['masakari_object.data'])
  562 
  563     def test_obj_make_compatible_handles_list_of_objects(self):
  564         subobj = MyOwnedObject(baz=1)
  565         obj = MyObj(rel_objects=[subobj])
  566         obj.obj_relationships = {'rel_objects': [('1.0', '1.123')]}
  567 
  568         def fake_make_compat(primitive, version):
  569             self.assertEqual('1.123', version)
  570             self.assertIn('baz', primitive)
  571 
  572         with mock.patch.object(subobj, 'obj_make_compatible') as mock_mc:
  573             mock_mc.side_effect = fake_make_compat
  574             obj.obj_to_primitive('1.0')
  575             self.assertTrue(mock_mc.called)
  576 
  577     def test_delattr(self):
  578         obj = MyObj(bar='foo')
  579         del obj.bar
  580 
  581         # Should appear unset now
  582         self.assertFalse(obj.obj_attr_is_set('bar'))
  583 
  584         # Make sure post-delete, references trigger lazy loads
  585         self.assertEqual('loaded!', getattr(obj, 'bar'))
  586 
  587     def test_delattr_unset(self):
  588         obj = MyObj()
  589         self.assertRaises(AttributeError, delattr, obj, 'bar')
  590 
  591 
  592 class TestObject(_LocalTest, _TestObject):
  593     def test_set_defaults(self):
  594         obj = MyObj()
  595         obj.obj_set_defaults('foo')
  596         self.assertTrue(obj.obj_attr_is_set('foo'))
  597         self.assertEqual(1, obj.foo)
  598 
  599     def test_set_defaults_no_default(self):
  600         obj = MyObj()
  601         self.assertRaises(ovo_exc.ObjectActionError,
  602                           obj.obj_set_defaults, 'bar')
  603 
  604     def test_set_all_defaults(self):
  605         obj = MyObj()
  606         obj.obj_set_defaults()
  607         self.assertEqual(set(['deleted', 'foo', 'mutable_default']),
  608                          obj.obj_what_changed())
  609         self.assertEqual(1, obj.foo)
  610 
  611     def test_set_defaults_not_overwrite(self):
  612         obj = MyObj(deleted=True)
  613         obj.obj_set_defaults()
  614         self.assertEqual(1, obj.foo)
  615         self.assertTrue(obj.deleted)
  616 
  617 
  618 class TestRegistry(test.NoDBTestCase):
  619     @mock.patch('masakari.objects.base.objects')
  620     def test_hook_chooses_newer_properly(self, mock_objects):
  621         reg = base.MasakariObjectRegistry()
  622         reg.registration_hook(MyObj, 0)
  623 
  624         class MyNewerObj(object):
  625             VERSION = '1.123'
  626 
  627             @classmethod
  628             def obj_name(cls):
  629                 return 'MyObj'
  630 
  631         self.assertEqual(MyObj, mock_objects.MyObj)
  632         reg.registration_hook(MyNewerObj, 0)
  633         self.assertEqual(MyNewerObj, mock_objects.MyObj)
  634 
  635     @mock.patch('masakari.objects.base.objects')
  636     def test_hook_keeps_newer_properly(self, mock_objects):
  637         reg = base.MasakariObjectRegistry()
  638         reg.registration_hook(MyObj, 0)
  639 
  640         class MyOlderObj(object):
  641             VERSION = '1.1'
  642 
  643             @classmethod
  644             def obj_name(cls):
  645                 return 'MyObj'
  646 
  647         self.assertEqual(MyObj, mock_objects.MyObj)
  648         reg.registration_hook(MyOlderObj, 0)
  649         self.assertEqual(MyObj, mock_objects.MyObj)
  650 
  651 
  652 # NOTE(Dinesh_Bhor): The hashes in this list should only be changed if
  653 # they come with a corresponding version bump in the affected
  654 # objects
  655 object_data = {
  656     'FailoverSegment': '1.0-5e8b8bc8840b35439b5f2b621482d15d',
  657     'FailoverSegmentList': '1.0-dfc5c6f5704d24dcaa37b0bbb03cbe60',
  658     'Host': '1.1-3fc4d548fa220c76906426095e5971fc',
  659     'HostList': '1.0-25ebe1b17fbd9f114fae8b6a10d198c0',
  660     'Notification': '1.1-91e3a051078e35300e325a3e2ae5fde5',
  661     'NotificationProgressDetails': '1.0-fc611ac932b719fbc154dbe34bb8edee',
  662     'NotificationList': '1.0-25ebe1b17fbd9f114fae8b6a10d198c0',
  663     'EventType': '1.0-d1d2010a7391fa109f0868d964152607',
  664     'ExceptionNotification': '1.0-1187e93f564c5cca692db76a66cda2a6',
  665     'ExceptionPayload': '1.0-96f178a12691e3ef0d8e3188fc481b90',
  666     'HostApiNotification': '1.0-1187e93f564c5cca692db76a66cda2a6',
  667     'HostApiPayload': '1.0-ca9035d81cec6697f12dd4cac4c8f027',
  668     'HostApiPayloadBase': '1.0-211379087a876212df6194b011207339',
  669     'NotificationApiPayload': '1.0-c050869a1f4aed23e7645bd4d1830ecd',
  670     'NotificationApiPayloadBase': '1.0-cda8d53a77e64f83e3782fc9c4d499bb',
  671     'NotificationApiNotification': '1.0-1187e93f564c5cca692db76a66cda2a6',
  672     'NotificationPublisher': '1.0-bbbc1402fb0e443a3eb227cc52b61545',
  673     'MyObj': '1.6-ee7b607402fbfb3390a92ab7199e0d88',
  674     'MyOwnedObject': '1.0-fec853730bd02d54cc32771dd67f08a0',
  675     'SegmentApiNotification': '1.0-1187e93f564c5cca692db76a66cda2a6',
  676     'SegmentApiPayload': '1.0-4c85836a1c2e4069b9dc84fa029a4657',
  677     'SegmentApiPayloadBase': '1.0-93a7c8b78d0e9ea3f6811d4ed75fa799'
  678 }
  679 
  680 
  681 def get_masakari_objects():
  682     """Get masakari versioned objects
  683 
  684     This returns a dict of versioned objects which are
  685     in the Masakari project namespace only. ie excludes
  686     objects from os-vif and other 3rd party modules
  687 
  688     :return: a dict mapping class names to lists of versioned objects
  689     """
  690 
  691     all_classes = base.MasakariObjectRegistry.obj_classes()
  692     masakari_classes = {}
  693     for name in all_classes:
  694         objclasses = all_classes[name]
  695         if (objclasses[0].OBJ_PROJECT_NAMESPACE != (
  696                 base.MasakariObject.OBJ_PROJECT_NAMESPACE)):
  697             continue
  698         masakari_classes[name] = objclasses
  699     return masakari_classes
  700 
  701 
  702 class TestObjectVersions(test.NoDBTestCase, _BaseTestCase):
  703     def setUp(self):
  704         super(test.NoDBTestCase, self).setUp()
  705         base.MasakariObjectRegistry.register_notification_objects()
  706 
  707     def test_versions(self):
  708         checker = fixture.ObjectVersionChecker(
  709             get_masakari_objects())
  710         fingerprints = checker.get_hashes()
  711 
  712         if os.getenv('GENERATE_HASHES'):
  713             open('object_hashes.txt', 'w').write(
  714                 pprint.pformat(fingerprints))
  715             raise test.TestingException(
  716                 'Generated hashes in object_hashes.txt')
  717 
  718         expected, actual = checker.test_hashes(object_data)
  719         self.assertEqual(expected, actual,
  720                          'Some objects have changed; please make sure the '
  721                          'versions have been bumped, and then update their '
  722                          'hashes here.')
  723 
  724     def test_obj_make_compatible(self):
  725         base.MasakariObjectRegistry.register(segment.FailoverSegment)
  726 
  727         # Iterate all object classes and verify that we can run
  728         # obj_make_compatible with every older version than current.
  729         # This doesn't actually test the data conversions, but it at least
  730         # makes sure the method doesn't blow up on something basic like
  731         # expecting the wrong version format.
  732 
  733         # Hold a dictionary of args/kwargs that need to get passed into
  734         # __init__() for specific classes. The key in the dictionary is
  735         # the obj_class that needs the init args/kwargs.
  736         init_args = fake_args.init_args
  737         init_kwargs = fake_args.init_kwargs
  738 
  739         checker = fixture.ObjectVersionChecker(
  740             base.MasakariObjectRegistry.obj_classes())
  741         checker.test_compatibility_routines(use_manifest=True,
  742                                             init_args=init_args,
  743                                             init_kwargs=init_kwargs)
  744 
  745     def test_list_obj_make_compatible(self):
  746         @base.MasakariObjectRegistry.register_if(False)
  747         class TestObj(base.MasakariObject):
  748             VERSION = '1.4'
  749             fields = {'foo': fields.IntegerField()}
  750 
  751         @base.MasakariObjectRegistry.register_if(False)
  752         class TestListObj(base.ObjectListBase, base.MasakariObject):
  753             VERSION = '1.5'
  754             fields = {'objects': fields.ListOfObjectsField('TestObj')}
  755             obj_relationships = {
  756                 'objects': [('1.0', '1.1'), ('1.1', '1.2'),
  757                             ('1.3', '1.3'), ('1.5', '1.4')]
  758             }
  759 
  760         my_list = TestListObj()
  761         my_obj = TestObj(foo=1)
  762         my_list.objects = [my_obj]
  763         primitive = my_list.obj_to_primitive(target_version='1.5')
  764         primitive_data = primitive['masakari_object.data']
  765         obj_primitive = my_obj.obj_to_primitive(target_version='1.4')
  766         obj_primitive_data = obj_primitive['masakari_object.data']
  767         with mock.patch.object(TestObj, 'obj_make_compatible') as comp:
  768             my_list.obj_make_compatible(primitive_data, '1.1')
  769             comp.assert_called_with(obj_primitive_data,
  770                                     '1.2')
  771 
  772     def test_list_obj_make_compatible_when_no_objects(self):
  773         # Test to make sure obj_make_compatible works with no 'objects'
  774         # If a List object ever has a version that did not contain the
  775         # 'objects' key, we need to make sure converting back to that version
  776         # doesn't cause backporting problems.
  777         @base.MasakariObjectRegistry.register_if(False)
  778         class TestObj(base.MasakariObject):
  779             VERSION = '1.1'
  780             fields = {'foo': fields.IntegerField()}
  781 
  782         @base.MasakariObjectRegistry.register_if(False)
  783         class TestListObj(base.ObjectListBase, base.MasakariObject):
  784             VERSION = '1.1'
  785             fields = {'objects': fields.ListOfObjectsField('TestObj')}
  786             obj_relationships = {
  787                 'objects': [('1.1', '1.1')]
  788             }
  789 
  790         my_list = TestListObj()
  791         my_list.objects = [TestObj(foo=1)]
  792         primitive = my_list.obj_to_primitive(target_version='1.1')
  793         primitive_data = primitive['masakari_object.data']
  794         my_list.obj_make_compatible(primitive_data,
  795                                     target_version='1.0')
  796         self.assertNotIn('objects', primitive_data,
  797                          "List was backported to before 'objects' existed."
  798                          " 'objects' should not be in the primitive.")
  799 
  800 
  801 class TestObjEqualPrims(_BaseTestCase):
  802 
  803     def test_object_equal(self):
  804         obj1 = MyObj(foo=1, bar='goodbye')
  805         obj1.obj_reset_changes()
  806         obj2 = MyObj(foo=1, bar='goodbye')
  807         obj2.obj_reset_changes()
  808         obj2.bar = 'goodbye'
  809         # obj2 will be marked with field 'three' updated
  810         self.assertTrue(base.obj_equal_prims(obj1, obj2),
  811                         "Objects that differ only because one a is marked "
  812                         "as updated should be equal")
  813 
  814     def test_object_not_equal(self):
  815         obj1 = MyObj(foo=1, bar='goodbye')
  816         obj1.obj_reset_changes()
  817         obj2 = MyObj(foo=1, bar='hello')
  818         obj2.obj_reset_changes()
  819         self.assertFalse(base.obj_equal_prims(obj1, obj2),
  820                          "Objects that differ in any field "
  821                          "should not be equal")
  822 
  823     def test_object_ignore_equal(self):
  824         obj1 = MyObj(foo=1, bar='goodbye')
  825         obj1.obj_reset_changes()
  826         obj2 = MyObj(foo=1, bar='hello')
  827         obj2.obj_reset_changes()
  828         self.assertTrue(base.obj_equal_prims(obj1, obj2, ['bar']),
  829                         "Objects that only differ in an ignored field "
  830                         "should be equal")
  831 
  832 
  833 class TestObjMethodOverrides(test.NoDBTestCase):
  834     def test_obj_reset_changes(self):
  835         args = inspect.getargspec(base.MasakariObject.obj_reset_changes)
  836         obj_classes = base.MasakariObjectRegistry.obj_classes()
  837         for obj_name in obj_classes:
  838             obj_class = obj_classes[obj_name][0]
  839             self.assertEqual(args,
  840                     inspect.getargspec(obj_class.obj_reset_changes))