diff --git a/test/test_config.py b/test/test_config.py index ebfbc61..f660cf5 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -131,8 +131,10 @@ def test_cfgimpl_get_home_by_path(): config.bool = False assert config.cfgimpl_get_home_by_path('gc.dummy')[1] == 'dummy' assert config.cfgimpl_get_home_by_path('dummy')[1] == 'dummy' - #assert config.getpaths(include_groups=False) == ['gc.name', 'gc.dummy', 'gc.float', 'bool', 'objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop'] - #assert config.getpaths(include_groups=True) == ['gc', 'gc.name', 'gc.dummy', 'gc.float', 'bool', 'objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop'] + + +def test_not_valid_properties(): + raises(TypeError, "stroption = StrOption('str', 'Test string option', default='abc', properties=['mandatory',])") def test_information_config(): @@ -142,6 +144,7 @@ def test_information_config(): config.impl_set_information('info', string) assert config.impl_get_information('info') == string raises(ValueError, "config.impl_get_information('noinfo')") + assert config.impl_get_information('noinfo', 'default') == 'default' #FIXME test impl_get_xxx pour OD ou ne pas cacher @@ -150,8 +153,10 @@ def test_config_impl_get_path_by_opt(): config = Config(descr) dummy = config.unwrap_from_path('gc.dummy') boo = config.unwrap_from_path('bool') + unknown = IntOption('test', '') assert config.cfgimpl_get_description().impl_get_path_by_opt(boo) == 'bool' assert config.cfgimpl_get_description().impl_get_path_by_opt(dummy) == 'gc.dummy' + raises(AttributeError, "config.cfgimpl_get_description().impl_get_path_by_opt(unknown)") def test_config_impl_get_opt_by_path(): @@ -161,6 +166,7 @@ def test_config_impl_get_opt_by_path(): boo = config.unwrap_from_path('bool') assert config.cfgimpl_get_description().impl_get_opt_by_path('bool') == boo assert config.cfgimpl_get_description().impl_get_opt_by_path('gc.dummy') == dummy + raises(AttributeError, "config.cfgimpl_get_description().impl_get_opt_by_path('gc.unknown')") def test_information_display(): @@ -235,7 +241,40 @@ def test_duplicated_option_diff_od(): raises(ConflictError, "d2 = OptionDescription('od2', '', [g1])") + def test_cannot_assign_value_to_option_description(): descr = make_description() cfg = Config(descr) raises(TypeError, "cfg.gc = 3") + + +def test_config_multi(): + i1 = IntOption('test1', '', multi=True) + i2 = IntOption('test2', '', multi=True, default_multi=1) + i3 = IntOption('test3', '', default=[2], multi=True, default_multi=1) + od = OptionDescription('test', '', [i1, i2, i3]) + config = Config(od) + assert config.test1 == [] + assert config.test2 == [] + config.test2.append() + assert config.test2 == [1] + assert config.test3 == [2] + config.test3.append() + assert config.test3 == [2, 1] + + +def test_no_validation(): + i1 = IntOption('test1', '') + od = OptionDescription('test', '', [i1]) + c = Config(od) + setting = c.cfgimpl_get_settings() + c.test1 = 1 + raises(ValueError, 'c.test1 = "yes"') + assert c.test1 == 1 + setting.remove('validator') + c.test1 = "yes" + assert c.test1 == "yes" + setting.append('validator') + raises(ValueError, 'c.test1') + del(c.test1) + assert c.test1 == None diff --git a/test/test_config_ip.py b/test/test_config_ip.py index b7d3010..581cbd2 100644 --- a/test/test_config_ip.py +++ b/test/test_config_ip.py @@ -21,6 +21,11 @@ def test_ip(): c.b = '0.0.0.0' raises(ValueError, "c.b = '255.255.255.0'") + raises(ValueError, "IPOption('a', 'ip', default='192.000.023.01')") + d = IPOption('a', 'ip', default='192.0.23.1') + od = OptionDescription('od', '', [d]) + c = Config(od) + raises(ValueError, "c.a = '192.000.023.01'") def test_ip_default(): a = IPOption('a', '', '88.88.88.88') diff --git a/test/test_option.py b/test/test_option.py index 0492ed2..ec769c5 100644 --- a/test/test_option.py +++ b/test/test_option.py @@ -2,8 +2,13 @@ and to compare them """ import autopath +from py.test import raises -from tiramisu.option import BoolOption, IntOption +from tiramisu.option import IntOption, OptionDescription + + +def a_func(): + return None #def test_option_comparison(): @@ -36,3 +41,60 @@ from tiramisu.option import BoolOption, IntOption # assert dummy1 != dummy5 # assert dummy1 == dummy6 # assert dummy1 != dummy7 + + +def test_option_valid_name(): + IntOption('test', '') + raises(ValueError, 'IntOption(1, "")') + raises(ValueError, 'IntOption("impl_test", "")') + raises(ValueError, 'IntOption("_test", "")') + raises(ValueError, 'IntOption("unwrap_from_path", "")') + + +def test_option_with_callback(): + #no default value with callback + raises(ValueError, "IntOption('test', '', default=1, callback=a_func)") + + +def test_option_get_information(): + description = "it's ok" + string = 'some informations' + i = IntOption('test', description) + i.impl_set_information('info', string) + assert i.impl_get_information('info') == string + raises(ValueError, "i.impl_get_information('noinfo')") + assert i.impl_get_information('noinfo', 'default') == 'default' + assert i.impl_get_information('doc') == description + assert i.impl_getdoc() == description + + +def test_optiondescription_get_information(): + description = "it's ok" + string = 'some informations' + o = OptionDescription('test', description, []) + o.impl_set_information('info', string) + assert o.impl_get_information('info') == string + raises(ValueError, "o.impl_get_information('noinfo')") + assert o.impl_get_information('noinfo', 'default') == 'default' + assert o.impl_get_information('doc') == description + assert o.impl_getdoc() == description + + +def test_option_multi(): + IntOption('test', '', multi=True) + IntOption('test', '', multi=True, default_multi=1) + IntOption('test', '', default=[1], multi=True, default_multi=1) + #add default_multi to not multi's option + raises(ValueError, "IntOption('test', '', default_multi=1)") + #unvalid default_multi + raises(ValueError, "IntOption('test', '', multi=True, default_multi='yes')") + #not default_multi with callback + raises(ValueError, "IntOption('test', '', multi=True, default_multi=1, callback=a_func)") + + +def test_option_is_multi_by_default(): + assert IntOption('test', '').impl_is_empty_by_default() is True + assert IntOption('test', '', 1).impl_is_empty_by_default() is False + assert IntOption('test', '', multi=True).impl_is_empty_by_default() is True + assert IntOption('test', '', [1], multi=True).impl_is_empty_by_default() is False + assert IntOption('test', '', multi=True, default_multi=1).impl_is_empty_by_default() is True diff --git a/test/test_option_calculation.py b/test/test_option_calculation.py index a1a9fd1..2cacf5e 100644 --- a/test/test_option_calculation.py +++ b/test/test_option_calculation.py @@ -21,7 +21,13 @@ def return_list(value=None): def return_list2(*args): - return list(args) + l = [] + for arg in args: + if isinstance(arg, list): + l.extend(arg) + else: + l.append(arg) + return l def return_value(value=None): @@ -34,6 +40,10 @@ def return_value2(*args, **kwargs): return value +def return_calc(i, j, k): + return i + j + k + + def make_description(): gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcdummy = BoolOption('dummy', 'dummy', default=False) @@ -93,83 +103,6 @@ def test_identical_paths(): raises(ConflictError, "make_description_duplicates()") -def make_description2(): - gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref') - gcdummy = BoolOption('dummy', 'dummy', default=False) - - floatoption = FloatOption('float', 'Test float option', default=2.3) - - objspaceoption = ChoiceOption('objspace', 'Object space', - ['std', 'thunk'], 'std') - booloption = BoolOption('bool', 'Test boolean option', default=True) - intoption = IntOption('int', 'Test int option', default=0) - stroption = StrOption('str', 'Test string option', default="abc") - # first multi - boolop = BoolOption('boolop', 'Test boolean option op', default=True) - boolop.enable_multi() - wantref_option = BoolOption('wantref', 'Test requires', default=False, - requires=({'option': boolop, 'expected': True, 'action': 'hidden'},)) - # second multi - wantframework_option = BoolOption('wantframework', 'Test requires', - default=False, - requires=({'option': boolop, 'expected': True, 'action': 'hidden'},)) - wantframework_option.enable_multi() - - gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption]) - descr = OptionDescription('constraints', '', [gcgroup, booloption, objspaceoption, - wantref_option, stroption, - wantframework_option, - intoption, boolop]) - return descr - - -# FIXME: il faudra tester les validations sur les multis -#def test_multi_constraints(): -# "a multi in a constraint has to have the same length" -# descr = make_description2() -# cfg = Config(descr) -# cfg.boolop = [True, True, False] -# cfg.wantframework = [False, False, True] -# -#def test_multi_raise(): -# "a multi in a constraint has to have the same length" -# # FIXME fusionner les deux tests, MAIS PROBLEME : -# # il ne devrait pas etre necessaire de refaire une config -# # si la valeur est modifiee une deuxieme fois -> -# #raises(ConflictConfigError, "cfg.wantframework = [False, False, True]") -# # ExceptionFailure: 'DID NOT RAISE' -# descr = make_description2() -# cfg = Config(descr) -# cfg.boolop = [True] -# raises(ConflictConfigError, "cfg.wantframework = [False, False, True]") -# ____________________________________________________________ -# adding dynamically new options description schema -#def test_newoption_add_in_descr(): -# descr = make_description() -# newoption = BoolOption('newoption', 'dummy twoo', default=False) -# descr.add_child(newoption) -# config = Config(descr) -# assert config.newoption == False - -#def test_newoption_add_in_subdescr(): -# descr = make_description() -# newoption = BoolOption('newoption', 'dummy twoo', default=False) -# descr.gc.add_child(newoption) -# config = Config(descr) -# config.bool = False -# assert config.gc.newoption == False - -#def test_newoption_add_in_config(): -# descr = make_description() -# config = Config(descr) -# config.bool = False -# newoption = BoolOption('newoption', 'dummy twoo', default=False) -# descr.add_child(newoption) -# config.cfgimpl_update() -# assert config.newoption == False -# ____________________________________________________________ - - def make_description_requires(): gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcdummy = BoolOption('dummy', 'dummy', default=False) @@ -310,6 +243,19 @@ def test_callback(): assert cfg.val1 == 'val' +def test_callback_params_without_callback(): + raises(ValueError, "StrOption('val2', '', callback_params={'': ('yes',)})") + + +def test_callback_invalid(): + raises(ValueError, 'val1 = StrOption("val1", "", callback="string")') + raises(ValueError, 'val1 = StrOption("val1", "", callback=return_val, callback_params="string")') + val1 = StrOption('val1', "", 'val') + raises(ValueError, "StrOption('val2', '', callback=return_value, callback_params={'': 'string'})") + raises(ValueError, "StrOption('val4', '', callback=return_value, callback_params={'value': (('string', False),)})") + raises(ValueError, "StrOption('val4', '', callback=return_value, callback_params={'value': ((val1, 'string'),)})") + + def test_callback_value(): val1 = StrOption('val1', "", 'val') val2 = StrOption('val2', "", callback=return_value, callback_params={'': ((val1, False),)}) @@ -422,7 +368,7 @@ def test_callback_multi_value(): cfg.val1.append('new-val2') assert cfg.val1 == ['new-val', 'new-val2'] assert cfg.val2 == ['new-val', 'new-val2'] - assert cfg.val4 == ['new-val', 'yes', 'new-val2', 'yes'] + assert cfg.val4 == ['new-val', 'new-val2', 'yes'] del(cfg.val1) assert cfg.val1 == ['val'] assert cfg.val2 == ['val'] @@ -444,6 +390,14 @@ def test_callback_multi_list(): assert cfg.val1 == ['val', 'val'] +def test_callback_multi_list_extend(): + val1 = StrOption('val1', "", callback=return_list2, callback_params={'': (['1', '2', '3'], ['4', '5'])}, multi=True) + maconfig = OptionDescription('rootconfig', '', [val1]) + cfg = Config(maconfig) + cfg.read_write() + assert cfg.val1 == ['1', '2', '3', '4', '5'] + + def test_callback_master_and_slaves_master(): val1 = StrOption('val1', "", multi=True, callback=return_val) val2 = StrOption('val2', "", multi=True) @@ -453,7 +407,7 @@ def test_callback_master_and_slaves_master(): cfg = Config(maconfig) cfg.read_write() assert cfg.val1.val1 == ['val'] - cfg.val1.val1.append(None) + cfg.val1.val1.append() assert cfg.val1.val1 == ['val', 'val'] assert cfg.val1.val2 == [None, None] @@ -468,7 +422,7 @@ def test_callback_master_and_slaves_master_list(): cfg.read_write() assert cfg.val1.val1 == ['val', 'val'] assert cfg.val1.val2 == [None, None] - cfg.val1.val1.append(None) + cfg.val1.val1.append() assert cfg.val1.val1 == ['val', 'val', None] assert cfg.val1.val2 == [None, None, None] del(cfg.val1.val1) @@ -536,7 +490,8 @@ def test_callback_master_and_slaves_value(): val3 = StrOption('val3', "", multi=True, callback=return_value, callback_params={'': ('yes',)}) val4 = StrOption('val4', '', multi=True, default=['val10', 'val11']) val5 = StrOption('val5', "", multi=True, callback=return_value, callback_params={'': ((val4, False),)}) - interface1 = OptionDescription('val1', '', [val1, val2, val3, val5]) + val6 = StrOption('val6', "", multi=True, callback=return_value, callback_params={'': ((val5, False),)}) + interface1 = OptionDescription('val1', '', [val1, val2, val3, val5, val6]) interface1.impl_set_group_type(groups.master) maconfig = OptionDescription('rootconfig', '', [interface1, val4]) cfg = Config(maconfig) @@ -545,30 +500,35 @@ def test_callback_master_and_slaves_value(): assert cfg.val1.val2 == [] assert cfg.val1.val3 == [] assert cfg.val1.val5 == [] + assert cfg.val1.val6 == [] # cfg.val1.val1 = ['val1'] assert cfg.val1.val1 == ['val1'] assert cfg.val1.val2 == ['val1'] assert cfg.val1.val3 == ['yes'] assert cfg.val1.val5 == ['val10'] + assert cfg.val1.val6 == ['val10'] # cfg.val1.val1.append('val2') assert cfg.val1.val1 == ['val1', 'val2'] assert cfg.val1.val2 == ['val1', 'val2'] assert cfg.val1.val3 == ['yes', 'yes'] assert cfg.val1.val5 == ['val10', 'val11'] + assert cfg.val1.val6 == ['val10', 'val11'] # cfg.val1.val1 = ['val1', 'val2', 'val3'] assert cfg.val1.val1 == ['val1', 'val2', 'val3'] assert cfg.val1.val2 == ['val1', 'val2', 'val3'] assert cfg.val1.val3 == ['yes', 'yes', 'yes'] assert cfg.val1.val5 == ['val10', 'val11', None] + assert cfg.val1.val6 == ['val10', 'val11', None] # cfg.val1.val1.pop(2) assert cfg.val1.val1 == ['val1', 'val2'] assert cfg.val1.val2 == ['val1', 'val2'] assert cfg.val1.val3 == ['yes', 'yes'] assert cfg.val1.val5 == ['val10', 'val11'] + assert cfg.val1.val6 == ['val10', 'val11'] # cfg.val1.val2 = ['val2', 'val2'] cfg.val1.val3 = ['val2', 'val2'] @@ -576,11 +536,13 @@ def test_callback_master_and_slaves_value(): assert cfg.val1.val2 == ['val2', 'val2'] assert cfg.val1.val3 == ['val2', 'val2'] assert cfg.val1.val5 == ['val2', 'val2'] + assert cfg.val1.val6 == ['val2', 'val2'] # cfg.val1.val1.append('val3') assert cfg.val1.val2 == ['val2', 'val2', 'val3'] assert cfg.val1.val3 == ['val2', 'val2', 'yes'] assert cfg.val1.val5 == ['val2', 'val2', None] + assert cfg.val1.val6 == ['val2', 'val2', None] cfg.cfgimpl_get_settings().remove('cache') cfg.val4 = ['val10', 'val11', 'val12'] #if value is already set, not updated ! @@ -588,6 +550,69 @@ def test_callback_master_and_slaves_value(): cfg.val1.val1.append('val3') cfg.val1.val1 = ['val1', 'val2', 'val3'] assert cfg.val1.val5 == ['val2', 'val2', 'val12'] + assert cfg.val1.val6 == ['val2', 'val2', 'val12'] + + +def test_callback_master(): + val2 = StrOption('val2', "", multi=True, callback=return_value) + val1 = StrOption('val1', "", multi=True, callback=return_value, callback_params={'': ((val2, False),)}) + interface1 = OptionDescription('val1', '', [val1, val2]) + raises(ValueError, "interface1.impl_set_group_type(groups.master)") + + +def test_callback_master_and_other_master_slave(): + val1 = StrOption('val1', "", multi=True) + val2 = StrOption('val2', "", multi=True) + val3 = StrOption('val3', "", multi=True) + val4 = StrOption('val4', '', multi=True, default=['val10', 'val11']) + val5 = StrOption('val5', "", multi=True, callback=return_value, callback_params={'': ((val1, False),)}) + val6 = StrOption('val6', "", multi=True, callback=return_value, callback_params={'': ((val2, False),)}) + interface1 = OptionDescription('val1', '', [val1, val2, val3]) + interface1.impl_set_group_type(groups.master) + interface2 = OptionDescription('val4', '', [val4, val5, val6]) + interface2.impl_set_group_type(groups.master) + maconfig = OptionDescription('rootconfig', '', [interface1, interface2]) + cfg = Config(maconfig) + cfg.read_write() + assert cfg.val4.val4 == ['val10', 'val11'] + assert cfg.val4.val5 == [None, None] + assert cfg.val4.val6 == [None, None] + cfg.val1.val1 = ['yes'] + assert cfg.val4.val4 == ['val10', 'val11'] + assert cfg.val4.val5 == ['yes', None] + assert cfg.val4.val6 == [None, None] + cfg.val1.val2 = ['no'] + assert cfg.val4.val4 == ['val10', 'val11'] + assert cfg.val4.val5 == ['yes', None] + assert cfg.val4.val6 == ['no', None] + cfg.val1.val1 = ['yes', 'yes', 'yes'] + cfg.val1.val2 = ['no', 'no', 'no'] + assert cfg.val4.val4 == ['val10', 'val11'] + assert cfg.val4.val5 == ['yes', 'yes'] + assert cfg.val4.val6 == ['no', 'no'] + + +def test_callback_different_type(): + val = IntOption('val', "", default=2) + val_ = IntOption('val_', "", default=3) + val1 = IntOption('val1', "", multi=True) + val2 = IntOption('val2', "", multi=True, callback=return_calc, callback_params={'': ((val, False), (val1, False)), 'k': ((val_, False),)}) + interface1 = OptionDescription('val1', '', [val1, val2]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('rootconfig', '', [interface1, val, val_]) + cfg = Config(maconfig) + cfg.read_write() + assert cfg.val1.val1 == [] + assert cfg.val1.val2 == [] + cfg.val1.val1 = [1] + assert cfg.val1.val1 == [1] + assert cfg.val1.val2 == [6] + cfg.val1.val1 = [1, 3] + assert cfg.val1.val1 == [1, 3] + assert cfg.val1.val2 == [6, 8] + cfg.val1.val1 = [1, 3, 5] + assert cfg.val1.val1 == [1, 3, 5] + assert cfg.val1.val2 == [6, 8, 10] def test_callback_hidden(): @@ -654,7 +679,7 @@ def test_callback_multi_list_params(): maconfig = OptionDescription('rootconfig', '', [val1, oval2]) cfg = Config(maconfig) cfg.read_write() - assert cfg.val2.val2 == ['val', 'val', 'val', 'val'] + assert cfg.val2.val2 == ['val', 'val'] def test_callback_multi_list_params_key(): @@ -664,31 +689,4 @@ def test_callback_multi_list_params_key(): maconfig = OptionDescription('rootconfig', '', [val1, oval2]) cfg = Config(maconfig) cfg.read_write() - assert cfg.val2.val2 == ['val', 'val', 'val', 'val'] - - -def test_callback_multi_multi(): - val1 = StrOption('val1', "", multi=True, default=['val1', 'val2', 'val3']) - val2 = StrOption('val2', "", multi=True, default=['val11', 'val12']) - val3 = StrOption('val3', "", default='val4') - val4 = StrOption('val4', "", multi=True, callback=return_list2, callback_params={'': ((val1, False), (val2, False))}) - val5 = StrOption('val5', "", multi=True, callback=return_list2, callback_params={'': ((val1, False), (val3, False))}) - val6 = StrOption('val6', "", multi=True, default=['val21', 'val22', 'val23']) - val7 = StrOption('val7', "", multi=True, callback=return_list2, callback_params={'': ((val1, False), (val6, False))}) - raises(ValueError, "StrOption('val8', '', multi=True, callback=return_list2, callback_params={'value': ((val1, False), (val6, False))})") - maconfig = OptionDescription('rootconfig', '', [val1, val2, val3, val4, val5, val6, val7]) - cfg = Config(maconfig) - cfg.read_write() - raises(ConfigError, "cfg.val4") - assert cfg.val5 == ['val1', 'val4', 'val2', 'val4', 'val3', 'val4'] - assert cfg.val7 == ['val1', 'val21', 'val2', 'val22', 'val3', 'val23'] - - -def test_multi_with_no_value(): - #First option return [] (so without value) - val1 = StrOption('val1', "", ['val'], multi=True) - val2 = StrOption('val2', "", multi=True) - val3 = StrOption('val3', '', multi=True, callback=return_value, callback_params={'': ((val2, False),), 'value': ((val1, False),)}) - od = OptionDescription('od', '', [val1, val2, val3]) - c = Config(od) - raises(ConfigError, "c.val3") + assert cfg.val2.val2 == ['val', 'val'] diff --git a/test/test_option_consistency.py b/test/test_option_consistency.py index 075f259..5e85530 100644 --- a/test/test_option_consistency.py +++ b/test/test_option_consistency.py @@ -8,6 +8,17 @@ from tiramisu.option import IPOption, NetworkOption, NetmaskOption, IntOption,\ from tiramisu.error import ConfigError +def test_consistency(): + a = IntOption('a', '') + b = IntOption('b', '') + od = OptionDescription('od', '', [a, b]) + a.impl_add_consistency('not_equal', b) + #consistency to itself + raises(ConfigError, "a.impl_add_consistency('not_equal', a)") + #consistency with string + raises(ConfigError, "a.impl_add_consistency('not_equal', 'a')") + + def test_consistency_not_equal(): a = IntOption('a', '') b = IntOption('b', '') diff --git a/test/test_option_owner.py b/test/test_option_owner.py index bc96285..b758e91 100644 --- a/test/test_option_owner.py +++ b/test/test_option_owner.py @@ -5,7 +5,7 @@ from tiramisu.setting import owners from tiramisu.config import Config from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \ StrOption, OptionDescription -from tiramisu.error import ConfigError +from tiramisu.error import ConfigError, ConstError def make_description(): @@ -52,6 +52,17 @@ def test_addowner(): assert cfg.getowner(gcdummy) == owners.gen_config +def test_addowner_multiple_time(): + owners.addowner("testowner") + raises(ConstError, 'owners.addowner("testowner")') + + +def test_delete_owner(): + owners.addowner('deleted') + raises(ConstError, 'del(owners.deleted)') + raises(ValueError, 'del(owners.deleted2)') + + def test_owner_is_not_a_string(): gcdummy = BoolOption('dummy', 'dummy', default=False) descr = OptionDescription('tiramisu', '', [gcdummy]) diff --git a/test/test_permissive.py b/test/test_permissive.py index 38a8bcf..20e6535 100644 --- a/test/test_permissive.py +++ b/test/test_permissive.py @@ -9,7 +9,8 @@ from tiramisu.error import PropertiesOptionError def make_description(): u1 = IntOption('u1', '', properties=('frozen', 'mandatory', 'disabled', )) - return OptionDescription('od1', '', [u1]) + u2 = IntOption('u2', '', properties=('frozen', 'mandatory', 'disabled', )) + return OptionDescription('od1', '', [u1, u2]) def test_permissive(): @@ -91,3 +92,116 @@ def test_invalid_permissive(): setting = config.cfgimpl_get_settings() config.read_write() raises(TypeError, "setting.setpermissive(['frozen', 'disabled',])") + + +def test_permissive_option(): + descr = make_description() + u1 = descr.u1 + config = Config(descr) + setting = config.cfgimpl_get_settings() + config.read_write() + + props = [] + try: + config.u1 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + props = [] + try: + config.u2 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + + setting.setpermissive(('disabled',), u1) + props = [] + try: + config.u1 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + props = [] + try: + config.u2 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + + setting.append('permissive') + config.u1 + props = [] + try: + config.u2 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + + setting.remove('permissive') + props = [] + try: + config.u1 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + props = [] + try: + config.u2 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + + +def test_permissive_option_mandatory(): + descr = make_description() + u1 = descr.u1 + config = Config(descr) + setting = config.cfgimpl_get_settings() + config.read_only() + props = [] + try: + config.u1 + except PropertiesOptionError as err: + props = err.proptype + assert set(props) == set(['disabled', 'mandatory']) + setting.setpermissive(('mandatory', 'disabled',), u1) + setting.append('permissive') + config.u1 + setting.remove('permissive') + try: + config.u1 + except PropertiesOptionError as err: + props = err.proptype + assert set(props) == set(['disabled', 'mandatory']) + + +def test_permissive_option_frozen(): + descr = make_description() + config = Config(descr) + u1 = descr.u1 + setting = config.cfgimpl_get_settings() + config.read_write() + setting.setpermissive(('frozen', 'disabled'), u1) + try: + config.u1 = 1 + except PropertiesOptionError as err: + props = err.proptype + assert set(props) == set(['frozen', 'disabled']) + setting.append('permissive') + config.u1 = 1 + assert config.u1 == 1 + setting.remove('permissive') + try: + config.u1 = 1 + except PropertiesOptionError as err: + props = err.proptype + assert set(props) == set(['frozen', 'disabled']) + + +def test_invalid_option_permissive(): + descr = make_description() + u1 = descr.u1 + config = Config(descr) + setting = config.cfgimpl_get_settings() + config.read_write() + raises(TypeError, "setting.setpermissive(['frozen', 'disabled',], u1)") diff --git a/test/test_requires.py b/test/test_requires.py index 69501b9..4f35cfd 100644 --- a/test/test_requires.py +++ b/test/test_requires.py @@ -26,6 +26,20 @@ def test_requires(): assert props == ['disabled'] +def test_requires_invalid(): + a = BoolOption('activate_service', '', True) + raises(ValueError, "IPOption('ip_address_service', '', requires='string')") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'expected': False, 'action': 'disabled', 'unknown': True}])") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'expected': False}])") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'action': 'disabled'}])") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'expected': False, 'action': 'disabled'}])") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'expected': False, 'action': 'disabled', 'inverse': 'string'}])") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'expected': False, 'action': 'disabled', 'transitive': 'string'}])") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'expected': False, 'action': 'disabled', 'same_action': 'string'}])") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': 'string', 'expected': False, 'action': 'disabled'}])") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'expected': 'string', 'action': 'disabled'}])") + + def test_requires_same_action(): a = BoolOption('activate_service', '', True) b = BoolOption('activate_service_web', '', True, @@ -504,6 +518,11 @@ def test_requires_multi_disabled_inverse_2(): # c.cfgimpl_get_settings()[b].append("test") +def test_requires_different_inverse(): + a = BoolOption('activate_service', '', True) + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'expected': True, 'action': 'disabled', 'inverse': True}, {'option': a, 'expected': True, 'action': 'disabled', 'inverse': False}])") + + def test_requires_recursive_path(): a = BoolOption('activate_service', '', True) b = IPOption('ip_address_service', '', diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index eea1c62..2c6c7d8 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -20,15 +20,17 @@ # ____________________________________________________________ "enables us to carry out a calculation and return an option's value" from tiramisu.error import PropertiesOptionError, ConfigError +from tiramisu.setting import multitypes from tiramisu.i18n import _ # ____________________________________________________________ -def carry_out_calculation(name, config, callback, callback_params, +def carry_out_calculation(option, config, callback, callback_params, index=None, max_len=None): """a function that carries out a calculation for an option's value :param name: the option name (`opt.impl_getname()`) + :param name: the option :param config: the context config in order to have the whole options available :param callback: the name of the callback function @@ -49,13 +51,13 @@ def carry_out_calculation(name, config, callback, callback_params, Values could have multiple values only when key is ''. * if no callback_params: - => calculate() + => calculate(, [], {}) * if callback_params={'': ('yes',)} - => calculate('yes') + => calculate(, ['yes'], {}) * if callback_params={'value': ('yes',)} - => calculate(value='yes') + => calculate(, [], {'value': 'yes'}) * if callback_params={'': ('yes', 'no')} => calculate('yes', 'no') @@ -63,58 +65,71 @@ def carry_out_calculation(name, config, callback, callback_params, * if callback_params={'value': ('yes', 'no')} => ValueError() + * if callback_params={'': (['yes', 'no'],)} + => calculate(, ['yes', 'no'], {}) + + * if callback_params={'value': ('yes', 'no')} + => raises ValueError() + * if callback_params={'': ((opt1, False),)} - a simple option: opt1 == 11 - => calculate(11) + => calculate(, [11], {}) - - a multi option: + - a multi option and not master/slave: opt1 == [1, 2, 3] - => calculate(1) - => calculate(2) - => calculate(3) + => calculate(, [[1, 2, 3]], {}) + + - option is master or slave of opt1: + opt1 == [1, 2, 3] + => calculate(, [1], {}) + => calculate(, [2], {}) + => calculate(, [3], {}) + + - opt is a master or slave but not related to option: + opt1 == [1, 2, 3] + => calculate(, [[1, 2, 3]], {}) * if callback_params={'value': ((opt1, False),)} - a simple option: opt1 == 11 - => calculate(value=11) + => calculate(, [], {'value': 11}) - a multi option: opt1 == [1, 2, 3] - => calculate(value=1) - => calculate(value=2) - => calculate(value=3) + => calculate(, [], {'value': [1, 2, 3]}) * if callback_params={'': ((opt1, False), (opt2, False))} + - two single options + opt1 = 11 + opt2 = 12 + => calculate(, [11, 12], {}) + - a multi option with a simple option opt1 == [1, 2, 3] - opt2 == 11 - => calculate(1, 11) - => calculate(2, 11) - => calculate(3, 11) + opt2 == 12 + => calculate(, [[1, 2, 3], 12], {}) - a multi option with an other multi option but with same length opt1 == [1, 2, 3] opt2 == [11, 12, 13] - => calculate(1, 11) - => calculate(2, 12) - => calculate(3, 13) + => calculate(, [[1, 2, 3], [11, 12, 13]], {}) - a multi option with an other multi option but with different length opt1 == [1, 2, 3] opt2 == [11, 12] - => ConfigError() + => calculate(, [[1, 2, 3], [11, 12]], {}) - a multi option without value with a simple option opt1 == [] opt2 == 11 - => [] + => calculate(, [[], 12], {}) * if callback_params={'value': ((opt1, False), (opt2, False))} - => ConfigError() + => raises ValueError() If index is not None, return a value, otherwise return: @@ -129,41 +144,42 @@ def carry_out_calculation(name, config, callback, callback_params, # multi's option should have same value for all option len_multi = None - if callback_params != []: - for callbacks in callback_params: - key = callbacks.name - for callbk in callbacks.params: - if callbk.option is not None: - # callbk is something link (opt, True|False) - option = callbk.get_option(config) - force_permissive = callbk.force_permissive - path = config.cfgimpl_get_description().impl_get_path_by_opt( - option) - # get value - try: - value = config._getattr(path, force_permissive=True) - except PropertiesOptionError as err: - if force_permissive: - continue - raise ConfigError(_('unable to carry out a calculation, ' - 'option {0} has properties: {1} for: ' - '{2}').format(option.impl_getname(), - err.proptype, - name)) - is_multi = option.impl_is_multi() - if is_multi: - len_value = len(value) - if len_multi is not None and len_multi != len_value: - raise ConfigError(_('unable to carry out a ' - 'calculation, option value with' - ' multi types must have same ' - 'length for: {0}').format(name)) - len_multi = len_value - one_is_multi = True - tcparams.setdefault(key, []).append((value, is_multi)) - else: - # callbk is a value and not a multi - tcparams.setdefault(key, []).append((callbk.value, False)) + for key, callbacks in callback_params.items(): + for callbk in callbacks: + if isinstance(callbk, tuple): + # callbk is something link (opt, True|False) + opt, force_permissive = callbk + path = config.cfgimpl_get_description().impl_get_path_by_opt( + opt) + # get value + try: + value = config._getattr(path, force_permissive=True) + except PropertiesOptionError as err: + if force_permissive: + continue + raise ConfigError(_('unable to carry out a calculation, ' + 'option {0} has properties: {1} for: ' + '{2}').format(opt._name, + err.proptype, + option._name)) + + is_multi = False + if opt.impl_is_multi(): + #opt is master, search if option is a slave + if opt.impl_get_multitype() == multitypes.master: + if option in opt.impl_get_master_slaves(): + is_multi = True + #opt is slave, search if option is an other slaves + elif opt.impl_get_multitype() == multitypes.slave: + if option in opt.impl_get_master_slaves().impl_get_master_slaves(): + is_multi = True + if is_multi: + len_multi = len(value) + one_is_multi = True + tcparams.setdefault(key, []).append((value, is_multi)) + else: + # callbk is a value and not a multi + tcparams.setdefault(key, []).append((callbk, False)) # if one value is a multi, launch several time calculate # if index is set, return a value @@ -171,16 +187,9 @@ def carry_out_calculation(name, config, callback, callback_params, if one_is_multi: ret = [] if index: - if index < len_multi: - range_ = [index] - else: - range_ = [] - ret = None + range_ = [index] else: - if max_len and max_len < len_multi: - range_ = range(max_len) - else: - range_ = range(len_multi) + range_ = range(len_multi) for incr in range_: args = [] kwargs = {} @@ -199,10 +208,7 @@ def carry_out_calculation(name, config, callback, callback_params, if index: ret = calc else: - if isinstance(calc, list): - ret.extend(calc) - else: - ret.append(calc) + ret.append(calc) return ret else: # no value is multi @@ -216,7 +222,18 @@ def carry_out_calculation(name, config, callback, callback_params, args.append(couple[0]) else: kwargs[key] = couple[0] - return calculate(callback, args, kwargs) + ret = calculate(callback, args, kwargs) + if callback_params != {}: + if isinstance(ret, list) and max_len: + ret = ret[:max_len] + if len(ret) < max_len: + ret = ret + [None] * (max_len - len(ret)) + if isinstance(ret, list) and index: + if len(ret) < index + 1: + ret = None + else: + ret = ret[index] + return ret def calculate(callback, args, kwargs): diff --git a/tiramisu/config.py b/tiramisu/config.py index 7351dd3..ea7a6b3 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -253,7 +253,8 @@ class SubConfig(object): force_properties=force_properties, force_permissive=force_permissive) - def find(self, bytype=None, byname=None, byvalue=None, type_='option'): + def find(self, bytype=None, byname=None, byvalue=None, type_='option', + check_properties=True): """ finds a list of options recursively in the config @@ -265,11 +266,11 @@ class SubConfig(object): return self._cfgimpl_get_context()._find(bytype, byname, byvalue, first=False, type_=type_, - _subpath=self.cfgimpl_get_path() - ) + _subpath=self.cfgimpl_get_path(), + check_properties=check_properties) def find_first(self, bytype=None, byname=None, byvalue=None, - type_='option', display_error=True): + type_='option', display_error=True, check_properties=True): """ finds an option recursively in the config @@ -280,7 +281,8 @@ class SubConfig(object): """ return self._cfgimpl_get_context()._find( bytype, byname, byvalue, first=True, type_=type_, - _subpath=self.cfgimpl_get_path(), display_error=display_error) + _subpath=self.cfgimpl_get_path(), display_error=display_error, + check_properties=check_properties) def _find(self, bytype, byname, byvalue, first, type_='option', _subpath=None, check_properties=True, display_error=True): diff --git a/tiramisu/option.py b/tiramisu/option.py index ec231c9..b4db8a8 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -47,9 +47,7 @@ forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first', def valid_name(name): "an option's name is a str and does not start with 'impl' or 'cfgimpl'" - try: - name = str(name) - except: + if not isinstance(name, str): return False if re.match(name_regexp, name) is None and not name.startswith('_') \ and name not in forbidden_names \ @@ -636,7 +634,7 @@ class Option(BaseOption): else: validator_params = (FakeCallbackParam('', (FakeCallbackParamOption(value=val),)),) # Raise ValueError if not valid - carry_out_calculation(self.impl_getname(), config=context, + carry_out_calculation(self, config=context, callback=self._validator, callback_params=validator_params) @@ -674,17 +672,13 @@ class Option(BaseOption): do_validation(value, force_index) else: if not isinstance(value, list): - raise ValueError(_("which must be a list").format(value, - self.impl_getname())) + raise ValueError(_("invalid value {0} for option {1} which must be a list").format(value, self.impl_getname())) for index, val in enumerate(value): do_validation(val, index) - def impl_getdefault(self, default_multi=False): + def impl_getdefault(self): "accessing the default value" - if not default_multi or not self.impl_is_multi(): - return self._default - else: - return self.getdefault_multi() + return self._default def impl_getdefault_multi(self): "accessing the default value for a multi" @@ -940,7 +934,7 @@ class StrOption(Option): if sys.version_info[0] >= 3: - #UnicodeOption is same has StrOption in python 3+ + #UnicodeOption is same as StrOption in python 3+ class UnicodeOption(StrOption): __slots__ = tuple() pass @@ -1022,6 +1016,12 @@ class IPOption(Option): warnings_only=warnings_only) def _validate(self, value): + # sometimes an ip term starts with a zero + # but this does not fit in some case, for example bind does not like it + for val in value.split('.'): + if val.startswith("0") and len(val) > 1: + raise ValueError(_('invalid IP')) + # 'standard' validation try: IP('{0}/32'.format(value)) except ValueError: @@ -1533,7 +1533,6 @@ class OptionDescription(BaseOption): self._optiondescription_group_type = group_type if isinstance(group_type, groups.MasterGroupType): #if master (same name has group) is set - identical_master_child_name = False #for collect all slaves slaves = [] master = None @@ -1549,8 +1548,7 @@ class OptionDescription(BaseOption): "in group {1}" ": this option is not a multi" "").format(child.impl_getname(), self.impl_getname())) - if child.impl_getname() == self.impl_getname(): - identical_master_child_name = True + if child._name == self.impl_getname(): child._multitype = multitypes.master master = child else: @@ -1559,14 +1557,18 @@ class OptionDescription(BaseOption): raise ValueError(_('master group with wrong' ' master name for {0}' ).format(self.impl_getname())) + if master._callback is not None and master._callback[1] is not None: + for key, callbacks in master._callback[1].items(): + for callbk in callbacks: + if isinstance(callbk, tuple): + if callbk[0] in slaves: + raise ValueError(_("callback of master's option shall " + "not refered a slave's ones")) master._master_slaves = tuple(slaves) for child in self.impl_getchildren(): if child != master: child._master_slaves = master child._multitype = multitypes.slave - if not identical_master_child_name: - raise ValueError(_("no child has same nom has master group" - " for: {0}").format(self.impl_getname())) else: raise ValueError(_('group_type: {0}' ' not allowed').format(group_type)) diff --git a/tiramisu/setting.py b/tiramisu/setting.py index e9449b7..f95674e 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -242,6 +242,12 @@ multitypes = MultiTypeModule() populate_multitypes() +# ____________________________________________________________ +class Undefined(): + pass + + +undefined = Undefined() # ____________________________________________________________ class Property(object): "a property is responsible of the option's value access rules" @@ -410,10 +416,11 @@ class Settings(object): """ # opt properties properties = copy(self._getproperties(opt_or_descr, path)) - # remove opt permissive - properties -= self._p_.getpermissive(path) - # remove global permissive if need self_properties = copy(self._getproperties()) + # remove opt permissive + if force_permissive is True or 'permissive' in self_properties: + properties -= self._p_.getpermissive(path) + # remove global permissive if need if force_permissive is True or 'permissive' in self_properties: properties -= self._p_.getpermissive() if force_permissives is not None: diff --git a/tiramisu/value.py b/tiramisu/value.py index 4d4b2ff..c969291 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -22,7 +22,7 @@ from copy import copy import sys import weakref from tiramisu.error import ConfigError, SlaveError -from tiramisu.setting import owners, multitypes, expires_time +from tiramisu.setting import owners, multitypes, expires_time, undefined from tiramisu.autolib import carry_out_calculation from tiramisu.i18n import _ from tiramisu.option import SymLinkOption @@ -137,7 +137,7 @@ class Values(object): callback, callback_params = opt.impl_get_callback() if callback_params is None: callback_params = {} - return carry_out_calculation(opt.impl_getname(), config=self.context(), + return carry_out_calculation(opt, config=self.context(), callback=callback, callback_params=callback_params, index=index, max_len=max_len) @@ -452,8 +452,7 @@ class Multi(list): values._getcallback_value(slave, index=index), force=True) else: - value_slave.append(slave.impl_getdefault_multi(), - force=True) + value_slave.append(undefined, force=True) def __setitem__(self, index, value): self._validate(value, index) @@ -461,7 +460,7 @@ class Multi(list): super(Multi, self).__setitem__(index, value) self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self) - def append(self, value, force=False): + def append(self, value=undefined, force=False): """the list value can be updated (appened) only if the option is a master """ @@ -471,12 +470,14 @@ class Multi(list): " which is a slave").format(self.opt.impl_getname())) elif self.opt.impl_get_multitype() == multitypes.master: values = self.context().cfgimpl_get_values() - if value is None and self.opt.impl_has_callback(): + if value is undefined and self.opt.impl_has_callback(): value = values._getcallback_value(self.opt) #Force None il return a list if isinstance(value, list): value = None index = self.__len__() + if value is undefined: + value = self.opt.impl_getdefault_multi() self._validate(value, index) super(Multi, self).append(value) self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, diff --git a/translations/fr/tiramisu.po b/translations/fr/tiramisu.po index ec9e66d..bff777c 100644 --- a/translations/fr/tiramisu.po +++ b/translations/fr/tiramisu.po @@ -116,8 +116,8 @@ msgid "invalid value for option {0}: {1}" msgstr "valeur invalide pour l'option {0} : {1}" #: tiramisu/option.py:454 -msgid "which must be a list" -msgstr "lequel doit ĂȘtre une liste" +msgid ""invalid value {0} for option {1} which must be a list" +msgstr "valeur invalide pour l'option {0} : {1} laquelle doit ĂȘtre une liste" #: tiramisu/option.py:514 msgid "consistency should be set with an option"