# ***************************************************************************** # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Module authors: # Enrico Faulhaber # # ***************************************************************************** """test data types.""" # no fixtures needed import pytest from frappy.datatypes import ArrayOf, BLOBType, BoolType, CommandType, \ ConfigError, DataType, EnumType, FloatRange, \ IntRange, ProgrammingError, ScaledInteger, StatusType, StringType, \ StructOf, TextType, TupleOf, ValueType, get_datatype from frappy.errors import BadValueError, RangeError, WrongTypeError from frappy.lib import generalConfig def copytest(dt): assert repr(dt) == repr(dt.copy()) assert dt.export_datatype() == dt.copy().export_datatype() assert dt != dt.copy() with pytest.raises(KeyError): dt.setProperty('visibility', 0) def valid(dt, *args, exported=None, formatted=()): for value in args: v = dt(value) assert dt.import_value(dt.export_value(v)) == v vv = dt.from_string(dt.to_string(v)) if isinstance(vv, float): assert abs(vv - v) <= max(dt.absolute_resolution, (vv + v) * dt.relative_resolution) else: assert vv == v if exported is None: exported = args for value, ex in zip(args, exported): assert dt.export_value(value) == ex for value, fm in zip(args, formatted): assert dt.format_value(dt(value)) == fm def invalid(dt, *args, test_import=True): for value in args: with pytest.raises(WrongTypeError): dt(value) if test_import: with pytest.raises(WrongTypeError): dt.import_value(value) def out_of_range(dt, *args): for value in args: dt(value) with pytest.raises(RangeError): dt.validate(value) def test_DataType(): dt = DataType() with pytest.raises(ProgrammingError): dt.export_datatype() with pytest.raises(NotImplementedError): dt('') dt.export_value('') def test_FloatRange(): dt = FloatRange(-3.14, 3.14) copytest(dt) assert dt.export_datatype() == {'type': 'double', 'min':-3.14, 'max':3.14} valid(dt, -2.718, 1, 0) dt(13.14 - 10) # raises an error, if resolution is not handled correctly invalid(dt, 'XX', [19, 'XX']) out_of_range(dt, -9, 9) # check that unit can be changed dt.setProperty('unit', 'K') assert dt.export_datatype() == {'type': 'double', 'min':-3.14, 'max':3.14, 'unit': 'K'} dt.setProperty('absolute_resolution', 0) valid(dt, 1.25, formatted=['1.25 K']) with pytest.raises(ProgrammingError): FloatRange('x', 'Y') dt = FloatRange() copytest(dt) assert dt.export_datatype() == {'type': 'double'} dt = FloatRange(unit='X', fmtstr='%.2f', absolute_resolution=1, relative_resolution=0.1) copytest(dt) assert dt.export_datatype() == {'type': 'double', 'unit':'X', 'fmtstr':'%.2f', 'absolute_resolution':1.0, 'relative_resolution':0.1} valid(dt, 4, 3.1392, formatted=['4.00 X', '3.14 X']) assert dt.format_value(3.14, '') == '3.14' assert dt.format_value(3.14, '#') == '3.14 #' dt.setProperty('min', 1) dt.setProperty('max', 0) with pytest.raises(ConfigError): dt.checkProperties() with pytest.raises(ProgrammingError): FloatRange(resolution=1) def test_IntRange(): dt = IntRange(-3, 3) copytest(dt) assert dt.export_datatype() == {'type': 'int', 'min':-3, 'max':3} out_of_range(dt, 9, -9) invalid(dt, 'XX', [19, 'X'], 1.3, '1.3') valid(dt, 0, 1) with pytest.raises(ProgrammingError): IntRange('xc', 'Yx') dt = IntRange() copytest(dt) assert dt.export_datatype()['type'] == 'int' assert dt.export_datatype()['min'] < 0 < dt.export_datatype()['max'] assert dt.export_datatype() == {'type': 'int', 'max': 16777216,'min': -16777216} assert dt.format_value(42) == '42' dt.setProperty('min', 1) dt.setProperty('max', 0) with pytest.raises(ConfigError): dt.checkProperties() def test_ScaledInteger(): dt = ScaledInteger(0.01, -3, 3) copytest(dt) # serialisation of datatype contains limits on the 'integer' value assert dt.export_datatype() == {'type': 'scaled', 'scale':0.01, 'min':-300, 'max':300} out_of_range(dt, 9, -9) invalid(dt, 'XX', [19, 'X'], '1.3') valid(dt, 0, 1, 0.0001, 2.71819, exported=[0, 100, 0, 272]) with pytest.raises(ProgrammingError): ScaledInteger('xc', 'Yx') with pytest.raises(ProgrammingError): ScaledInteger(scale=0, min=1, max=2) with pytest.raises(ProgrammingError): ScaledInteger(scale=-10, min=1, max=2) # check that unit can be changed dt.setProperty('unit', 'A') assert dt.export_datatype() == {'type': 'scaled', 'scale':0.01, 'min':-300, 'max':300, 'unit': 'A'} dt.setProperty('scale', 0.1) assert dt.export_datatype() == {'type': 'scaled', 'scale':0.1, 'min':-30, 'max':30, 'unit':'A'} assert dt.absolute_resolution == dt.scale 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':133, 'max':333, 'unit':'X', 'fmtstr':'%.1f', 'absolute_resolution':0.001, 'relative_resolution':1e-5} 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 out_of_range(dt, 1.006, 0.395) # rounded value outside limit assert round(dt.validate(0.398), 5) == 0.399 # rounded value within rounded limit dt.setProperty('min', 1) dt.setProperty('max', 0) with pytest.raises(ConfigError): dt.checkProperties() with pytest.raises(WrongTypeError): dt.setProperty('scale', None) def test_EnumType(): # test constructor catching illegal arguments with pytest.raises(TypeError): EnumType(1) with pytest.raises(TypeError): EnumType(['b', 0]) dt = EnumType('dt', a=3, c=7, stuff=1) copytest(dt) assert dt.export_datatype() == {'type': 'enum', 'members': {'a': 3, 'c': 7, 'stuff': 1}} invalid(dt, 2.3, [19, 'X']) with pytest.raises(RangeError): dt(9) with pytest.raises(RangeError): dt(-9) with pytest.raises(RangeError): dt('XX') valid(dt, 'a', 'stuff', 1, 'c', exported=[3, 1, 1, 7]) assert dt.format_value(dt(3)) == 'a<3>' def test_BLOBType(): # test constructor catching illegal arguments dt = BLOBType() copytest(dt) assert dt.export_datatype() == {'type': 'blob', 'maxbytes':255} dt = BLOBType(10) copytest(dt) assert dt.export_datatype() == {'type': 'blob', 'minbytes':10, 'maxbytes':10} dt = BLOBType(3, 10) copytest(dt) assert dt.export_datatype() == {'type': 'blob', 'minbytes':3, 'maxbytes':10} valid(dt, b'abcd', b'ert', b'123456789a', exported=['YWJjZA==']) invalid(dt, 9, 'abcd', test_import=False) with pytest.raises(RangeError): dt(b'av') with pytest.raises(RangeError): dt(b'abcdefghijklmno') dt.setProperty('minbytes', 1) dt.setProperty('maxbytes', 0) with pytest.raises(ConfigError): dt.checkProperties() assert dt.import_value('YWJjZA==') == b'abcd' assert dt.format_value(b'ab\0cd') == "b'ab\\x00cd\'" def test_StringType(): # test constructor catching illegal arguments dt = StringType() copytest(dt) assert dt.export_datatype() == {'type': 'string'} dt = StringType(12) copytest(dt) assert dt.export_datatype() == {'type': 'string', 'minchars':12, 'maxchars':12} dt = StringType(4, 11) copytest(dt) assert dt.export_datatype() == {'type': 'string', 'minchars':4, 'maxchars':11} invalid(dt, 9, b'abcd') valid(dt, 'abcd', exported=['abcd']) with pytest.raises(RangeError): dt('av') with pytest.raises(RangeError): dt('abcdefghijklmno') with pytest.raises(RangeError): dt('abcdefg\0') assert dt('abcd') == 'abcd' assert dt.format_value('abcd') == "'abcd'" assert dt.to_string('abcd') == 'abcd' dt.setProperty('minchars', 1) dt.setProperty('maxchars', 0) with pytest.raises(ConfigError): dt.checkProperties() def test_TextType(): # test constructor catching illegal arguments dt = TextType(12) copytest(dt) assert dt.export_datatype() == {'type': 'string', 'maxchars':12} invalid(dt, 9, b'abcd') with pytest.raises(RangeError): dt('abcdefghijklmno') with pytest.raises(RangeError): dt('abcdefg\0') valid(dt, 'abcd', exported=['abcd']) assert dt('ab\n\ncd\n') == 'ab\n\ncd\n' def test_BoolType(): # test constructor catching illegal arguments dt = BoolType() copytest(dt) assert dt.export_datatype() == {'type': 'bool'} valid(dt, 1, True, 0, False, exported=[1, 1, 0, 0], formatted=['True', 'True', 'False', 'False']) assert dt.from_string('true') is True assert dt.from_string('off') is False invalid(dt, 2, 'av') with pytest.raises(TypeError): # pylint: disable=unexpected-keyword-arg BoolType(unit='K') def test_ArrayOf(): # test constructor catching illegal arguments with pytest.raises(ProgrammingError): ArrayOf(int) with pytest.raises(ProgrammingError): ArrayOf(-3, IntRange(-10,10)) dt = ArrayOf(IntRange(-10, 10), 5) copytest(dt) assert dt.export_datatype() == {'type': 'array', 'minlen':5, 'maxlen':5, 'members': {'type': 'int', 'min':-10, 'max':10}} dt = ArrayOf(FloatRange(-10, 10, unit='Z'), 1, 3) copytest(dt) assert dt.export_datatype() == {'type': 'array', 'minlen':1, 'maxlen':3, 'members':{'type': 'double', 'min':-10, 'max':10, 'unit': 'Z'}} with pytest.raises(WrongTypeError): dt(9) with pytest.raises(WrongTypeError): dt('av') valid(dt, [1, 2, 3]) assert dt([1, 2, 3]) == (1, 2, 3) assert dt.export_value([1, 2, 3]) == [1, 2, 3] assert dt.import_value([1, 2, 3]) == (1, 2, 3) assert dt.format_value([1,2,3]) == '[1, 2, 3] Z' assert dt.format_value([1,2,3], '') == '[1, 2, 3]' assert dt.format_value([1,2,3], 'Q') == '[1, 2, 3] Q' dt = ArrayOf(FloatRange(unit='K')) assert dt.members.unit == 'K' dt.setProperty('unit', 'mm') with pytest.raises(TypeError): # pylint: disable=unexpected-keyword-arg ArrayOf(BoolType(), unit='K') dt.setProperty('minlen', 1) dt.setProperty('maxlen', 0) with pytest.raises(ConfigError): dt.checkProperties() dt = ArrayOf(EnumType('myenum', single=0), 5) copytest(dt) dt = ArrayOf(ArrayOf(FloatRange(unit='m'))) assert dt.format_value([[0, 1], [2, 3]]) == '[[0, 1], [2, 3]] m' dt = ArrayOf(StructOf(f=FloatRange(unit='K'))) assert dt.format_value([{'f': 1.5}]) == "[{f=1.5 K}]" assert dt.to_string([{'f': 1.5}]) == "[{'f': 1.5}]" dt = ArrayOf(ArrayOf(EnumType(a=1, b=2))) assert dt.to_string(dt([[1, 2]])) == "[['a', 'b']]" def test_TupleOf(): # test constructor catching illegal arguments with pytest.raises(ProgrammingError): TupleOf(2) dt = TupleOf(IntRange(-10, 10), BoolType()) copytest(dt) assert dt.export_datatype() == {'type': 'tuple', 'members':[{'type': 'int', 'min':-10, 'max':10}, {'type': 'bool'}]} with pytest.raises(WrongTypeError): dt(9) with pytest.raises(WrongTypeError): dt([99, 'X']) valid(dt, [1, True]) assert dt([1, True]) == (1, True) assert dt.export_value([1, True]) == [1, True] assert dt.import_value([1, True]) == (1, True) assert dt.format_value(dt([3,0])) == "(3, False)" dt = TupleOf(EnumType('myenum', single=0)) copytest(dt) def test_StructOf(): # test constructor catching illegal arguments with pytest.raises(ProgrammingError): StructOf(IntRange) # pylint: disable=E1121 with pytest.raises(ProgrammingError): StructOf(IntRange=1) dt = StructOf(a_string=StringType(0, 55), an_int=IntRange(0, 999), optional=['an_int']) copytest(dt) assert dt.export_datatype() == {'type': 'struct', 'members':{'a_string': {'type': 'string', 'maxchars':55}, 'an_int': {'type': 'int', 'min':0, 'max':999}}, 'optional':['an_int']} with pytest.raises(WrongTypeError): dt(9) with pytest.raises(WrongTypeError): dt([99, 'X']) with pytest.raises(RangeError): dt.validate({'a_string': 'XXX', 'an_int': 1811}) valid(dt, {'a_string': 'XXX', 'an_int': 8}) assert dt({'a_string': 'XXX', 'an_int': 8}) == {'a_string': 'XXX', 'an_int': 8} assert dt.export_value({'an_int': 13, 'a_string': 'WFEC'}) == { 'a_string': 'WFEC', 'an_int': 13} assert dt.import_value({'an_int': 13, 'a_string': 'WFEC'}) == { 'a_string': 'WFEC', 'an_int': 13} assert dt.format_value({'an_int': 2, 'a_string': 'Z'}) == "{an_int=2, a_string='Z'}" assert dt.to_string({'an_int': 2, 'a_string': 'Z'}) == "{'an_int': 2, 'a_string': 'Z'}" dt = StructOf(['optionalmember'], optionalmember=EnumType('myenum', single=0)) copytest(dt) def test_Command(): dt = CommandType() assert dt.export_datatype() == {'type': 'command'} dt = CommandType(IntRange(-1,1)) assert dt.export_datatype() == {'type': 'command', 'argument':{'type': 'int', 'min':-1, 'max':1}} dt = CommandType(IntRange(-1,1), IntRange(-3,3)) assert dt.export_datatype() == {'type': 'command', 'argument':{'type': 'int', 'min':-1, 'max':1}, 'result':{'type': 'int', 'min':-3, 'max':3}} def test_StatusType(): dt = StatusType('IDLE', 'WARN', 'ERROR', 'DISABLED') assert dt.IDLE == StatusType.IDLE == 100 assert dt.ERROR == StatusType.ERROR == 400 dt2 = StatusType(None, IDLE=100, WARN=200, ERROR=400, DISABLED=0) assert dt2.export_datatype() == dt.export_datatype() dt3 = StatusType(dt.enum) assert dt3.export_datatype() == dt.export_datatype() with pytest.raises(ProgrammingError): StatusType('__init__') # built in attribute of StatusType with pytest.raises(ProgrammingError): StatusType(dt.enum, 'custom') # not a standard attribute StatusType(dt.enum, custom=499) # o.k., if value is given def test_get_datatype(): with pytest.raises(WrongTypeError): get_datatype(1) with pytest.raises(WrongTypeError): get_datatype(True) with pytest.raises(WrongTypeError): get_datatype(str) with pytest.raises(WrongTypeError): get_datatype({'undefined': {}}) assert isinstance(get_datatype({'type': 'bool'}), BoolType) with pytest.raises(WrongTypeError): get_datatype(['bool']) with pytest.raises(WrongTypeError): get_datatype({'type': 'int', 'min':-10}) # missing max with pytest.raises(WrongTypeError): get_datatype({'type': 'int', 'max':10}) # missing min assert isinstance(get_datatype({'type': 'int', 'min':-10, 'max':10}), IntRange) with pytest.raises(WrongTypeError): get_datatype({'type': 'int', 'min':10, 'max':-10}) # min > max with pytest.raises(WrongTypeError): get_datatype({'type': 'int'}) # missing limits with pytest.raises(WrongTypeError): get_datatype({'type': 'int', 'x': 2}) assert isinstance(get_datatype({'type': 'double'}), FloatRange) assert isinstance(get_datatype({'type': 'double', 'min':-2.718}), FloatRange) assert isinstance(get_datatype({'type': 'double', 'max':3.14}), FloatRange) assert isinstance(get_datatype({'type': 'double', 'min':-9.9, 'max':11.1}), FloatRange) with pytest.raises(WrongTypeError): get_datatype(['double']) with pytest.raises(WrongTypeError): get_datatype({'type': 'double', 'min':10, 'max':-10}) with pytest.raises(WrongTypeError): get_datatype(['double', {}, 2]) with pytest.raises(WrongTypeError): get_datatype({'type': 'scaled', 'scale':0.01, 'min':-2.718}) with pytest.raises(WrongTypeError): get_datatype({'type': 'scaled', 'scale':0.02, 'max':3.14}) assert isinstance(get_datatype( {'type': 'scaled', 'scale':0.03, 'min':-99, 'max':111}), ScaledInteger) dt = ScaledInteger(scale=0.03, min=0, max=9.9) assert dt.export_datatype() == {'type': 'scaled', 'max':330, 'min':0, 'scale':0.03} assert get_datatype(dt.export_datatype()).export_datatype() == dt.export_datatype() with pytest.raises(WrongTypeError): get_datatype(['scaled']) # dict missing with pytest.raises(WrongTypeError): get_datatype({'type': 'scaled', 'min':-10, 'max':10}) # no scale with pytest.raises(WrongTypeError): get_datatype({'type': 'scaled', 'min':10, 'max':-10, 'scale': 1}) # limits reversed with pytest.raises(WrongTypeError): get_datatype(['scaled', {'min':10, 'max':-10, 'scale': 1}, 2]) with pytest.raises(WrongTypeError): get_datatype(['enum']) with pytest.raises(WrongTypeError): get_datatype({'type': 'enum', 'a': -2}) assert isinstance(get_datatype({'type': 'enum', 'members': {'a': -2}}), EnumType) assert isinstance(get_datatype({'type': 'blob', 'maxbytes':1}), BLOBType) assert isinstance(get_datatype({'type': 'blob', 'minbytes':1, 'maxbytes':10}), BLOBType) with pytest.raises(WrongTypeError): get_datatype({'type': 'blob', 'minbytes':10, 'maxbytes':1}) with pytest.raises(WrongTypeError): get_datatype({'type': 'blob', 'minbytes':10, 'maxbytes':-10}) with pytest.raises(WrongTypeError): get_datatype(['blob', {'maxbytes':10}, 'x']) assert isinstance(get_datatype({'type': 'string', 'maxchars':1}), StringType) assert isinstance(get_datatype({'type': 'string', 'maxchars':1}), StringType) assert isinstance(get_datatype({'type': 'string', 'minchars':1, 'maxchars':10}), StringType) with pytest.raises(WrongTypeError): get_datatype({'type': 'string', 'minchars':10, 'maxchars':1}) with pytest.raises(WrongTypeError): get_datatype({'type': 'string', 'minchars':10, 'maxchars':-10}) with pytest.raises(WrongTypeError): get_datatype(['string', {'maxchars':-0}, 'x']) with pytest.raises(WrongTypeError): get_datatype(['array']) with pytest.raises(WrongTypeError): get_datatype({'type': 'array', 'members': [1]}) assert isinstance(get_datatype({'type': 'array', 'minlen':1, 'maxlen':1, 'members':{'type': 'blob', 'maxbytes':1}} ), ArrayOf) assert isinstance(get_datatype({'type': 'array', 'minlen':1, 'maxlen':1, 'members':{'type': 'blob', 'maxbytes':1}} ).members, BLOBType) with pytest.raises(WrongTypeError): get_datatype({'type': 'array', 'members':{'type': 'blob', 'maxbytes':1}, 'minbytes':-10}) with pytest.raises(WrongTypeError): get_datatype({'type': 'array', 'members':{'type': 'blob', 'maxbytes':1}, 'min':10, 'max':1}) with pytest.raises(WrongTypeError): get_datatype({'type': 'array', 'blob': {'max': 4}, 'maxbytes': 10}) with pytest.raises(WrongTypeError): get_datatype(['tuple']) with pytest.raises(WrongTypeError): get_datatype(['tuple', [1], 2, 3]) assert isinstance(get_datatype( {'type': 'tuple', 'members':[{'type': 'blob', 'maxbytes':1}]}), TupleOf) assert isinstance(get_datatype( {'type': 'tuple', 'members':[{'type': 'blob', 'maxbytes':1}]}).members[0], BLOBType) with pytest.raises(WrongTypeError): get_datatype({'type': 'tuple', 'members': {}}) with pytest.raises(WrongTypeError): get_datatype(['tuple', 10, -10]) assert isinstance(get_datatype({'type': 'tuple', 'members':[{'type': 'blob', 'maxbytes':1}, {'type': 'bool'}]}), TupleOf) with pytest.raises(WrongTypeError): get_datatype(['struct']) with pytest.raises(WrongTypeError): get_datatype(['struct', [1], 2, 3]) assert isinstance(get_datatype({'type': 'struct', 'members': {'name': {'type': 'blob', 'maxbytes':1}}}), StructOf) assert isinstance(get_datatype({'type': 'struct', 'members': {'name': {'type': 'blob', 'maxbytes':1}}}).members['name'], BLOBType) with pytest.raises(WrongTypeError): get_datatype({'type': 'struct', 'members': {}}) with pytest.raises(WrongTypeError): get_datatype({'type': 'struct', 'members':[1,2,3]}) @pytest.mark.parametrize('dt, contained_in', [ (FloatRange(-10, 10), FloatRange()), (IntRange(-10, 10), FloatRange()), (IntRange(-10, 10), IntRange(-20, 10)), (FloatRange(-10, 10), FloatRange(-15, 10)), (StringType(), StringType(isUTF8=True)), (StringType(10, 10), StringType()), (ArrayOf(StringType(), 3, 5), ArrayOf(StringType(), 3, 6)), (TupleOf(StringType(), BoolType()), TupleOf(StringType(), IntRange())), (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) with pytest.raises(BadValueError): contained_in.compatible(dt) @pytest.mark.parametrize('dt1, dt2', [ (FloatRange(-5.5, 5.5), ScaledInteger(10, -5.5, 5.5)), (IntRange(0,1), BoolType()), (IntRange(-10, 10), IntRange(-10, 10)), ]) def test_twoway_compatible(dt1, dt2): dt1.compatible(dt1) dt2.compatible(dt2) @pytest.mark.parametrize('dt1, dt2', [ (StringType(), FloatRange()), (IntRange(-10, 10), StringType()), (StructOf(a=BoolType(), b=BoolType()), ArrayOf(StringType(), 2)), (ArrayOf(BoolType(), 2), TupleOf(BoolType(), StringType())), (TupleOf(BoolType(), BoolType()), StructOf(a=BoolType(), b=BoolType())), (ArrayOf(StringType(), 3), ArrayOf(BoolType(), 3)), (TupleOf(StringType(), BoolType()), TupleOf(BoolType(), BoolType())), (StructOf(a=FloatRange(-1, 1), b=StringType()), StructOf(a=FloatRange(), b=BoolType())), ]) def test_incompatible(dt1, dt2): with pytest.raises(BadValueError): dt1.compatible(dt2) with pytest.raises(BadValueError): dt2.compatible(dt1) @pytest.mark.parametrize('dt', [FloatRange(), IntRange(), ScaledInteger(1)]) def test_lazy_validation(dt): generalConfig.defaults['lazy_number_validation'] = True dt('0') generalConfig.defaults['lazy_number_validation'] = False with pytest.raises(WrongTypeError): dt('0') mytuple = TupleOf(ScaledInteger(0.1, 0, 10, unit='$'), FloatRange(unit='$/min')) myarray = ArrayOf(mytuple) @pytest.mark.parametrize('unit, dt', [ ('m', FloatRange(unit='$/sec')), ('A', mytuple), ('V', myarray), ('X', StructOf(a=myarray, b=mytuple)), ]) def test_main_unit(unit, dt): fixed_dt = dt.copy() fixed_dt.set_main_unit(unit) before = repr(dt.export_datatype()) after = repr(fixed_dt.export_datatype()) assert '$' in before assert before != after assert before.replace('$', unit) == after def ex_validator(i): if i > 10: raise RuntimeError('too large') return i @pytest.mark.parametrize('validator, value, result', [ (dict, [('a', 1)], {'a': 1}), (ex_validator, 5, 5), # pylint: disable=unnecessary-lambda (lambda x: dict(x), {'a': 1}, {'a': 1}), # pylint: disable=unnecessary-lambda (lambda i: ex_validator(i) * 3, 3, 9), ]) def test_value_type(validator, value, result): t = ValueType() tv = ValueType(validator) assert t(value) == value assert tv(value) == result @pytest.mark.parametrize('validator, value', [ (dict, 'strinput'), (ex_validator, 20), # pylint: disable=unnecessary-lambda (lambda i: list(i), 1), ]) def test_value_type_rejecting(validator, value): t = ValueType() tv = ValueType(validator) assert t(value) == value with pytest.raises(ConfigError): tv(value)