mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Replace float with Decimal
Having float type as a base type for floating point parameters in ipalib introduces several issues, e.g. problem with representation or value comparison. Python language provides a Decimal type which help overcome these issues. This patch replaces a float type and Float parameter with a decimal.Decimal type in Decimal parameter. A precision attribute was added to Decimal parameter that can be used to limit a number of decimal places in parameter representation. This approach fixes a problem with API.txt validation where comparison of float values may fail on different architectures due to float representation error. In order to safely transfer the parameter value over RPC it is being converted to string which is then converted back to decimal.Decimal number on a server side. https://fedorahosted.org/freeipa/ticket/2260
This commit is contained in:
@@ -25,6 +25,7 @@ Test the `ipalib.parameters` module.
|
||||
import re
|
||||
import sys
|
||||
from types import NoneType
|
||||
from decimal import Decimal
|
||||
from inspect import isclass
|
||||
from tests.util import raises, ClassChecker, read_only
|
||||
from tests.util import dummy_ugettext, assert_equal
|
||||
@@ -1300,77 +1301,77 @@ class test_Int(ClassChecker):
|
||||
assert o._convert_scalar(u'0x10') == 16
|
||||
assert o._convert_scalar(u'020') == 16
|
||||
|
||||
class test_Float(ClassChecker):
|
||||
class test_Decimal(ClassChecker):
|
||||
"""
|
||||
Test the `ipalib.parameters.Float` class.
|
||||
Test the `ipalib.parameters.Decimal` class.
|
||||
"""
|
||||
_cls = parameters.Float
|
||||
_cls = parameters.Decimal
|
||||
|
||||
def test_init(self):
|
||||
"""
|
||||
Test the `ipalib.parameters.Float.__init__` method.
|
||||
Test the `ipalib.parameters.Decimal.__init__` method.
|
||||
"""
|
||||
# Test with no kwargs:
|
||||
o = self.cls('my_number')
|
||||
assert o.type is float
|
||||
assert isinstance(o, parameters.Float)
|
||||
assert o.type is Decimal
|
||||
assert isinstance(o, parameters.Decimal)
|
||||
assert o.minvalue is None
|
||||
assert o.maxvalue is None
|
||||
|
||||
# Test when min > max:
|
||||
e = raises(ValueError, self.cls, 'my_number', minvalue=22.5, maxvalue=15.1)
|
||||
e = raises(ValueError, self.cls, 'my_number', minvalue=Decimal('22.5'), maxvalue=Decimal('15.1'))
|
||||
assert str(e) == \
|
||||
"Float('my_number'): minvalue > maxvalue (minvalue=22.5, maxvalue=15.1)"
|
||||
"Decimal('my_number'): minvalue > maxvalue (minvalue=22.5, maxvalue=15.1)"
|
||||
|
||||
def test_rule_minvalue(self):
|
||||
"""
|
||||
Test the `ipalib.parameters.Float._rule_minvalue` method.
|
||||
Test the `ipalib.parameters.Decimal._rule_minvalue` method.
|
||||
"""
|
||||
o = self.cls('my_number', minvalue=3.1)
|
||||
assert o.minvalue == 3.1
|
||||
o = self.cls('my_number', minvalue='3.1')
|
||||
assert o.minvalue == Decimal('3.1')
|
||||
rule = o._rule_minvalue
|
||||
translation = u'minvalue=%(minvalue)r'
|
||||
translation = u'minvalue=%(minvalue)s'
|
||||
dummy = dummy_ugettext(translation)
|
||||
assert dummy.translation is translation
|
||||
|
||||
# Test with passing values:
|
||||
for value in (3.2, 99.0):
|
||||
for value in (Decimal('3.2'), Decimal('99.0')):
|
||||
assert rule(dummy, value) is None
|
||||
assert dummy.called() is False
|
||||
|
||||
# Test with failing values:
|
||||
for value in (-1.2, 0.0, 3.0):
|
||||
for value in (Decimal('-1.2'), Decimal('0.0'), Decimal('3.0')):
|
||||
assert_equal(
|
||||
rule(dummy, value),
|
||||
translation % dict(minvalue=3.1)
|
||||
translation % dict(minvalue=Decimal('3.1'))
|
||||
)
|
||||
assert dummy.message == 'must be at least %(minvalue)f'
|
||||
assert dummy.message == 'must be at least %(minvalue)s'
|
||||
assert dummy.called() is True
|
||||
dummy.reset()
|
||||
|
||||
def test_rule_maxvalue(self):
|
||||
"""
|
||||
Test the `ipalib.parameters.Float._rule_maxvalue` method.
|
||||
Test the `ipalib.parameters.Decimal._rule_maxvalue` method.
|
||||
"""
|
||||
o = self.cls('my_number', maxvalue=4.7)
|
||||
assert o.maxvalue == 4.7
|
||||
o = self.cls('my_number', maxvalue='4.7')
|
||||
assert o.maxvalue == Decimal('4.7')
|
||||
rule = o._rule_maxvalue
|
||||
translation = u'maxvalue=%(maxvalue)r'
|
||||
dummy = dummy_ugettext(translation)
|
||||
assert dummy.translation is translation
|
||||
|
||||
# Test with passing values:
|
||||
for value in (-1.0, 0.1, 4.2):
|
||||
for value in (Decimal('-1.0'), Decimal('0.1'), Decimal('4.2')):
|
||||
assert rule(dummy, value) is None
|
||||
assert dummy.called() is False
|
||||
|
||||
# Test with failing values:
|
||||
for value in (5.3, 99.9):
|
||||
for value in (Decimal('5.3'), Decimal('99.9')):
|
||||
assert_equal(
|
||||
rule(dummy, value),
|
||||
translation % dict(maxvalue=4.7)
|
||||
translation % dict(maxvalue=Decimal('4.7'))
|
||||
)
|
||||
assert dummy.message == 'can be at most %(maxvalue)f'
|
||||
assert dummy.message == 'can be at most %(maxvalue)s'
|
||||
assert dummy.called() is True
|
||||
dummy.reset()
|
||||
|
||||
|
||||
@@ -598,7 +598,7 @@ class test_dns(Declarative):
|
||||
'idnsname': [dnszone1],
|
||||
'mxrecord': [u"0 %s" % dnszone1_mname],
|
||||
'nsrecord': [dnszone1_mname],
|
||||
'locrecord': [u"49 11 42.4 N 16 36 29.6 E 227.64"],
|
||||
'locrecord': [u"49 11 42.400 N 16 36 29.600 E 227.64"],
|
||||
},
|
||||
},
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user