diff --git a/sdk/cpp/core/tests/models/ydktest-sanity@2015-11-17.yang b/sdk/cpp/core/tests/models/ydktest-sanity@2015-11-17.yang index 0b4711cdb..e371c4474 100644 --- a/sdk/cpp/core/tests/models/ydktest-sanity@2015-11-17.yang +++ b/sdk/cpp/core/tests/models/ydktest-sanity@2015-11-17.yang @@ -579,7 +579,7 @@ module ydktest-sanity { } } - grouping three-level-w-list-at-two { + grouping three-level-w-list-at-two { container inbtw-list { description "config for one_level list data"; list ldata { @@ -645,6 +645,21 @@ module ydktest-sanity { } } + grouping two-key-list { + list two-key-list { + key "first second"; + leaf first { + type string; + } + leaf second { + type uint32; + } + leaf property { + type string; + } + } + } + container runner { //container at 1,2,3 nested level @@ -666,6 +681,8 @@ module ydktest-sanity { uses not-supported; + uses two-key-list; + container runner-2 { presence "Runner-2 is presence controlled"; diff --git a/sdk/python/core/tests/test_entity_diff.py b/sdk/python/core/tests/test_entity_diff.py new file mode 100644 index 000000000..d46846e0c --- /dev/null +++ b/sdk/python/core/tests/test_entity_diff.py @@ -0,0 +1,211 @@ +""" +Test cases for YDK entity_diff. + + +""" + +from __future__ import print_function + +import unittest + +from ydk.types import entity_to_dict, entity_diff + +from ydk.models.ydktest import ydktest_sanity as ysanity + +# from test_utils import enable_logging, print_entity, print_data_node + + +def check_empty_str_value(v): + if isinstance(v, str) and v.__len__() == 0: + v = 'exists' + return v + + +def print_dictionary(legend, ent_dict, key_width=70): + print("\n------> DICTIONARY%s:" % legend) + for n, v in sorted(ent_dict.items()): + print("%s: %s" % (n.rjust(key_width), check_empty_str_value(v))) + + +def print_diffs(diff, key_width=70): + print("\n------> DIFFS:") + for key in diff: + value = diff[key] + print("%s: %s vs %s" % (key.rjust(key_width), check_empty_str_value(value[0]), check_empty_str_value(value[1]))) + + +class EntityDiffTest(unittest.TestCase): + + def test_entity_diff_two_key(self): + runner = ysanity.Runner() + l_1 = ysanity.Runner.TwoKeyList() + l_2 = ysanity.Runner.TwoKeyList() + l_1.first, l_2.first = 'f1', 'f2' + l_1.second, l_2.second = 11, 22 + l_1.property, l_2.property = '82', '83' + runner.two_key_list.extend([l_1, l_2]) + + ent_dict = entity_to_dict(runner) + self.assertEqual(len(ent_dict), 4) + print_dictionary('-LEFT', ent_dict) + + runner2 = ysanity.Runner() + l_1, l_2 = ysanity.Runner.TwoKeyList(), ysanity.Runner.TwoKeyList() + l_1.first, l_2.first = 'f1', 'f2' + l_1.second, l_2.second = 11, 22 + l_1.property, l_2.property = '82', '83' + runner2.two_key_list.extend([l_1, l_2]) + + diff = entity_diff(runner, runner2) + self.assertEqual(len(diff), 0) + + l_1.property = '83' + print_dictionary('-RIGHT', entity_to_dict(runner2)) + diff = entity_diff(runner, runner2) + self.assertEqual(len(diff), 1) + print_diffs(diff) + + def test_entity_diff_two_key_not_equal(self): + runner = ysanity.Runner() + l_1, l_2 = ysanity.Runner.TwoKeyList(), ysanity.Runner.TwoKeyList() + l_1.first, l_2.first = 'f1', 'f2' + l_1.second, l_2.second = 11, 22 + l_1.property, l_2.property = '82', '83' + runner.two_key_list.extend([l_1, l_2]) + + ent_dict = entity_to_dict(runner) + self.assertEqual(len(ent_dict), 4) + print_dictionary('-LEFT', ent_dict) + + runner3 = ysanity.Runner() + l_1, l_2 = ysanity.Runner.TwoKeyList(), ysanity.Runner.TwoKeyList() + l_1.first, l_2.first = 'f1', 'f3' + l_1.second, l_2.second = 11, 22 + l_1.property, l_2.property = '82', '84' + runner3.two_key_list.extend([l_1, l_2]) + + ent_dict = entity_to_dict(runner3) + self.assertEqual(len(ent_dict), 4) + print_dictionary('-RIGHT', ent_dict) + + diff = entity_diff(runner, runner3) + self.assertEqual(len(diff), 2) + print_diffs(diff) + + def test_entity_to_dict_aug_onelist(self): + runner = ysanity.Runner() + e_1 = ysanity.Runner.OneList.OneAugList.Ldata() + e_2 = ysanity.Runner.OneList.OneAugList.Ldata() + e_1.number = 1 + e_1.name = "e_1.name" + e_2.number = 2 + e_2.name = "e_2.name" + runner.one_list.one_aug_list.ldata.extend([e_1, e_2]) + runner.one_list.one_aug_list.enabled = True + + ent_dict = entity_to_dict(runner) + self.assertEqual(len(ent_dict), 5) + print_dictionary('', ent_dict, 60) + + def test_entity_to_dict_enum_leaflist(self): + runner = ysanity.Runner() + runner.ytypes.built_in_t.enum_llist.append(ysanity.YdkEnumTestEnum.local) + runner.ytypes.built_in_t.enum_llist.append(ysanity.YdkEnumTestEnum.remote) + + ent_dict = entity_to_dict(runner) + self.assertEqual(len(ent_dict), 2) + print_dictionary('', ent_dict, 50) + + def test_entity_diff_number_leaf(self): + runner1 = ysanity.Runner() + runner1.ytypes.built_in_t.number8 = 23 + + ent_dict = entity_to_dict(runner1) + self.assertEqual(len(ent_dict), 1) + print_dictionary('-LEFT', ent_dict, 50) + + runner2 = ysanity.Runner() + + ent_dict = entity_to_dict(runner2) + self.assertEqual(len(ent_dict), 0) + print_dictionary('-RIGHT', ent_dict, 50) + + diff = entity_diff(runner1, runner2) + self.assertEqual(len(diff), 1) + print_diffs(diff, 50) + + def test_entity_to_dict_presence(self): + runner = ysanity.Runner() + runner.runner_2 = ysanity.Runner.Runner2() + runner.runner_2.some_leaf = 'some-leaf' + + ent_dict = entity_to_dict(runner) + self.assertEqual(len(ent_dict), 2) + print_dictionary('-LEFT', ent_dict, 40) + + runner_ = ysanity.Runner() + ent_dict = entity_to_dict(runner_) + self.assertEqual(len(ent_dict), 0) + print_dictionary('-RIGHT', ent_dict, 40) + + diff = entity_diff(runner, runner_) + self.assertEqual(len(diff), 1) + print_diffs(diff, 40) + + def test_entity_diff_two_level_list(self): + r_1 = ysanity.Runner() + e_1, e_2 = ysanity.Runner.TwoList.Ldata(), ysanity.Runner.TwoList.Ldata() + e_11, e_12 = ysanity.Runner.TwoList.Ldata.Subl1(), ysanity.Runner.TwoList.Ldata.Subl1() + e_1.number = 21 + e_1.name = 'runner:twolist:ldata['+str(e_1.number)+']:name' + e_11.number = 211 + e_11.name = 'runner:twolist:ldata['+str(e_1.number)+']:subl1['+str(e_11.number)+']:name' + e_12.number = 212 + e_12.name = 'runner:twolist:ldata['+str(e_1.number)+']:subl1['+str(e_12.number)+']:name' + e_1.subl1.append(e_11) + e_1.subl1.append(e_12) + e_21, e_22 = ysanity.Runner.TwoList.Ldata.Subl1(), ysanity.Runner.TwoList.Ldata.Subl1() + e_2.number = 22 + e_2.name = 'runner:twolist:ldata['+str(e_2.number)+']:name' + e_21.number = 221 + e_21.name = 'runner:twolist:ldata['+str(e_2.number)+']:subl1['+str(e_21.number)+']:name' + e_22.number = 222 + e_22.name = 'runner:twolist:ldata['+str(e_2.number)+']:subl1['+str(e_22.number)+']:name' + e_2.subl1.append(e_21) + e_2.subl1.append(e_22) + r_1.two_list.ldata.append(e_1) + r_1.two_list.ldata.append(e_2) + + ent_dict = entity_to_dict(r_1) + self.assertEqual(len(ent_dict), 12) + print_dictionary('-LEFT', ent_dict, 80) + + r_2 = ysanity.Runner() + e_1, e_2 = ysanity.Runner.TwoList.Ldata(), ysanity.Runner.TwoList.Ldata() + e_11, e_12 = ysanity.Runner.TwoList.Ldata.Subl1(), ysanity.Runner.TwoList.Ldata.Subl1() + e_1.number = 21 + e_1.name = 'runner:twolist:ldata['+str(e_1.number)+']:name' + e_11.number = 211 + e_11.name = 'runner:twolist:ldata['+str(e_1.number)+']:subl1['+str(e_11.number)+']:name' + e_12.number = 212 + e_12.name = 'runner:twolist:ldata['+str(e_1.number)+']:subl1['+str(e_12.number)+']:name' + e_1.subl1.append(e_11) + e_1.subl1.append(e_12) + r_2.two_list.ldata.append(e_1) + + ent_dict = entity_to_dict(r_2) + self.assertEqual(len(ent_dict), 6) + print_dictionary('-RIGHT', ent_dict, 80) + + diff = entity_diff(r_1, r_2) + self.assertEqual(len(diff), 1) + print_diffs(diff, 50) + + +if __name__ == '__main__': + suite = unittest.TestSuite() + testloader = unittest.TestLoader() + testnames = testloader.getTestCaseNames(EntityDiffTest) + for name in testnames: + suite.addTest(EntityDiffTest(name)) + ret = not unittest.TextTestRunner(verbosity=2).run(suite).wasSuccessful() diff --git a/sdk/python/core/ydk/_core/_dm_meta_info.py b/sdk/python/core/ydk/_core/_dm_meta_info.py index 3bdc9a94b..0580887cd 100644 --- a/sdk/python/core/ydk/_core/_dm_meta_info.py +++ b/sdk/python/core/ydk/_core/_dm_meta_info.py @@ -130,7 +130,7 @@ def union_list(self): _list = [] if self._mtype == REFERENCE_UNION: for union_member in self._members: - if (union_member._ptype == 'str'): + if union_member._ptype == 'str': pattern = union_member._pattern else: pattern = union_member._range diff --git a/sdk/python/core/ydk/providers/_encoder.py b/sdk/python/core/ydk/providers/_encoder.py index 346ff0cb7..b2f76383e 100644 --- a/sdk/python/core/ydk/providers/_encoder.py +++ b/sdk/python/core/ydk/providers/_encoder.py @@ -60,7 +60,7 @@ def encode_to_xml(self, entity, root, optype, is_filter=False, validate=True): elem.set('{' + xc + '}operation', get_yfilter_tag(yfilter)) parent_ns = None current_parent = root - while current_parent != None and parent_ns is None: + while current_parent is not None and parent_ns is None: parent_ns = current_parent.get('xmlns') current_parent = current_parent.getparent() diff --git a/sdk/python/core/ydk/types.py b/sdk/python/core/ydk/types.py index 41e177c9f..3b3a69d2a 100644 --- a/sdk/python/core/ydk/types.py +++ b/sdk/python/core/ydk/types.py @@ -22,27 +22,29 @@ from __future__ import absolute_import from decimal import Decimal, getcontext -from .errors import YPYModelError +from .errors import YPYModelError, YPYError +from ._core._dm_meta_info import ANYXML_CLASS, REFERENCE_CLASS, REFERENCE_LIST, REFERENCE_LEAFLIST +from ._core._dm_meta_info import REFERENCE_IDENTITY_CLASS, REFERENCE_UNION, ATTRIBUTE class DELETE(object): - '''Marker class used to mark nodes that are to be deleted + """Marker class used to mark nodes that are to be deleted Assign DELETE object to a mark a leaf for deletion. - A CRUD update operation will delete the leaf from the device it is on.''' + A CRUD update operation will delete the leaf from the device it is on.""" pass class REMOVE(object): - '''Marker class used to mark nodes that are to be removed + """Marker class used to mark nodes that are to be removed Assign REMOVE object to a mark a leaf for deletion. - A CRUD update operation will delete the leaf from the device it is on.''' + A CRUD update operation will delete the leaf from the device it is on.""" pass class MERGE(object): - '''Marker MERGE used to mark nodes that are to be merged + """Marker MERGE used to mark nodes that are to be merged Assign DELETE object to a mark a leaf for deletion. - A CRUD update operation will delete the leaf from the device it is on.''' + A CRUD update operation will delete the leaf from the device it is on.""" def __init__(self, value=None): self._value = value @@ -55,9 +57,9 @@ def set(self, value): class REPLACE(object): - '''Marker class used to mark nodes that are to be replaced + """Marker class used to mark nodes that are to be replaced Assign REPLACE object to a mark a leaf for deletion. - A CRUD update operation will delete the leaf from the device it is on.''' + A CRUD update operation will delete the leaf from the device it is on.""" def __init__(self, value=None): self._value = value @@ -68,10 +70,11 @@ def value(self): def set(self, value): self._value = value + class CREATE(object): - '''Marker class used to mark nodes that are to be created + """Marker class used to mark nodes that are to be created Assign CREATE object to a mark a leaf for deletion. - A CRUD update operation will delete the leaf from the device it is on.''' + A CRUD update operation will delete the leaf from the device it is on.""" def __init__(self, value=None): self._value = value @@ -84,9 +87,10 @@ def set(self, value): class READ(object): - '''Marker class used to mark nodes that are to be read ''' + """Marker class used to mark nodes that are to be read """ pass + class Empty(object): """ .. _ydk_models_types_Empty: @@ -283,12 +287,13 @@ def __getslice__(self, i, j): return ret def append(self, item): - super(YList, self).append(item) - item.parent = self.parent + super(YList, self).append(item) + item.parent = self.parent def extend(self, items): - for item in items: - self.append(item) + for item in items: + self.append(item) + class YListItem(object): def __init__(self, item, parent, name): @@ -411,3 +416,102 @@ def count(self, item): if i.item == item: cnt += 1 return cnt + + +def get_absolute_path(entity): + path = entity._common_path + segments = path.split("/") + module = segments[1].split(':', 1)[0] + for i in range(2, len(segments)): + del_str = module + ':' + if del_str in segments[i]: + segments[i] = segments[i].replace(del_str, '') + else: + if ':' in segments[i]: + module = segments[i].split(':', 1)[0] + path = '/'.join(segments) + return path + + +def get_name_leaf_data(entity): + leaf_name_data = {} + for member in entity._meta_info().meta_info_class_members: + value = getattr(entity, member.presentation_name) + if value is None or isinstance(value, list) and value == []: + continue + + if member.mtype in [ATTRIBUTE, REFERENCE_IDENTITY_CLASS]: + leaf_name_data[member.name] = value + elif member.mtype == REFERENCE_LEAFLIST and isinstance(value, list): + for child in value: + key = "%s[.='%s']" % (member.name, child) + leaf_name_data[key] = '' + return leaf_name_data + + +def get_children(entity): + children = {} + for member in entity._meta_info().meta_info_class_members: + value = getattr(entity, member.presentation_name) + if value is None or isinstance(value, list) and value == []: + continue + + if member.mtype == REFERENCE_CLASS: + abs_path = get_absolute_path(value) + children[abs_path] = value + elif member.mtype == REFERENCE_LIST: + for child in value: + abs_path = get_absolute_path(child) + children[abs_path] = child + return children + + +def entity_to_dict(entity): + edict = {} + abs_path = get_absolute_path(entity) + ent_meta = entity._meta_info() + if (hasattr(ent_meta, 'is_presence') and ent_meta.is_presence) or \ + abs_path.endswith(']'): + edict[abs_path] = '' + leaf_name_data = get_name_leaf_data(entity) + for leaf_name, leaf_value in leaf_name_data.items(): + if leaf_name not in entity.ylist_key_names: + edict["%s/%s" % (abs_path, leaf_name)] = leaf_value + for name, child in get_children(entity).items(): + child_dict = entity_to_dict(child) + for n, v in child_dict.items(): + edict[n] = v + return edict + + +def entity_diff(ent1, ent2): + if ent1 is None or ent2 is None or type(ent1) != type(ent2): + raise YPYError("entity_diff: Incompatible arguments provided.") + + diffs = {} + ent1_dict = entity_to_dict(ent1) + ent2_dict = entity_to_dict(ent2) + ent1_keys = sorted(ent1_dict.keys()) + ent2_keys = sorted(ent2_dict.keys()) + ent1_skip_keys = [] + for key in ent1_keys: + if key in ent1_skip_keys: + continue + if key in ent2_keys: + if ent1_dict[key] != ent2_dict[key]: + diffs[key] = (ent1_dict[key], ent2_dict[key]) + ent2_keys.remove(key) + else: + diffs[key] = (ent1_dict[key], None) + for dup_key in ent1_keys: + if dup_key.startswith(key): + ent1_skip_keys.append(dup_key) + ent2_skip_keys = [] + for key in ent2_keys: + if key in ent2_skip_keys: + continue + diffs[key] = (None, ent2_dict[key]) + for dup_key in ent2_keys: + if dup_key.startswith(key): + ent2_skip_keys.append(dup_key) + return diffs diff --git a/test/tests.sh b/test/tests.sh index 8a19f5e26..8ee049026 100755 --- a/test/tests.sh +++ b/test/tests.sh @@ -67,6 +67,7 @@ function init_env { print_msg "init_env" YDKGEN_HOME=$(pwd) + print_msg "YDKGEN_HOME is set to ${YDKGEN_HOME}" PY_GENERATE="$1" PY_TEST="$2" @@ -180,6 +181,7 @@ function py_sanity_ydktest_test { function py_sanity_ydktest_test_ncclient { print_msg "py_sanity_ydktest_test_ncclient" run_test sdk/python/core/tests/test_sanity_types.py + run_test sdk/python/core/tests/test_entity_diff.py run_test sdk/python/core/tests/test_sanity_errors.py run_test sdk/python/core/tests/test_sanity_filters.py run_test sdk/python/core/tests/test_sanity_levels.py diff --git a/yang/ydktest/ydktest-sanity@2015-11-17.yang b/yang/ydktest/ydktest-sanity@2015-11-17.yang index 0b4711cdb..e371c4474 100644 --- a/yang/ydktest/ydktest-sanity@2015-11-17.yang +++ b/yang/ydktest/ydktest-sanity@2015-11-17.yang @@ -579,7 +579,7 @@ module ydktest-sanity { } } - grouping three-level-w-list-at-two { + grouping three-level-w-list-at-two { container inbtw-list { description "config for one_level list data"; list ldata { @@ -645,6 +645,21 @@ module ydktest-sanity { } } + grouping two-key-list { + list two-key-list { + key "first second"; + leaf first { + type string; + } + leaf second { + type uint32; + } + leaf property { + type string; + } + } + } + container runner { //container at 1,2,3 nested level @@ -666,6 +681,8 @@ module ydktest-sanity { uses not-supported; + uses two-key-list; + container runner-2 { presence "Runner-2 is presence controlled";