rework datatypes (setter should not check limits)

- use Datatype.validate for converting and checking limits
  (used also in properties)
- Datatype.__call__ converts and validates, but without checking
  limits (used in setter)
- Datatype.validate may be used to add missing optional struct elements
  from previous value (used in Dispatcher._setParameterValue)
- remove problematic range check
+ use shorter formula for converting float to int in ScaledInteger
  (leftover from python2 compatibility)
+ improve error messages (strip very long repr(value))

Change-Id: Ib85736fe558ec3370ebce4e1c43f957e3bb0497c
This commit is contained in:
2022-11-15 17:39:04 +01:00
parent 09d48ea913
commit 7a870aa56c
6 changed files with 188 additions and 127 deletions

View File

@@ -55,9 +55,11 @@ def test_FloatRange():
assert dt.export_datatype() == {'type': 'double', 'min':-3.14, 'max':3.14}
with pytest.raises(ValueError):
dt(9)
dt.validate(9)
with pytest.raises(ValueError):
dt(-9)
dt.validate(-9)
dt(9) # convert, but do not check limits
dt(-9) # convert, but do not check limits
with pytest.raises(ValueError):
dt('XX')
with pytest.raises(ValueError):
@@ -106,9 +108,11 @@ def test_IntRange():
assert dt.export_datatype() == {'type': 'int', 'min':-3, 'max':3}
with pytest.raises(ValueError):
dt(9)
dt.validate(9)
with pytest.raises(ValueError):
dt(-9)
dt.validate(-9)
dt(9) # convert, but do not check limits
dt(-9) # convert, but do not check limits
with pytest.raises(ValueError):
dt('XX')
with pytest.raises(ValueError):
@@ -142,9 +146,11 @@ def test_ScaledInteger():
assert dt.export_datatype() == {'type': 'scaled', 'scale':0.01, 'min':-300, 'max':300}
with pytest.raises(ValueError):
dt(9)
dt.validate(9)
with pytest.raises(ValueError):
dt(-9)
dt.validate(-9)
dt(9) # convert, but do not check limits
dt(-9) # convert, but do not check limits
with pytest.raises(ValueError):
dt('XX')
with pytest.raises(ValueError):
@@ -171,20 +177,23 @@ def test_ScaledInteger():
'unit':'A'}
assert dt.absolute_resolution == dt.scale
dt = ScaledInteger(0.003, 0, 1, unit='X', fmtstr='%.1f',
dt = ScaledInteger(0.003, 0.4, 1, unit='X', fmtstr='%.1f',
absolute_resolution=0.001, relative_resolution=1e-5)
copytest(dt)
assert dt.export_datatype() == {'type': 'scaled', 'scale':0.003, 'min':0, 'max':333,
assert dt.export_datatype() == {'type': 'scaled', 'scale':0.003, 'min':133, 'max':333,
'unit':'X', 'fmtstr':'%.1f',
'absolute_resolution':0.001,
'relative_resolution':1e-5}
assert dt(0.4) == 0.399
assert dt.format_value(0.4) == '0.4 X'
assert dt.format_value(0.4, '') == '0.4'
assert dt.format_value(0.4, 'Z') == '0.4 Z'
assert dt(1.0029) == 0.999
assert round(dt(0.7), 5) == 0.699
assert dt.format_value(0.6) == '0.6 X'
assert dt.format_value(0.6, '') == '0.6'
assert dt.format_value(0.6, 'Z') == '0.6 Z'
assert round(dt.validate(1.0004), 5) == 0.999 # rounded value within limit
with pytest.raises(ValueError):
dt(1.004)
dt.validate(1.006) # rounded value outside limit
assert round(dt.validate(0.398), 5) == 0.399 # rounded value within rounded limit
with pytest.raises(ValueError):
dt.validate(0.395) # rounded value outside limit
dt.setProperty('min', 1)
dt.setProperty('max', 0)
@@ -456,7 +465,7 @@ def test_StructOf():
with pytest.raises(ValueError):
dt([99, 'X'])
with pytest.raises(ValueError):
dt(dict(a_string='XXX', an_int=1811))
dt.validate(dict(a_string='XXX', an_int=1811))
assert dt(dict(a_string='XXX', an_int=8)) == {'a_string': 'XXX',
'an_int': 8}
@@ -638,7 +647,7 @@ def test_get_datatype():
(StringType(10, 10), StringType()),
(ArrayOf(StringType(), 3, 5), ArrayOf(StringType(), 3, 6)),
(TupleOf(StringType(), BoolType()), TupleOf(StringType(), IntRange())),
(StructOf(a=FloatRange(-1,1)), StructOf(a=FloatRange(), b=BoolType(), optional=['b'])),
(StructOf(a=FloatRange(-1,1), b=BoolType()), StructOf(a=FloatRange(), b=BoolType(), optional=['b'])),
])
def test_oneway_compatible(dt, contained_in):
dt.compatible(contained_in)

View File

@@ -31,7 +31,6 @@ from frappy.errors import ProgrammingError, ConfigError
from frappy.modules import Communicator, Drivable, Readable, Module
from frappy.params import Command, Parameter
from frappy.rwhandler import ReadHandler, WriteHandler, nopoll
from frappy.lib import generalConfig
class DispatcherStub:
@@ -235,7 +234,7 @@ def test_ModuleMagic():
assert o2.parameters['a1'].datatype.unit == 'mm/s'
cfg = Newclass2.configurables
assert set(cfg.keys()) == {
'export', 'group', 'description', 'disable_value_range_check', 'features',
'export', 'group', 'description', 'features',
'meaning', 'visibility', 'implementation', 'interface_classes', 'target', 'stop',
'status', 'param1', 'param2', 'cmd', 'a2', 'pollinterval', 'slowinterval', 'b2',
'cmd2', 'value', 'a1'}
@@ -631,7 +630,7 @@ def test_problematic_value_range():
obj = Mod('obj', logger, {'description': '', 'value':{'max': 10.1}}, srv) # pylint: disable=unused-variable
with pytest.raises(ConfigError):
obj = Mod('obj', logger, {'description': ''}, srv)
obj = Mod('obj', logger, {'description': '', 'value.max': 9.9}, srv)
class Mod2(Drivable):
value = Parameter('', FloatRange(), default=0)
@@ -640,17 +639,10 @@ def test_problematic_value_range():
obj = Mod2('obj', logger, {'description': ''}, srv)
obj = Mod2('obj', logger, {'description': '', 'target':{'min': 0, 'max': 10}}, srv)
with pytest.raises(ConfigError):
obj = Mod('obj', logger, {
'value':{'min': 0, 'max': 10},
'target':{'min': 0, 'max': 10}, 'description': ''}, srv)
obj = Mod('obj', logger, {'disable_value_range_check': True,
obj = Mod('obj', logger, {
'value': {'min': 0, 'max': 10},
'target': {'min': 0, 'max': 10}, 'description': ''}, srv)
generalConfig.defaults['disable_value_range_check'] = True
class Mod4(Drivable):
value = Parameter('', FloatRange(0, 10), default=0)
target = Parameter('', FloatRange(0, 10), default=0)