oslo.versionedobjects/oslo_versionedobjects/tests/test_fixture.py
Takashi Kajinami c50057eb8d Run pyupgrade to clean up Python 2 syntaxes
Update all .py source files by
 $ pyupgrade --py3-only $(git ls-files | grep ".py$")
to modernize the code according to Python 3 syntaxes.

Also add the pyupgrade hook to pre-commit to avoid merging additional
Python 2 syntaxes.

Change-Id: Ia14b0f9ed4268677a77b557f04495655162f040e
2024-10-24 10:13:23 +09:00

787 lines
32 KiB
Python

# Copyright 2015 IBM Corp.
#
# 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.
import copy
import datetime
import hashlib
import inspect
from unittest import mock
import iso8601
from oslo_versionedobjects import base
from oslo_versionedobjects import exception
from oslo_versionedobjects import fields
from oslo_versionedobjects import fixture
from oslo_versionedobjects import test
class MyObject(base.VersionedObject):
fields = {'diglett': fields.IntegerField()}
@base.remotable
def remotable_method(self):
pass
@classmethod
@base.remotable
def remotable_classmethod(cls):
pass
def non_remotable_method(self):
pass
@classmethod
def non_remotable_classmethod(cls):
pass
class MyObject2(base.VersionedObject):
pass
class MyExtraObject(base.VersionedObject):
pass
class TestObjectComparators(test.TestCase):
@base.VersionedObjectRegistry.register_if(False)
class MyComparedObject(base.VersionedObject):
fields = {'foo': fields.IntegerField(),
'bar': fields.IntegerField()}
@base.VersionedObjectRegistry.register_if(False)
class MyComparedObjectWithTZ(base.VersionedObject):
fields = {'tzfield': fields.DateTimeField()}
def test_compare_obj(self):
mock_test = mock.Mock()
mock_test.assertEqual = mock.Mock()
my_obj = self.MyComparedObject(foo=1, bar=2)
my_db_obj = {'foo': 1, 'bar': 2}
fixture.compare_obj(mock_test, my_obj, my_db_obj)
expected_calls = [(1, 1), (2, 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_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 = {}
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()
mock_test.assertEqual = mock.Mock()
my_obj = self.MyComparedObject(foo=1, bar=2)
my_db_obj = {'doo': 1, 'bar': 2}
subs = {'foo': 'doo'}
fixture.compare_obj(mock_test, my_obj, my_db_obj, subs=subs)
expected_calls = [(1, 1), (2, 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_allow_missing(self):
mock_test = mock.Mock()
mock_test.assertEqual = mock.Mock()
my_obj = self.MyComparedObject(foo=1)
my_db_obj = {'foo': 1, 'bar': 2}
ignores = ['bar']
fixture.compare_obj(mock_test, my_obj, my_db_obj,
allow_missing=ignores)
mock_test.assertEqual.assert_called_once_with(1, 1)
def test_compare_obj_with_comparators(self):
mock_test = mock.Mock()
mock_test.assertEqual = mock.Mock()
comparator = mock.Mock()
comp_dict = {'foo': comparator}
my_obj = self.MyComparedObject(foo=1, bar=2)
my_db_obj = {'foo': 1, 'bar': 2}
fixture.compare_obj(mock_test, my_obj, my_db_obj,
comparators=comp_dict)
comparator.assert_called_once_with(1, 1)
mock_test.assertEqual.assert_called_once_with(2, 2)
def test_compare_obj_with_dt(self):
mock_test = mock.Mock()
mock_test.assertEqual = mock.Mock()
dt = datetime.datetime(1955, 11, 5, tzinfo=iso8601.iso8601.UTC)
replaced_dt = dt.replace(tzinfo=None)
my_obj = self.MyComparedObjectWithTZ(tzfield=dt)
my_db_obj = {'tzfield': replaced_dt}
fixture.compare_obj(mock_test, my_obj, my_db_obj)
mock_test.assertEqual.assert_called_once_with(replaced_dt,
replaced_dt)
class FakeResource(base.VersionedObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'identifier': fields.Field(fields.Integer(), default=123)
}
class TestObjectVersionChecker(test.TestCase):
def setUp(self):
super().setUp()
objects = [MyObject, MyObject2, ]
self.obj_classes = {obj.__name__: [obj] for obj in objects}
self.ovc = fixture.ObjectVersionChecker(obj_classes=self.obj_classes)
def test_get_hashes(self):
# Make sure get_hashes retrieves the fingerprint of all objects
fp = 'ashketchum'
with mock.patch.object(self.ovc, '_get_fingerprint') as mock_gf:
mock_gf.return_value = fp
actual = self.ovc.get_hashes()
expected = self._generate_hashes(self.obj_classes, fp)
self.assertEqual(expected, actual, "ObjectVersionChecker is not "
"getting the fingerprints of all registered "
"objects.")
def test_get_hashes_with_extra_data(self):
# Make sure get_hashes uses the extra_data_func
fp = 'garyoak'
mock_func = mock.MagicMock()
with mock.patch.object(self.ovc, '_get_fingerprint') as mock_gf:
mock_gf.return_value = fp
actual = self.ovc.get_hashes(extra_data_func=mock_func)
expected = self._generate_hashes(self.obj_classes, fp)
expected_calls = [((name,), {'extra_data_func': mock_func})
for name in self.obj_classes.keys()]
self.assertEqual(expected, actual, "ObjectVersionChecker is not "
"getting the fingerprints of all registered "
"objects.")
self.assertEqual(len(expected_calls), len(mock_gf.call_args_list),
"get_hashes() did not call get the fingerprints of "
"all objects in the registry.")
for call in expected_calls:
self.assertIn(call, mock_gf.call_args_list,
"get_hashes() did not call _get_fingerprint()"
"correctly.")
def test_test_hashes_none_changed(self):
# Make sure test_hashes() generates an empty dictionary when
# there are no objects that have changed
fp = 'pikachu'
hashes = self._generate_hashes(self.obj_classes, fp)
with mock.patch.object(self.ovc, 'get_hashes') as mock_gh:
mock_gh.return_value = hashes
# I'm so sorry, but they have to be named this way
actual_expected, actual_actual = self.ovc.test_hashes(hashes)
expected_expected = expected_actual = {}
self.assertEqual(expected_expected, actual_expected, "There are no "
"objects changed, so the 'expected' return value "
"should contain no objects.")
self.assertEqual(expected_actual, actual_actual, "There are no "
"objects changed, so the 'actual' return value "
"should contain no objects.")
def test_test_hashes_class_not_added(self):
# Make sure the expected and actual values differ when a class
# was added to the registry, but not the static dictionary
fp = 'gyrados'
new_classes = copy.copy(self.obj_classes)
self._add_class(new_classes, MyExtraObject)
expected_hashes = self._generate_hashes(self.obj_classes, fp)
actual_hashes = self._generate_hashes(new_classes, fp)
with mock.patch.object(self.ovc, 'get_hashes') as mock_gh:
mock_gh.return_value = actual_hashes
actual_exp, actual_act = self.ovc.test_hashes(expected_hashes)
expected_expected = {MyExtraObject.__name__: None}
expected_actual = {MyExtraObject.__name__: fp}
self.assertEqual(expected_expected, actual_exp, "Expected hashes "
"should not contain the fingerprint of the class "
"that has not been added to the expected hash "
"dictionary.")
self.assertEqual(expected_actual, actual_act, "The actual hash "
"should contain the class that was added to the "
"registry.")
def test_test_hashes_new_fp_incorrect(self):
# Make sure the expected and actual values differ when a fingerprint
# was changed, but the static dictionary was not updated
fp1 = 'beedrill'
fp2 = 'snorlax'
expected_hashes = self._generate_hashes(self.obj_classes, fp1)
actual_hashes = copy.copy(expected_hashes)
actual_hashes[MyObject.__name__] = fp2
with mock.patch.object(self.ovc, 'get_hashes') as mock_gh:
mock_gh.return_value = actual_hashes
actual_exp, actual_act = self.ovc.test_hashes(expected_hashes)
expected_expected = {MyObject.__name__: fp1}
expected_actual = {MyObject.__name__: fp2}
self.assertEqual(expected_expected, actual_exp, "Expected hashes "
"should contain the updated object with the old "
"hash.")
self.assertEqual(expected_actual, actual_act, "Actual hashes "
"should contain the updated object with the new "
"hash.")
def test_test_hashes_passes_extra_func(self):
# Make sure that test_hashes passes the extra_func to get_hashes
mock_extra_func = mock.Mock()
with mock.patch.object(self.ovc, 'get_hashes') as mock_get_hashes:
self.ovc.test_hashes({}, extra_data_func=mock_extra_func)
mock_get_hashes.assert_called_once_with(
extra_data_func=mock_extra_func)
def test_get_dependency_tree(self):
# Make sure get_dependency_tree() gets the dependencies of all
# objects in the registry
with mock.patch.object(self.ovc, '_get_dependencies') as mock_gd:
self.ovc.get_dependency_tree()
expected_calls = [(({}, MyObject),), (({}, MyObject2),)]
self.assertEqual(2, len(mock_gd.call_args_list),
"get_dependency_tree() tried to get the dependencies"
" too many times.")
for call in expected_calls:
self.assertIn(call, mock_gd.call_args_list,
"get_dependency_tree() did not get the dependencies "
"of the objects correctly.")
def test_test_relationships_none_changed(self):
# Make sure test_relationships() generates an empty dictionary when
# no relationships have been changed
dep_tree = {}
# tree will be {'MyObject': {'MyObject2': '1.0'}}
self._add_dependency(MyObject, MyObject2, dep_tree)
with mock.patch.object(self.ovc, 'get_dependency_tree') as mock_gdt:
mock_gdt.return_value = dep_tree
actual_exp, actual_act = self.ovc.test_relationships(dep_tree)
expected_expected = expected_actual = {}
self.assertEqual(expected_expected, actual_exp, "There are no "
"objects changed, so the 'expected' return value "
"should contain no objects.")
self.assertEqual(expected_actual, actual_act, "There are no "
"objects changed, so the 'actual' return value "
"should contain no objects.")
def test_test_relationships_rel_added(self):
# Make sure expected and actual relationships differ if a
# relationship is added to a class
exp_tree = {}
actual_tree = {}
self._add_dependency(MyObject, MyObject2, exp_tree)
self._add_dependency(MyObject, MyObject2, actual_tree)
self._add_dependency(MyObject, MyExtraObject, actual_tree)
with mock.patch.object(self.ovc, 'get_dependency_tree') as mock_gdt:
mock_gdt.return_value = actual_tree
actual_exp, actual_act = self.ovc.test_relationships(exp_tree)
expected_expected = {'MyObject': {'MyObject2': '1.0'}}
expected_actual = {'MyObject': {'MyObject2': '1.0',
'MyExtraObject': '1.0'}}
self.assertEqual(expected_expected, actual_exp, "The expected "
"relationship tree is not being built from changes "
"correctly.")
self.assertEqual(expected_actual, actual_act, "The actual "
"relationship tree is not being built from changes "
"correctly.")
def test_test_relationships_class_added(self):
# Make sure expected and actual relationships differ if a new
# class is added to the relationship tree
exp_tree = {}
actual_tree = {}
self._add_dependency(MyObject, MyObject2, exp_tree)
self._add_dependency(MyObject, MyObject2, actual_tree)
self._add_dependency(MyObject2, MyExtraObject, actual_tree)
with mock.patch.object(self.ovc, 'get_dependency_tree') as mock_gdt:
mock_gdt.return_value = actual_tree
actual_exp, actual_act = self.ovc.test_relationships(exp_tree)
expected_expected = {'MyObject2': None}
expected_actual = {'MyObject2': {'MyExtraObject': '1.0'}}
self.assertEqual(expected_expected, actual_exp, "The expected "
"relationship tree is not being built from changes "
"correctly.")
self.assertEqual(expected_actual, actual_act, "The actual "
"relationship tree is not being built from changes "
"correctly.")
def test_test_compatibility_routines(self):
# Make sure test_compatibility_routines() checks the object
# compatibility of all objects in the registry
del self.ovc.obj_classes[MyObject2.__name__]
with mock.patch.object(self.ovc, '_test_object_compatibility') as toc:
self.ovc.test_compatibility_routines()
toc.assert_called_once_with(MyObject, manifest=None, init_args=[],
init_kwargs={})
def test_test_compatibility_routines_with_manifest(self):
# Make sure test_compatibility_routines() uses the version manifest
del self.ovc.obj_classes[MyObject2.__name__]
man = {'who': 'cares'}
with mock.patch.object(self.ovc, '_test_object_compatibility') as toc:
with mock.patch('oslo_versionedobjects.base'
'.obj_tree_get_versions') as otgv:
otgv.return_value = man
self.ovc.test_compatibility_routines(use_manifest=True)
otgv.assert_called_once_with(MyObject.__name__)
toc.assert_called_once_with(MyObject, manifest=man, init_args=[],
init_kwargs={})
def test_test_compatibility_routines_with_args_kwargs(self):
# Make sure test_compatibility_routines() uses init args/kwargs
del self.ovc.obj_classes[MyObject2.__name__]
init_args = {MyObject: [1]}
init_kwargs = {MyObject: {'foo': 'bar'}}
with mock.patch.object(self.ovc, '_test_object_compatibility') as toc:
self.ovc.test_compatibility_routines(init_args=init_args,
init_kwargs=init_kwargs)
toc.assert_called_once_with(MyObject, manifest=None, init_args=[1],
init_kwargs={'foo': 'bar'})
def test_test_relationships_in_order(self):
# Make sure test_relationships_in_order() tests the relationships
# of all objects in the registry
with mock.patch.object(self.ovc,
'_test_relationships_in_order') as mock_tr:
self.ovc.test_relationships_in_order()
expected_calls = [((MyObject,),), ((MyObject2,),)]
self.assertEqual(2, len(mock_tr.call_args_list),
"test_relationships_in_order() tested too many "
"relationships.")
for call in expected_calls:
self.assertIn(call, mock_tr.call_args_list,
"test_relationships_in_order() did not test the "
"relationships of the individual objects "
"correctly.")
def test_test_relationships_in_order_positive(self):
# Make sure a correct relationship ordering doesn't blow up
rels = {'bellsprout': [('1.0', '1.0'), ('1.1', '1.2'),
('1.3', '1.3')]}
MyObject.obj_relationships = rels
self.ovc._test_relationships_in_order(MyObject)
def test_test_relationships_in_order_negative(self):
# Make sure an out-of-order relationship does blow up
rels = {'rattata': [('1.0', '1.0'), ('1.1', '1.2'),
('1.3', '1.1')]}
MyObject.obj_relationships = rels
self.assertRaises(AssertionError,
self.ovc._test_relationships_in_order, MyObject)
def test_find_remotable_method(self):
# Make sure we can find a remotable method on an object
method = self.ovc._find_remotable_method(MyObject,
MyObject.remotable_method)
self.assertEqual(MyObject.remotable_method.original_fn,
method,
"_find_remotable_method() did not find the remotable"
" method of MyObject.")
def test_find_remotable_method_classmethod(self):
# Make sure we can find a remotable classmethod on an object
rcm = MyObject.remotable_classmethod
method = self.ovc._find_remotable_method(MyObject, rcm)
expected = rcm.__get__(None, MyObject).original_fn
self.assertEqual(expected, method, "_find_remotable_method() did not "
"find the remotable classmethod.")
def test_find_remotable_method_non_remotable_method(self):
# Make sure nothing is found when we have only a non-remotable method
nrm = MyObject.non_remotable_method
method = self.ovc._find_remotable_method(MyObject, nrm)
self.assertIsNone(method, "_find_remotable_method() found a method "
"that isn't remotable.")
def test_find_remotable_method_non_remotable_classmethod(self):
# Make sure we don't find a non-remotable classmethod
nrcm = MyObject.non_remotable_classmethod
method = self.ovc._find_remotable_method(MyObject, nrcm)
self.assertIsNone(method, "_find_remotable_method() found a method "
"that isn't remotable.")
def test_get_fingerprint(self):
# Make sure _get_fingerprint() generates a consistent fingerprint
MyObject.VERSION = '1.1'
argspec = 'vulpix'
with mock.patch.object(fixture, 'get_method_spec') as mock_gas:
mock_gas.return_value = argspec
fp = self.ovc._get_fingerprint(MyObject.__name__)
exp_fields = sorted(list(MyObject.fields.items()))
exp_methods = sorted([('remotable_method', argspec),
('remotable_classmethod', argspec)])
expected_relevant_data = (exp_fields, exp_methods)
# NOTE(hberaud) the following hashlib usage will emit a bandit
# warning. It can be solved by passing `usedforsecurity=False` to
# the md5 function, however, this parameter was introduced with py39
# so passing it will break py38 unittest. I'd suggest to ignore this
# bandit rule while py38 is in our supported runtimes.
expected_hash = hashlib.md5(bytes(repr(
expected_relevant_data).encode())).hexdigest() # nosec
expected_fp = '{}-{}'.format(MyObject.VERSION, expected_hash)
self.assertEqual(expected_fp, fp, "_get_fingerprint() did not "
"generate a correct fingerprint.")
def test_get_fingerprint_with_child_versions(self):
# Make sure _get_fingerprint() generates a consistent fingerprint
# when child_versions are present
child_versions = {'1.0': '1.0', '1.1': '1.1'}
MyObject.VERSION = '1.1'
MyObject.child_versions = child_versions
argspec = 'onix'
with mock.patch.object(fixture, 'get_method_spec') as mock_gas:
mock_gas.return_value = argspec
fp = self.ovc._get_fingerprint(MyObject.__name__)
exp_fields = sorted(list(MyObject.fields.items()))
exp_methods = sorted([('remotable_method', argspec),
('remotable_classmethod', argspec)])
exp_child_versions = fixture.OsloOrderedDict(
sorted(child_versions.items())
)
exp_relevant_data = (exp_fields, exp_methods, exp_child_versions)
# NOTE(hberaud) the following hashlib usage will emit a bandit
# warning. It can be solved by passing `usedforsecurity=False` to
# the md5 function, however, this parameter was introduced with py39
# so passing it will break py38 unittest. I'd suggest to ignore this
# bandit rule while py38 is in our supported runtimes.
expected_hash = hashlib.md5(bytes(repr(
exp_relevant_data).encode())).hexdigest() # nosec
expected_fp = '{}-{}'.format(MyObject.VERSION, expected_hash)
self.assertEqual(expected_fp, fp, "_get_fingerprint() did not "
"generate a correct fingerprint.")
def test_get_fingerprint_with_extra_data(self):
# Make sure _get_fingerprint() uses extra_data_func when it is
# supplied
class ExtraDataObj(base.VersionedObject):
pass
def get_data(obj_class):
return (obj_class,)
ExtraDataObj.VERSION = '1.1'
argspec = 'cubone'
self._add_class(self.obj_classes, ExtraDataObj)
with mock.patch.object(fixture, 'get_method_spec') as mock_gas:
mock_gas.return_value = argspec
fp = self.ovc._get_fingerprint(ExtraDataObj.__name__,
extra_data_func=get_data)
exp_fields = []
exp_methods = []
exp_extra_data = ExtraDataObj
exp_relevant_data = (exp_fields, exp_methods, exp_extra_data)
# NOTE(hberaud) the following hashlib usage will emit a bandit
# warning. It can be solved by passing `usedforsecurity=False` to
# the md5 function, however, this parameter was introduced with py39
# so passing it will break py38 unittest. I'd suggest to ignore this
# bandit rule while py38 is in our supported runtimes.
expected_hash = hashlib.md5(bytes(repr(
exp_relevant_data).encode())).hexdigest() # nosec
expected_fp = '{}-{}'.format(ExtraDataObj.VERSION, expected_hash)
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
self._add_class(self.obj_classes, MyExtraObject)
MyObject.fields['subob'] = fields.ObjectField('MyExtraObject')
MyExtraObject.VERSION = '1.0'
tree = {}
self.ovc._get_dependencies(tree, MyObject)
expected_tree = {'MyObject': {'MyExtraObject': '1.0'}}
self.assertEqual(expected_tree, tree, "_get_dependencies() did "
"not generate a correct dependency tree.")
def test_test_object_compatibility(self):
# Make sure _test_object_compatibility() tests obj_to_primitive()
# on each prior version to the current version
to_prim = mock.MagicMock(spec=callable)
MyObject.VERSION = '1.1'
MyObject.obj_to_primitive = to_prim
self.ovc._test_object_compatibility(MyObject)
expected_calls = [((), {'target_version': '1.0'}),
((), {'target_version': '1.1'})]
self.assertEqual(expected_calls, to_prim.call_args_list,
"_test_object_compatibility() did not test "
"obj_to_primitive() on the correct target versions")
def test_test_object_compatibility_args_kwargs(self):
# Make sure _test_object_compatibility() tests obj_to_primitive()
# with the correct args and kwargs to init
to_prim = mock.MagicMock(spec=callable)
MyObject.obj_to_primitive = to_prim
MyObject.VERSION = '1.1'
args = [1]
kwargs = {'foo': 'bar'}
with mock.patch.object(MyObject, '__init__',
return_value=None) as mock_init:
self.ovc._test_object_compatibility(MyObject, init_args=args,
init_kwargs=kwargs)
expected_init = ((1,), {'foo': 'bar'})
expected_init_calls = [expected_init, expected_init]
self.assertEqual(expected_init_calls, mock_init.call_args_list,
"_test_object_compatibility() did not call "
"__init__() properly on the object")
expected_to_prim = [((), {'target_version': '1.0'}),
((), {'target_version': '1.1'})]
self.assertEqual(expected_to_prim, to_prim.call_args_list,
"_test_object_compatibility() did not test "
"obj_to_primitive() on the correct target versions")
def _add_class(self, obj_classes, cls):
obj_classes[cls.__name__] = [cls]
def _generate_hashes(self, classes, fp):
# Generate hashes for classes, giving fp as the fingerprint
# for all classes
return {cls: fp for cls in classes.keys()}
def _add_dependency(self, parent_cls, child_cls, tree):
# Add a dependency to the tree with the parent class holding
# version 1.0 of the given child class
deps = tree.get(parent_cls.__name__, {})
deps[child_cls.__name__] = '1.0'
tree[parent_cls.__name__] = deps
class TestVersionedObjectRegistryFixture(test.TestCase):
primitive = {'versioned_object.name': 'FakeResource',
'versioned_object.namespace': 'versionedobjects',
'versioned_object.version': '1.0',
'versioned_object.data': {'identifier': 123}}
def test_object_registered_temporarily(self):
# Test object that has not been registered
self.assertRaises(
exception.UnsupportedObjectError,
FakeResource.obj_from_primitive,
self.primitive)
with fixture.VersionedObjectRegistryFixture() as obj_registry:
# Register object locally
obj_registry.setUp()
obj_registry.register(FakeResource)
# Test object has now been registered
obj = FakeResource.obj_from_primitive(
self.primitive)
self.assertEqual(obj.identifier, 123)
self.assertEqual('1.0', obj.VERSION)
# Test object that is no longer registered
self.assertRaises(
exception.UnsupportedObjectError,
FakeResource.obj_from_primitive,
self.primitive)
class TestStableObjectJsonFixture(test.TestCase):
def test_changes_sort(self):
@base.VersionedObjectRegistry.register_if(False)
class TestObject(base.VersionedObject):
fields = {'z': fields.StringField(),
'a': fields.StringField()}
def obj_what_changed(self):
return ['z', 'a']
obj = TestObject(a='foo', z='bar')
self.assertEqual(['z', 'a'],
obj.obj_to_primitive()['versioned_object.changes'])
with fixture.StableObjectJsonFixture():
self.assertEqual(
['a', 'z'],
obj.obj_to_primitive()['versioned_object.changes'])
class TestMethodSpec(test.TestCase):
def setUp(self):
super().setUp()
def test_method1(a, b, kw1=123, **kwargs):
pass
def test_method2(a, b, *args):
pass
def test_method3(a, b, *args, kw1=123, **kwargs):
pass
self._test_method1 = test_method1
self._test_method2 = test_method2
self._test_method3 = test_method3
def test_method_spec_compat(self):
self.assertEqual(fixture.CompatArgSpec(args=['a', 'b', 'kw1'],
varargs=None,
keywords='kwargs',
defaults=(123,)),
fixture.get_method_spec(self._test_method1))
self.assertEqual(fixture.CompatArgSpec(args=['a', 'b'],
varargs='args',
keywords=None,
defaults=None),
fixture.get_method_spec(self._test_method2))
self.assertEqual(inspect.getfullargspec(self._test_method3),
fixture.get_method_spec(self._test_method3))