oslo.versionedobjects/oslo_versionedobjects/tests/test_fields.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

1270 lines
48 KiB
Python

# Copyright 2013 Red Hat, Inc.
#
# 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 datetime
from unittest import mock
import warnings
import iso8601
import netaddr
import testtools
from oslo_versionedobjects import _utils
from oslo_versionedobjects import base as obj_base
from oslo_versionedobjects import exception
from oslo_versionedobjects import fields
from oslo_versionedobjects import test
class FakeFieldType(fields.FieldType):
def coerce(self, obj, attr, value):
return '*%s*' % value
def to_primitive(self, obj, attr, value):
return '!%s!' % value
def from_primitive(self, obj, attr, value):
return value[1:-1]
def get_schema(self):
return {'type': ['foo']}
class FakeEnum(fields.Enum):
FROG = "frog"
PLATYPUS = "platypus"
ALLIGATOR = "alligator"
ALL = (FROG, PLATYPUS, ALLIGATOR)
def __init__(self, **kwargs):
super().__init__(valid_values=FakeEnum.ALL,
**kwargs)
class FakeEnumAlt(fields.Enum):
FROG = "frog"
PLATYPUS = "platypus"
AARDVARK = "aardvark"
ALL = {FROG, PLATYPUS, AARDVARK}
def __init__(self, **kwargs):
super().__init__(valid_values=FakeEnumAlt.ALL,
**kwargs)
class FakeEnumField(fields.BaseEnumField):
AUTO_TYPE = FakeEnum()
class FakeStateMachineField(fields.StateMachine):
ACTIVE = 'ACTIVE'
PENDING = 'PENDING'
ERROR = 'ERROR'
ALLOWED_TRANSITIONS = {
ACTIVE: {
PENDING,
ERROR
},
PENDING: {
ACTIVE,
ERROR
},
ERROR: {
PENDING,
},
}
_TYPES = (ACTIVE, PENDING, ERROR)
def __init__(self, **kwargs):
super().__init__(self._TYPES, **kwargs)
class FakeEnumAltField(fields.BaseEnumField):
AUTO_TYPE = FakeEnumAlt()
class TestFieldType(test.TestCase):
def test_get_schema(self):
self.assertRaises(NotImplementedError, fields.FieldType().get_schema)
class TestField(test.TestCase):
def setUp(self):
super().setUp()
self.field = fields.Field(FakeFieldType())
self.coerce_good_values = [('foo', '*foo*')]
self.coerce_bad_values = []
self.to_primitive_values = [('foo', '!foo!')]
self.from_primitive_values = [('!foo!', 'foo')]
def test_coerce_good_values(self):
for in_val, out_val in self.coerce_good_values:
self.assertEqual(out_val, self.field.coerce('obj', 'attr', in_val))
def test_coerce_bad_values(self):
for in_val in self.coerce_bad_values:
self.assertRaises((TypeError, ValueError),
self.field.coerce, 'obj', 'attr', in_val)
def test_to_primitive(self):
for in_val, prim_val in self.to_primitive_values:
self.assertEqual(prim_val, self.field.to_primitive('obj', 'attr',
in_val))
def test_from_primitive(self):
class ObjectLikeThing:
_context = 'context'
for prim_val, out_val in self.from_primitive_values:
self.assertEqual(out_val, self.field.from_primitive(
ObjectLikeThing, 'attr', prim_val))
def test_stringify(self):
self.assertEqual('123', self.field.stringify(123))
class TestSchema(test.TestCase):
def setUp(self):
super().setUp()
self.field = fields.Field(FakeFieldType(), nullable=True,
default='', read_only=False)
def test_get_schema(self):
self.assertEqual({'type': ['foo', 'null'], 'default': '',
'readonly': False},
self.field.get_schema())
class TestString(TestField):
def setUp(self):
super().setUp()
self.field = fields.StringField()
self.coerce_good_values = [
('foo', 'foo'), (1, '1'), (1.0, '1.0'), (True, 'True')]
self.coerce_bad_values = [None]
self.to_primitive_values = self.coerce_good_values[0:1]
self.from_primitive_values = self.coerce_good_values[0:1]
def test_stringify(self):
self.assertEqual("'123'", self.field.stringify(123))
def test_fieldtype_get_schema(self):
self.assertEqual({'type': ['string']}, self.field._type.get_schema())
def test_get_schema(self):
self.assertEqual({'type': ['string'], 'readonly': False},
self.field.get_schema())
class TestSensitiveString(TestString):
def setUp(self):
super().setUp()
self.field = fields.SensitiveStringField()
def test_stringify(self):
payload = """{'admin_password':'mypassword'}"""
expected = """'{'admin_password':'***'}'"""
self.assertEqual(expected, self.field.stringify(payload))
class TestVersionPredicate(TestString):
def setUp(self):
super().setUp()
self.field = fields.VersionPredicateField()
self.coerce_good_values = [('>=1.0', '>=1.0'),
('==1.1', '==1.1'),
('<1.1.0', '<1.1.0')]
self.coerce_bad_values = ['1', 'foo', '>1', 1.0, '1.0', '=1.0']
self.to_primitive_values = self.coerce_good_values[0:1]
self.from_primitive_values = self.coerce_good_values[0:1]
class TestMACAddress(TestField):
def setUp(self):
super().setUp()
self.field = fields.MACAddressField()
self.coerce_good_values = [
('c6:df:11:a5:c8:5d', 'c6:df:11:a5:c8:5d'),
('C6:DF:11:A5:C8:5D', 'c6:df:11:a5:c8:5d'),
('c6:df:11:a5:c8:5d', 'c6:df:11:a5:c8:5d'),
('C6:DF:11:A5:C8:5D', 'c6:df:11:a5:c8:5d'),
]
self.coerce_bad_values = [
'C6:DF:11:A5:C8', # Too short
'C6:DF:11:A5:C8:5D:D7', # Too long
'C6:DF:11:A5:C8:KD', # Bad octal
1123123, # Number
{}, # dict
]
self.to_primitive_values = self.coerce_good_values[0:1]
self.from_primitive_values = self.coerce_good_values[0:1]
def test_get_schema(self):
schema = self.field.get_schema()
self.assertEqual(['string'], schema['type'])
self.assertEqual(False, schema['readonly'])
pattern = schema['pattern']
for _, valid_val in self.coerce_good_values:
self.assertRegex(valid_val, pattern)
invalid_vals = [x for x in self.coerce_bad_values
if isinstance(x, str)]
for invalid_val in invalid_vals:
self.assertNotRegex(invalid_val, pattern)
class TestPCIAddress(TestField):
def setUp(self):
super().setUp()
self.field = fields.PCIAddressField()
self.coerce_good_values = [
('0000:02:00.0', '0000:02:00.0'),
('FFFF:FF:1F.7', 'ffff:ff:1f.7'),
('fFfF:fF:1F.7', 'ffff:ff:1f.7'),
]
self.coerce_bad_values = [
'000:02:00.0', # Too short
'00000:02:00.0', # Too long
'FFFF:FF:2F.7', # Bad slot
'FFFF:GF:1F.7', # Bad octal
1123123, # Number
{}, # dict
]
self.to_primitive_values = self.coerce_good_values[0:1]
self.from_primitive_values = self.coerce_good_values[0:1]
def test_get_schema(self):
schema = self.field.get_schema()
self.assertEqual(['string'], schema['type'])
self.assertEqual(False, schema['readonly'])
pattern = schema['pattern']
for _, valid_val in self.coerce_good_values:
self.assertRegex(valid_val, pattern)
invalid_vals = [x for x in self.coerce_bad_values
if isinstance(x, str)]
for invalid_val in invalid_vals:
self.assertNotRegex(invalid_val, pattern)
class TestUUID(TestField):
def setUp(self):
super().setUp()
self.field = fields.UUIDField()
self.coerce_good_values = [
('da66a411-af0e-4829-9b67-475017ddd152',
'da66a411-af0e-4829-9b67-475017ddd152'),
('da66a411af0e48299b67475017ddd152',
'da66a411af0e48299b67475017ddd152'),
('DA66A411-AF0E-4829-9B67-475017DDD152',
'DA66A411-AF0E-4829-9B67-475017DDD152'),
('DA66A411AF0E48299b67475017DDD152',
'DA66A411AF0E48299b67475017DDD152'),
# These values are included to ensure there is not change in
# behaviour - only when we can remove the old UUID behaviour can
# we add these to the "self.coerce_bad_values" list
('da66a411-af0e-4829-9b67',
'da66a411-af0e-4829-9b67'),
('da66a411-af0e-4829-9b67-475017ddd152548999',
'da66a411-af0e-4829-9b67-475017ddd152548999'),
('da66a411-af0e-4829-9b67-475017ddz152',
'da66a411-af0e-4829-9b67-475017ddz152'),
('fake_uuid', 'fake_uuid'),
('fake_uāid', 'fake_uāid'),
(b'fake_u\xe1id'.decode('latin_1'),
b'fake_u\xe1id'.decode('latin_1')),
('1', '1'),
(1, '1')
]
self.to_primitive_values = self.coerce_good_values[0:1]
self.from_primitive_values = self.coerce_good_values[0:1]
@mock.patch('warnings.warn')
def test_coerce_good_values(self, mock_warn):
super().test_coerce_good_values()
mock_warn.assert_has_calls(
[mock.call(mock.ANY, FutureWarning)] * 8,
)
def test_validation_warning_can_be_escalated_to_exception(self):
warnings.filterwarnings(action='error')
self.assertRaises(FutureWarning, self.field.coerce, 'obj', 'attr',
'not a uuid')
def test_get_schema(self):
field = fields.UUIDField()
schema = field.get_schema()
self.assertEqual(['string'], schema['type'])
self.assertEqual(False, schema['readonly'])
pattern = schema['pattern']
for _, valid_val in self.coerce_good_values[:4]:
self.assertRegex(valid_val, pattern)
invalid_vals = [x for x in self.coerce_bad_values
if isinstance(x, str)]
for invalid_val in invalid_vals:
self.assertNotRegex(invalid_val, pattern)
class TestBaseEnum(TestField):
def setUp(self):
super().setUp()
self.field = FakeEnumField()
self.coerce_good_values = [('frog', 'frog'),
('platypus', 'platypus'),
('alligator', 'alligator')]
self.coerce_bad_values = ['aardvark', 'wookie']
self.to_primitive_values = self.coerce_good_values[0:1]
self.from_primitive_values = self.coerce_good_values[0:1]
def test_stringify(self):
self.assertEqual("'platypus'", self.field.stringify('platypus'))
def test_stringify_invalid(self):
self.assertRaises(ValueError, self.field.stringify, 'aardvark')
def test_fingerprint(self):
# Notes(yjiang5): make sure changing valid_value will be detected
# in test_objects.test_versions
field1 = FakeEnumField()
field2 = FakeEnumAltField()
self.assertNotEqual(str(field1), str(field2))
def test_valid_values(self):
self.assertEqual(self.field.valid_values,
FakeEnum.ALL)
def test_valid_values_keeps_type(self):
self.assertIsInstance(self.field.valid_values, tuple)
self.assertIsInstance(FakeEnumAltField().valid_values, set)
class TestEnum(TestField):
def setUp(self):
super().setUp()
self.field = fields.EnumField(
valid_values=['foo', 'bar', 1, True])
self.coerce_good_values = [('foo', 'foo'), (1, '1'), (True, 'True')]
self.coerce_bad_values = ['boo', 2, False]
self.to_primitive_values = self.coerce_good_values[0:1]
self.from_primitive_values = self.coerce_good_values[0:1]
def test_stringify(self):
self.assertEqual("'foo'", self.field.stringify('foo'))
def test_stringify_invalid(self):
self.assertRaises(ValueError, self.field.stringify, '123')
def test_fieldtype_get_schema(self):
self.assertEqual({'type': ['string'], 'enum': ["foo", "bar", 1, True]},
self.field._type.get_schema())
def test_get_schema(self):
self.assertEqual({'type': ['string'], 'enum': ["foo", "bar", 1, True],
'readonly': False}, self.field.get_schema())
def test_fingerprint(self):
# Notes(yjiang5): make sure changing valid_value will be detected
# in test_objects.test_versions
field1 = fields.EnumField(valid_values=['foo', 'bar'])
field2 = fields.EnumField(valid_values=['foo', 'bar1'])
self.assertNotEqual(str(field1), str(field2))
def test_missing_valid_values(self):
self.assertRaises(exception.EnumRequiresValidValuesError,
fields.EnumField, None)
def test_empty_valid_values(self):
self.assertRaises(exception.EnumRequiresValidValuesError,
fields.EnumField, [])
def test_non_iterable_valid_values(self):
self.assertRaises(exception.EnumValidValuesInvalidError,
fields.EnumField, True)
def test_enum_subclass_check(self):
def _test():
class BrokenEnumField(fields.BaseEnumField):
AUTO_TYPE = int
BrokenEnumField()
self.assertRaises(exception.EnumFieldInvalid, _test)
class TestStateMachine(TestField):
def test_good_transitions(self):
@obj_base.VersionedObjectRegistry.register
class AnObject(obj_base.VersionedObject):
fields = {
'status': FakeStateMachineField(),
}
obj = AnObject()
obj.status = FakeStateMachineField.ACTIVE
obj.status = FakeStateMachineField.PENDING
obj.status = FakeStateMachineField.ERROR
obj.status = FakeStateMachineField.PENDING
obj.status = FakeStateMachineField.ACTIVE
def test_bad_transitions(self):
@obj_base.VersionedObjectRegistry.register
class AnObject(obj_base.VersionedObject):
fields = {
'status': FakeStateMachineField(),
}
obj = AnObject(status='ERROR')
try:
obj.status = FakeStateMachineField.ACTIVE
except ValueError as e:
ex = e
else:
ex = None
self.assertIsNotNone(ex, 'Invalid transition failed to raise error')
self.assertEqual('AnObject.status is not allowed to transition out '
'of \'ERROR\' state to \'ACTIVE\' state, choose from '
'[\'PENDING\']',
str(ex))
def test_bad_initial_value(self):
@obj_base.VersionedObjectRegistry.register
class AnObject(obj_base.VersionedObject):
fields = {
'status': FakeStateMachineField(),
}
obj = AnObject()
with testtools.ExpectedException(ValueError):
obj.status = "FOO"
def test_bad_updated_value(self):
@obj_base.VersionedObjectRegistry.register
class AnObject(obj_base.VersionedObject):
fields = {
'status': FakeStateMachineField(),
}
obj = AnObject()
with testtools.ExpectedException(ValueError):
obj.status = FakeStateMachineField.ACTIVE
obj.status = "FOO"
class TestInteger(TestField):
def setUp(self):
super(TestField, self).setUp()
self.field = fields.IntegerField()
self.coerce_good_values = [(1, 1), ('1', 1)]
self.coerce_bad_values = ['foo', None]
self.to_primitive_values = self.coerce_good_values[0:1]
self.from_primitive_values = self.coerce_good_values[0:1]
def test_fieldtype_get_schema(self):
self.assertEqual({'type': ['integer']}, self.field._type.get_schema())
def test_get_schema(self):
self.assertEqual({'type': ['integer'], 'readonly': False},
self.field.get_schema())
class TestNonNegativeInteger(TestField):
def setUp(self):
super().setUp()
self.field = fields.NonNegativeIntegerField()
self.coerce_good_values = [(1, 1), ('1', 1)]
self.coerce_bad_values = ['-2', '4.2', 'foo', None]
self.to_primitive_values = self.coerce_good_values[0:1]
self.from_primitive_values = self.coerce_good_values[0:1]
def test_get_schema(self):
self.assertEqual({'type': ['integer'], 'readonly': False,
'minimum': 0}, self.field.get_schema())
class TestFloat(TestField):
def setUp(self):
super().setUp()
self.field = fields.FloatField()
self.coerce_good_values = [(1.1, 1.1), ('1.1', 1.1)]
self.coerce_bad_values = ['foo', None]
self.to_primitive_values = self.coerce_good_values[0:1]
self.from_primitive_values = self.coerce_good_values[0:1]
def test_fieldtype_get_schema(self):
self.assertEqual({'type': ['number']}, self.field._type.get_schema())
def test_get_schema(self):
self.assertEqual({'type': ['number'], 'readonly': False},
self.field.get_schema())
class TestNonNegativeFloat(TestField):
def setUp(self):
super().setUp()
self.field = fields.NonNegativeFloatField()
self.coerce_good_values = [(1.1, 1.1), ('1.1', 1.1)]
self.coerce_bad_values = ['-4.2', 'foo', None]
self.to_primitive_values = self.coerce_good_values[0:1]
self.from_primitive_values = self.coerce_good_values[0:1]
def test_get_schema(self):
self.assertEqual({'type': ['number'], 'readonly': False,
'minimum': 0}, self.field.get_schema())
class TestBoolean(TestField):
def setUp(self):
super(TestField, self).setUp()
self.field = fields.BooleanField()
self.coerce_good_values = [(True, True), (False, False), (1, True),
('foo', True), (0, False), ('', False)]
self.coerce_bad_values = []
self.to_primitive_values = self.coerce_good_values[0:2]
self.from_primitive_values = self.coerce_good_values[0:2]
def test_fieldtype_get_schema(self):
self.assertEqual({'type': ['boolean']}, self.field._type.get_schema())
def test_get_schema(self):
self.assertEqual({'type': ['boolean'], 'readonly': False},
self.field.get_schema())
class TestFlexibleBoolean(TestField):
def setUp(self):
super().setUp()
self.field = fields.FlexibleBooleanField()
self.coerce_good_values = [(True, True), (False, False),
("true", True), ("false", False),
("t", True), ("f", False),
("yes", True), ("no", False),
("y", True), ("n", False),
("on", True), ("off", False),
(1, True), (0, False),
('frog', False), ('', False)]
self.coerce_bad_values = []
self.to_primitive_values = self.coerce_good_values[0:2]
self.from_primitive_values = self.coerce_good_values[0:2]
class TestDateTime(TestField):
def setUp(self):
super().setUp()
self.dt = datetime.datetime(1955, 11, 5, tzinfo=iso8601.iso8601.UTC)
self.field = fields.DateTimeField()
self.coerce_good_values = [(self.dt, self.dt),
(_utils.isotime(self.dt), self.dt)]
self.coerce_bad_values = [1, 'foo']
self.to_primitive_values = [(self.dt, _utils.isotime(self.dt))]
self.from_primitive_values = [(_utils.isotime(self.dt), self.dt)]
def test_stringify(self):
self.assertEqual(
'1955-11-05T18:00:00Z',
self.field.stringify(
datetime.datetime(1955, 11, 5, 18, 0, 0,
tzinfo=iso8601.iso8601.UTC)))
def test_get_schema(self):
self.assertEqual({'type': ['string'], 'format': 'date-time',
'readonly': False},
self.field.get_schema())
class TestDateTimeNoTzinfo(TestField):
def setUp(self):
super().setUp()
self.dt = datetime.datetime(1955, 11, 5)
self.field = fields.DateTimeField(tzinfo_aware=False)
self.coerce_good_values = [(self.dt, self.dt),
(_utils.isotime(self.dt), self.dt)]
self.coerce_bad_values = [1, 'foo']
self.to_primitive_values = [(self.dt, _utils.isotime(self.dt))]
self.from_primitive_values = [
(
_utils.isotime(self.dt),
self.dt,
)
]
def test_stringify(self):
self.assertEqual(
'1955-11-05T18:00:00Z',
self.field.stringify(
datetime.datetime(1955, 11, 5, 18, 0, 0)))
class TestDict(TestField):
def setUp(self):
super().setUp()
self.field = fields.Field(fields.Dict(FakeFieldType()))
self.coerce_good_values = [({'foo': 'bar'}, {'foo': '*bar*'}),
({'foo': 1}, {'foo': '*1*'})]
self.coerce_bad_values = [{1: 'bar'}, 'foo']
self.to_primitive_values = [({'foo': 'bar'}, {'foo': '!bar!'})]
self.from_primitive_values = [({'foo': '!bar!'}, {'foo': 'bar'})]
def test_stringify(self):
self.assertEqual("{key=val}", self.field.stringify({'key': 'val'}))
def test_get_schema(self):
self.assertEqual({'type': ['object'],
'additionalProperties': {'readonly': False,
'type': ['foo']},
'readonly': False},
self.field.get_schema())
class TestDictOfStrings(TestField):
def setUp(self):
super().setUp()
self.field = fields.DictOfStringsField()
self.coerce_good_values = [({'foo': 'bar'}, {'foo': 'bar'}),
({'foo': 1}, {'foo': '1'})]
self.coerce_bad_values = [{1: 'bar'}, {'foo': None}, 'foo']
self.to_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})]
self.from_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})]
def test_stringify(self):
self.assertEqual("{key='val'}", self.field.stringify({'key': 'val'}))
class TestDictOfIntegers(TestField):
def setUp(self):
super().setUp()
self.field = fields.DictOfIntegersField()
self.coerce_good_values = [({'foo': '42'}, {'foo': 42}),
({'foo': 4.2}, {'foo': 4})]
self.coerce_bad_values = [{1: 'bar'}, {'foo': 'boo'},
'foo', {'foo': None}]
self.to_primitive_values = [({'foo': 42}, {'foo': 42})]
self.from_primitive_values = [({'foo': 42}, {'foo': 42})]
def test_stringify(self):
self.assertEqual("{key=42}", self.field.stringify({'key': 42}))
class TestDictOfStringsNone(TestField):
def setUp(self):
super().setUp()
self.field = fields.DictOfNullableStringsField()
self.coerce_good_values = [({'foo': 'bar'}, {'foo': 'bar'}),
({'foo': 1}, {'foo': '1'}),
({'foo': None}, {'foo': None})]
self.coerce_bad_values = [{1: 'bar'}, 'foo']
self.to_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})]
self.from_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})]
def test_stringify(self):
self.assertEqual("{k2=None,key='val'}",
self.field.stringify({'k2': None,
'key': 'val'}))
class TestListOfDictOfNullableStringsField(TestField):
def setUp(self):
super().setUp()
self.field = fields.ListOfDictOfNullableStringsField()
self.coerce_good_values = [([{'f': 'b', 'f1': 'b1'}, {'f2': 'b2'}],
[{'f': 'b', 'f1': 'b1'}, {'f2': 'b2'}]),
([{'f': 1}, {'f1': 'b1'}],
[{'f': '1'}, {'f1': 'b1'}]),
([{'foo': None}], [{'foo': None}])]
self.coerce_bad_values = [[{1: 'a'}], ['ham', 1], ['eggs']]
self.to_primitive_values = [([{'f': 'b'}, {'f1': 'b1'}, {'f2': None}],
[{'f': 'b'}, {'f1': 'b1'}, {'f2': None}])]
self.from_primitive_values = [([{'f': 'b'}, {'f1': 'b1'},
{'f2': None}],
[{'f': 'b'}, {'f1': 'b1'},
{'f2': None}])]
def test_stringify(self):
self.assertEqual("[{f=None,f1='b1'},{f2='b2'}]",
self.field.stringify(
[{'f': None, 'f1': 'b1'}, {'f2': 'b2'}]))
class TestList(TestField):
def setUp(self):
super().setUp()
self.field = fields.Field(fields.List(FakeFieldType()))
self.coerce_good_values = [(['foo', 'bar'], ['*foo*', '*bar*'])]
self.coerce_bad_values = ['foo']
self.to_primitive_values = [(['foo'], ['!foo!'])]
self.from_primitive_values = [(['!foo!'], ['foo'])]
def test_stringify(self):
self.assertEqual('[123]', self.field.stringify([123]))
def test_fieldtype_get_schema(self):
self.assertEqual({'type': ['array'],
'items': {'type': ['foo'], 'readonly': False}},
self.field._type.get_schema())
def test_get_schema(self):
self.assertEqual({'type': ['array'],
'items': {'type': ['foo'], 'readonly': False},
'readonly': False},
self.field.get_schema())
class TestListOfStrings(TestField):
def setUp(self):
super().setUp()
self.field = fields.ListOfStringsField()
self.coerce_good_values = [(['foo', 'bar'], ['foo', 'bar'])]
self.coerce_bad_values = ['foo']
self.to_primitive_values = [(['foo'], ['foo'])]
self.from_primitive_values = [(['foo'], ['foo'])]
def test_stringify(self):
self.assertEqual("['abc']", self.field.stringify(['abc']))
class TestDictOfListOfStrings(TestField):
def setUp(self):
super().setUp()
self.field = fields.DictOfListOfStringsField()
self.coerce_good_values = [({'foo': ['1', '2']}, {'foo': ['1', '2']}),
({'foo': [1]}, {'foo': ['1']})]
self.coerce_bad_values = [{'foo': [None, None]}, 'foo']
self.to_primitive_values = [({'foo': ['1', '2']}, {'foo': ['1', '2']})]
self.from_primitive_values = [({'foo': ['1', '2']},
{'foo': ['1', '2']})]
def test_stringify(self):
self.assertEqual("{foo=['1','2']}",
self.field.stringify({'foo': ['1', '2']}))
class TestListOfEnum(TestField):
def setUp(self):
super().setUp()
self.field = fields.ListOfEnumField(valid_values=['foo', 'bar'])
self.coerce_good_values = [(['foo', 'bar'], ['foo', 'bar'])]
self.coerce_bad_values = ['foo', ['foo', 'bar1']]
self.to_primitive_values = [(['foo'], ['foo'])]
self.from_primitive_values = [(['foo'], ['foo'])]
def test_stringify(self):
self.assertEqual("['foo']", self.field.stringify(['foo']))
def test_stringify_invalid(self):
self.assertRaises(ValueError, self.field.stringify, '[abc]')
def test_fingerprint(self):
# Notes(yjiang5): make sure changing valid_value will be detected
# in test_objects.test_versions
field1 = fields.ListOfEnumField(valid_values=['foo', 'bar'])
field2 = fields.ListOfEnumField(valid_values=['foo', 'bar1'])
self.assertNotEqual(str(field1), str(field2))
class TestSet(TestField):
def setUp(self):
super().setUp()
self.field = fields.Field(fields.Set(FakeFieldType()))
self.coerce_good_values = [({'foo', 'bar'},
{'*foo*', '*bar*'})]
self.coerce_bad_values = [['foo'], {'foo': 'bar'}]
self.to_primitive_values = [({'foo'}, tuple(['!foo!']))]
self.from_primitive_values = [(tuple(['!foo!']), {'foo'})]
def test_stringify(self):
self.assertEqual('set([123])', self.field.stringify({123}))
def test_get_schema(self):
self.assertEqual({'type': ['array'], 'uniqueItems': True,
'items': {'type': ['foo'], 'readonly': False},
'readonly': False},
self.field.get_schema())
class TestSetOfIntegers(TestField):
def setUp(self):
super().setUp()
self.field = fields.SetOfIntegersField()
self.coerce_good_values = [({'1', 2},
{1, 2})]
self.coerce_bad_values = [{'foo'}]
self.to_primitive_values = [({1}, tuple([1]))]
self.from_primitive_values = [(tuple([1]), {1})]
def test_stringify(self):
self.assertEqual('set([1,2])', self.field.stringify({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):
super().setUp()
self.field = fields.ListOfSetsOfIntegersField()
self.coerce_good_values = [([{'1', 2}, {3, '4'}],
[{1, 2}, {3, 4}])]
self.coerce_bad_values = [[{'foo'}]]
self.to_primitive_values = [([{1}], [tuple([1])])]
self.from_primitive_values = [([tuple([1])], [{1}])]
def test_stringify(self):
self.assertEqual('[set([1,2])]', self.field.stringify([{1, 2}]))
class TestListOfIntegers(TestField):
def setUp(self):
super().setUp()
self.field = fields.ListOfIntegersField()
self.coerce_good_values = [(['1', 2], [1, 2]),
([1, 2], [1, 2])]
self.coerce_bad_values = [['foo']]
self.to_primitive_values = [([1], [1])]
self.from_primitive_values = [([1], [1])]
def test_stringify(self):
self.assertEqual('[[1, 2]]', self.field.stringify([[1, 2]]))
class TestListOfUUIDField(TestField):
def setUp(self):
super().setUp()
self.field = fields.ListOfUUIDField()
self.uuid1 = '6b2097ea-d0e3-44dd-b131-95472b3ea8fd'
self.uuid2 = '478c193d-2533-4e71-ab2b-c7683f67d7f9'
self.coerce_good_values = [([self.uuid1, self.uuid2],
[self.uuid1, self.uuid2])]
# coerce_bad_values is intentionally ignored since the UUID field
# allows non-UUID values for now. See TestUUIDField for examples.
self.to_primitive_values = [([self.uuid1], [self.uuid1])]
self.from_primitive_values = [([self.uuid1], [self.uuid1])]
def test_stringify(self):
self.assertEqual('[{},{}]'.format(self.uuid1, self.uuid2),
self.field.stringify([self.uuid1, self.uuid2]))
class TestLocalMethods(test.TestCase):
@mock.patch.object(obj_base.LOG, 'exception')
def test__make_class_properties_setter_value_error(self, mock_log):
@obj_base.VersionedObjectRegistry.register
class AnObject(obj_base.VersionedObject):
fields = {
'intfield': fields.IntegerField(),
}
self.assertRaises(ValueError, AnObject, intfield='badvalue')
self.assertFalse(mock_log.called)
@mock.patch.object(obj_base.LOG, 'exception')
def test__make_class_properties_setter_setattr_fails(self, mock_log):
@obj_base.VersionedObjectRegistry.register
class AnObject(obj_base.VersionedObject):
fields = {
'intfield': fields.IntegerField(),
}
# We want the setattr() call in _make_class_properties.setter() to
# raise an exception
with mock.patch.object(obj_base, '_get_attrname') as mock_attr:
mock_attr.return_value = '__class__'
self.assertRaises(TypeError, AnObject, intfield=2)
mock_attr.assert_called_once_with('intfield')
mock_log.assert_called_once_with(mock.ANY,
{'attr': 'AnObject.intfield'})
class TestObject(TestField):
def setUp(self):
super().setUp()
@obj_base.VersionedObjectRegistry.register
class TestableObject(obj_base.VersionedObject):
fields = {
'uuid': fields.StringField(),
}
def __eq__(self, value):
# NOTE(danms): Be rather lax about this equality thing to
# satisfy the assertEqual() in test_from_primitive(). We
# just want to make sure the right type of object is re-created
return value.__class__.__name__ == TestableObject.__name__
class OtherTestableObject(obj_base.VersionedObject):
pass
test_inst = TestableObject()
self._test_cls = TestableObject
self.field = fields.Field(fields.Object('TestableObject'))
self.coerce_good_values = [(test_inst, test_inst)]
self.coerce_bad_values = [OtherTestableObject(), 1, 'foo']
self.to_primitive_values = [(test_inst, test_inst.obj_to_primitive())]
self.from_primitive_values = [(test_inst.obj_to_primitive(),
test_inst),
(test_inst, test_inst)]
def test_stringify(self):
obj = self._test_cls(uuid='fake-uuid')
self.assertEqual('TestableObject(fake-uuid)',
self.field.stringify(obj))
def test_from_primitive(self):
@obj_base.VersionedObjectRegistry.register
class TestFakeObject(obj_base.VersionedObject):
OBJ_PROJECT_NAMESPACE = 'fake-project'
@obj_base.VersionedObjectRegistry.register
class TestBar(TestFakeObject, obj_base.ComparableVersionedObject):
fields = {
'name': fields.StringField(),
}
@obj_base.VersionedObjectRegistry.register
class TestFoo(TestFakeObject, obj_base.ComparableVersionedObject):
fields = {
'name': fields.StringField(),
'bar': fields.ObjectField('TestBar', nullable=True)
}
bar = TestBar(name='bar')
foo = TestFoo(name='foo', bar=bar)
from_primitive_values = [(foo.obj_to_primitive(), foo), (foo, foo)]
for prim_val, out_val in from_primitive_values:
self.assertEqual(out_val, self.field.from_primitive(
foo, 'attr', prim_val))
def test_inheritance(self):
# We need a whole lot of classes in a hierarchy to
# test subclass recognition for the Object field
class TestAnimal(obj_base.VersionedObject):
pass
class TestMammal(TestAnimal):
pass
class TestReptile(TestAnimal):
pass
# We'll use this to create a diamond in the
# class hierarchy
class TestPet(TestAnimal):
pass
# Non-versioned object mixin
class TestScary:
pass
class TestCrocodile(TestReptile, TestPet, TestScary):
pass
class TestPig(TestMammal):
pass
class TestDog(TestMammal, TestPet):
pass
# Some fictional animals
wolfy = TestDog() # Terminator-2
ticktock = TestCrocodile() # Peter Pan
babe = TestPig() # Babe
# The various classes
animals = fields.Object('TestAnimal', subclasses=True)
mammals = fields.Object('TestMammal', subclasses=True)
reptiles = fields.Object('TestReptile', subclasses=True)
pets = fields.Object('TestPet', subclasses=True)
pigs = fields.Object('TestPig', subclasses=True)
dogs = fields.Object('TestDog', subclasses=True)
crocs = fields.Object('TestCrocodile', subclasses=True)
self.assertEqual(["TestDog", "TestMammal", "TestPet",
"TestAnimal", "VersionedObject"],
fields.Object._get_all_obj_names(wolfy))
self.assertEqual(["TestCrocodile", "TestReptile", "TestPet",
"TestAnimal", "VersionedObject"],
fields.Object._get_all_obj_names(ticktock))
self.assertEqual(["TestPig", "TestMammal",
"TestAnimal", "VersionedObject"],
fields.Object._get_all_obj_names(babe))
# When stringifying we should see the subclass object name
# not the base class object name
self.assertEqual("TestDog", animals.stringify(wolfy))
self.assertEqual("TestCrocodile", animals.stringify(ticktock))
self.assertEqual("TestPig", animals.stringify(babe))
# Everything is an animal
self.assertEqual(wolfy, animals.coerce(None, "animal", wolfy))
self.assertEqual(ticktock, animals.coerce(None, "animal", ticktock))
self.assertEqual(babe, animals.coerce(None, "animal", babe))
# crocodiles are not mammals
self.assertEqual(wolfy, mammals.coerce(None, "animal", wolfy))
self.assertRaises(ValueError, mammals.coerce, None, "animal", ticktock)
self.assertEqual(babe, mammals.coerce(None, "animal", babe))
# dogs and pigs are not reptiles
self.assertRaises(ValueError, reptiles.coerce, None, "animal", wolfy)
self.assertEqual(ticktock, reptiles.coerce(None, "animal", ticktock))
self.assertRaises(ValueError, reptiles.coerce, None, "animal", babe)
# pigs are not pets, but crocodiles (!) & dogs are
self.assertEqual(wolfy, pets.coerce(None, "animal", wolfy))
self.assertEqual(ticktock, pets.coerce(None, "animal", ticktock))
self.assertRaises(ValueError, pets.coerce, None, "animal", babe)
# Only dogs are dogs
self.assertEqual(wolfy, dogs.coerce(None, "animal", wolfy))
self.assertRaises(ValueError, dogs.coerce, None, "animal", ticktock)
self.assertRaises(ValueError, dogs.coerce, None, "animal", babe)
# Only crocs are crocs
self.assertRaises(ValueError, crocs.coerce, None, "animal", wolfy)
self.assertEqual(ticktock, crocs.coerce(None, "animal", ticktock))
self.assertRaises(ValueError, crocs.coerce, None, "animal", babe)
# Only pigs are pigs
self.assertRaises(ValueError, pigs.coerce, None, "animal", ticktock)
self.assertRaises(ValueError, pigs.coerce, None, "animal", wolfy)
self.assertEqual(babe, pigs.coerce(None, "animal", babe))
def test_coerce_bad_value_primitive_type(self):
# Tests that the ValueError has the primitive type in it's message.
ex = self.assertRaises(ValueError, self.field.coerce,
'obj', 'attr', [{}])
self.assertEqual('An object of type TestableObject is required '
'in field attr, not a list', str(ex))
def test_get_schema(self):
self.assertEqual(
{
'properties': {
'versioned_object.changes':
{'items': {'type': 'string'}, 'type': 'array'},
'versioned_object.data': {
'description': 'fields of TestableObject',
'properties':
{'uuid': {'readonly': False, 'type': ['string']}},
'required': ['uuid'],
'type': 'object'},
'versioned_object.name': {'type': 'string'},
'versioned_object.namespace': {'type': 'string'},
'versioned_object.version': {'type': 'string'}
},
'readonly': False,
'required': ['versioned_object.namespace',
'versioned_object.name',
'versioned_object.version',
'versioned_object.data'],
'type': ['object']
},
self.field.get_schema())
class TestIPAddress(TestField):
def setUp(self):
super().setUp()
self.field = fields.IPAddressField()
self.coerce_good_values = [('1.2.3.4', netaddr.IPAddress('1.2.3.4')),
('::1', netaddr.IPAddress('::1')),
(netaddr.IPAddress('::1'),
netaddr.IPAddress('::1'))]
self.coerce_bad_values = ['1-2', 'foo']
self.to_primitive_values = [(netaddr.IPAddress('1.2.3.4'), '1.2.3.4'),
(netaddr.IPAddress('::1'), '::1')]
self.from_primitive_values = [('1.2.3.4',
netaddr.IPAddress('1.2.3.4')),
('::1',
netaddr.IPAddress('::1'))]
class TestIPAddressV4(TestField):
def setUp(self):
super().setUp()
self.field = fields.IPV4AddressField()
self.coerce_good_values = [('1.2.3.4', netaddr.IPAddress('1.2.3.4')),
(netaddr.IPAddress('1.2.3.4'),
netaddr.IPAddress('1.2.3.4'))]
self.coerce_bad_values = ['1-2', 'foo', '::1']
self.to_primitive_values = [(netaddr.IPAddress('1.2.3.4'), '1.2.3.4')]
self.from_primitive_values = [('1.2.3.4',
netaddr.IPAddress('1.2.3.4'))]
def test_get_schema(self):
self.assertEqual({'type': ['string'], 'readonly': False,
'format': 'ipv4'},
self.field.get_schema())
class TestIPAddressV6(TestField):
def setUp(self):
super().setUp()
self.field = fields.IPV6AddressField()
self.coerce_good_values = [('::1', netaddr.IPAddress('::1')),
(netaddr.IPAddress('::1'),
netaddr.IPAddress('::1'))]
self.coerce_bad_values = ['1.2', 'foo', '1.2.3.4']
self.to_primitive_values = [(netaddr.IPAddress('::1'), '::1')]
self.from_primitive_values = [('::1', netaddr.IPAddress('::1'))]
def test_get_schema(self):
self.assertEqual({'type': ['string'], 'readonly': False,
'format': 'ipv6'},
self.field.get_schema())
class TestIPV4AndV6Address(TestField):
def setUp(self):
super().setUp()
self.field = fields.IPV4AndV6Address()
self.coerce_good_values = [('::1', netaddr.IPAddress('::1')),
(netaddr.IPAddress('::1'),
netaddr.IPAddress('::1')),
('1.2.3.4',
netaddr.IPAddress('1.2.3.4')),
(netaddr.IPAddress('1.2.3.4'),
netaddr.IPAddress('1.2.3.4'))]
self.coerce_bad_values = ['1-2', 'foo']
self.to_primitive_values = [(netaddr.IPAddress('::1'),
'::1'),
(netaddr.IPAddress('1.2.3.4'),
'1.2.3.4')]
self.from_primitive_values = [('::1',
netaddr.IPAddress('::1')),
('1.2.3.4',
netaddr.IPAddress('1.2.3.4'))]
def test_get_schema(self):
self.assertEqual({'oneOf': [{'format': 'ipv4', 'type': ['string']},
{'format': 'ipv6', 'type': ['string']}]},
self.field.get_schema())
class TestIPNetwork(TestField):
def setUp(self):
super().setUp()
self.field = fields.IPNetworkField()
self.coerce_good_values = [('::1/0', netaddr.IPNetwork('::1/0')),
('1.2.3.4/24',
netaddr.IPNetwork('1.2.3.4/24')),
(netaddr.IPNetwork('::1/32'),
netaddr.IPNetwork('::1/32'))]
self.coerce_bad_values = ['foo']
self.to_primitive_values = [(netaddr.IPNetwork('::1/0'), '::1/0')]
self.from_primitive_values = [('::1/0',
netaddr.IPNetwork('::1/0'))]
class TestIPV4Network(TestField):
def setUp(self):
super().setUp()
self.field = fields.IPV4NetworkField()
self.coerce_good_values = [('1.2.3.4/24',
netaddr.IPNetwork('1.2.3.4/24'))]
self.coerce_bad_values = ['foo', '::1/32']
self.to_primitive_values = [(netaddr.IPNetwork('1.2.3.4/24'),
'1.2.3.4/24')]
self.from_primitive_values = [('1.2.3.4/24',
netaddr.IPNetwork('1.2.3.4/24'))]
def test_get_schema(self):
schema = self.field.get_schema()
self.assertEqual(['string'], schema['type'])
self.assertEqual(False, schema['readonly'])
pattern = schema['pattern']
for _, valid_val in self.coerce_good_values:
self.assertRegex(str(valid_val), pattern)
invalid_vals = [x for x in self.coerce_bad_values]
for invalid_val in invalid_vals:
self.assertNotRegex(str(invalid_val), pattern)
class TestIPV6Network(TestField):
def setUp(self):
super().setUp()
self.field = fields.IPV6NetworkField()
self.coerce_good_values = [('::1/0', netaddr.IPNetwork('::1/0')),
(netaddr.IPNetwork('::1/32'),
netaddr.IPNetwork('::1/32'))]
self.coerce_bad_values = ['foo', '1.2.3.4/24']
self.to_primitive_values = [(netaddr.IPNetwork('::1/0'), '::1/0')]
self.from_primitive_values = [('::1/0',
netaddr.IPNetwork('::1/0'))]
def test_get_schema(self):
schema = self.field.get_schema()
self.assertEqual(['string'], schema['type'])
self.assertEqual(False, schema['readonly'])
pattern = schema['pattern']
for _, valid_val in self.coerce_good_values:
self.assertRegex(str(valid_val), pattern)
invalid_vals = [x for x in self.coerce_bad_values]
for invalid_val in invalid_vals:
self.assertNotRegex(str(invalid_val), pattern)
class FakeCounter:
def __init__(self):
self.n = 0
def __iter__(self):
return self
def __next__(self):
if self.n <= 4:
self.n += 1
return self.n
else:
raise StopIteration
class TestListTypes(test.TestCase):
def test_regular_list(self):
fields.List(fields.Integer).coerce(None, None, [1, 2])
def test_non_iterable(self):
self.assertRaises(ValueError,
fields.List(fields.Integer).coerce, None, None, 2)
def test_string_iterable(self):
self.assertRaises(ValueError,
fields.List(fields.Integer).coerce, None, None,
'hello')
def test_mapping_iterable(self):
self.assertRaises(ValueError,
fields.List(fields.Integer).coerce, None, None,
{'a': 1, 'b': 2})
def test_iter_class(self):
fields.List(fields.Integer).coerce(None, None, FakeCounter())