diff --git a/test/test_option_calculation.py b/test/test_option_calculation.py index 117de9d..680303d 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) @@ -421,7 +354,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'] @@ -443,6 +376,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) @@ -535,7 +476,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) @@ -544,30 +486,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'] @@ -575,11 +522,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 ! @@ -587,6 +536,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(): @@ -653,7 +665,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(): @@ -663,31 +675,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/tiramisu/autolib.py b/tiramisu/autolib.py index efb7c0e..a176c8d 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -20,15 +20,16 @@ # ____________________________________________________________ "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._name`) + :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 +50,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 +64,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: @@ -133,9 +147,9 @@ def carry_out_calculation(name, config, callback, callback_params, for callbk in callbacks: if isinstance(callbk, tuple): # callbk is something link (opt, True|False) - option, force_permissive = callbk + opt, force_permissive = callbk path = config.cfgimpl_get_description().impl_get_path_by_opt( - option) + opt) # get value try: value = config._getattr(path, force_permissive=True) @@ -144,18 +158,22 @@ def carry_out_calculation(name, config, callback, callback_params, continue raise ConfigError(_('unable to carry out a calculation, ' 'option {0} has properties: {1} for: ' - '{2}').format(option._name, + '{2}').format(opt._name, err.proptype, - name)) - is_multi = option.impl_is_multi() + 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_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 + len_multi = len(value) one_is_multi = True tcparams.setdefault(key, []).append((value, is_multi)) else: @@ -168,16 +186,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 = {} @@ -196,10 +207,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 @@ -213,7 +221,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): @@ -224,4 +243,5 @@ def calculate(callback, args, kwargs): :param kwargs: in the callback's arity, the named parameters """ + print args, kwargs, callback(*args, **kwargs) return callback(*args, **kwargs) diff --git a/tiramisu/option.py b/tiramisu/option.py index f85c3ad..246363c 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -413,7 +413,7 @@ class Option(BaseOption): else: validator_params = {'': (val,)} # Raise ValueError if not valid - carry_out_calculation(self._name, config=context, + carry_out_calculation(self, config=context, callback=self._validator[0], callback_params=validator_params) @@ -714,7 +714,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 @@ -788,7 +788,7 @@ class IPOption(Option): # 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: + if val.startswith("0") and len(val) > 1: raise ValueError(_('invalid IP')) # 'standard' validation try: @@ -1284,13 +1284,20 @@ class OptionDescription(BaseOption): raise ValueError(_('master group with wrong' ' master name for {0}' ).format(self._name)) + 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" + raise ValueError(_("no child has same name has master group" " for: {0}").format(self._name)) else: raise ValueError(_('group_type: {0}' diff --git a/tiramisu/value.py b/tiramisu/value.py index 4426742..ed7d39d 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -137,7 +137,7 @@ class Values(object): callback, callback_params = opt._callback if callback_params is None: callback_params = {} - return carry_out_calculation(opt._name, config=self.context(), + return carry_out_calculation(opt, config=self.context(), callback=callback, callback_params=callback_params, index=index, max_len=max_len)