diff --git a/sdk/cpp/core/src/entity_util.cpp b/sdk/cpp/core/src/entity_util.cpp index 10b053b47..cd28601a2 100644 --- a/sdk/cpp/core/src/entity_util.cpp +++ b/sdk/cpp/core/src/entity_util.cpp @@ -22,7 +22,6 @@ ////////////////////////////////////////////////////////////////// #include -#include #include "entity_util.hpp" #include "errors.hpp" @@ -129,4 +128,127 @@ const EntityPath get_entity_path(const Entity & entity, Entity* ancestor) return get_entity_path(entity, path_buffer.str()); } +std::string absolute_path(Entity & entity) +{ + string path = entity.get_segment_path(); + if (!entity.is_top_level_class && entity.parent) + { + path = absolute_path(*entity.parent) + "/" + path; + } + return path; +} + +std::map entity_to_dict(Entity & entity) +{ + std::map edict{}; + auto abs_path = absolute_path(entity); + if (entity.is_presence_container || abs_path.rfind("]") == abs_path.length()-1) + { + edict[abs_path] = ""; + } + auto name_leaf_data_vector = entity.get_name_leaf_data(); + for (auto name_leaf_data : name_leaf_data_vector) + { + auto leaf_name = name_leaf_data.first; + auto leaf_value = name_leaf_data.second.value; + std::ostringstream key_buffer; + key_buffer << "[" << leaf_name << "="; + if (abs_path.find(key_buffer.str()) == string::npos) + { + std::string path = abs_path + "/" + leaf_name; + edict[path] = leaf_value; + } + } + for (auto const & entry : entity.get_children()) + { + auto child = entry.second; + auto child_dict = entity_to_dict(*child); + for (auto const & e : child_dict) + { + edict[e.first] = e.second; + } + } + return edict; +} + +static bool key_in_vector(string & k, vector v) +{ + for (auto e : v) + { + if (e == k) { + return true; + } + } + return false; +} + +static void remove_key_from_vector(string & k, vector & v) +{ + for (vector::iterator it=v.begin(); it!=v.end(); it++) + { + if (k == *it) + { + v.erase(it); + break; + } + } +} + +std::map> entity_diff(Entity & ent1, Entity & ent2) +{ + if (typeid(ent1) != typeid(ent2)) + { + throw(YInvalidArgumentError{"entity_diff: Incompatible arguments provided."}); + } + std::map> diffs{}; + auto ent1_dict = entity_to_dict(ent1); + auto ent2_dict = entity_to_dict(ent2); + vector ent1_keys; + for (auto entry : ent1_dict) ent1_keys.push_back(entry.first); + vector ent2_keys; + for (auto entry : ent2_dict) ent2_keys.push_back(entry.first); + vector ent1_skip_keys; + for (auto key : ent1_keys) + { + if (key_in_vector(key, ent1_skip_keys)) + continue; + if (key_in_vector(key, ent2_keys)) + { + if (ent1_dict[key] != ent2_dict[key]) + { + diffs[key] = make_pair(ent1_dict[key], ent2_dict[key]); + } + remove_key_from_vector(key, ent2_keys); + } + else + { + diffs[key] = make_pair(ent1_dict[key], "None"); + for (auto dup_key : ent1_keys) + { + if (dup_key.find(key) == 0) + { + ent1_skip_keys.push_back(dup_key); + } + } + } + } + vector ent2_skip_keys; + for (auto key : ent2_keys) + { + if (key_in_vector(key, ent2_skip_keys)) + continue; + diffs[key] = make_pair("None", ent2_dict[key]); + for (auto dup_key : ent2_keys) + { + if (dup_key.find(key) == 0) + { + ent2_skip_keys.push_back(dup_key); + } + } + } + return diffs; +} + + + } diff --git a/sdk/cpp/core/src/entity_util.hpp b/sdk/cpp/core/src/entity_util.hpp index 1f66cb500..ee74394da 100644 --- a/sdk/cpp/core/src/entity_util.hpp +++ b/sdk/cpp/core/src/entity_util.hpp @@ -35,6 +35,12 @@ bool is_set(const YFilter & yfilter); const EntityPath get_entity_path(const Entity & entity, Entity* ancestor); +std::string absolute_path(Entity & entity); + +std::map entity_to_dict(Entity & entity); + +std::map> entity_diff(Entity & ent1, Entity & ent2); + } #define ADD_KEY_TOKEN(attr, attr_name) {\ diff --git a/sdk/cpp/tests/CMakeLists.txt b/sdk/cpp/tests/CMakeLists.txt index f6807a92a..f03f5c121 100644 --- a/sdk/cpp/tests/CMakeLists.txt +++ b/sdk/cpp/tests/CMakeLists.txt @@ -10,6 +10,7 @@ set(bundle_tests_src main.cpp test_c_api.cpp test_crud.cpp + test_entity_diff.cpp test_errors.cpp test_executor_service.cpp test_netconf_operations.cpp diff --git a/sdk/cpp/tests/test_entity_diff.cpp b/sdk/cpp/tests/test_entity_diff.cpp new file mode 100644 index 000000000..9949fb8f9 --- /dev/null +++ b/sdk/cpp/tests/test_entity_diff.cpp @@ -0,0 +1,239 @@ +/// YANG Development Kit +// Copyright 2019 Cisco Systems. All rights reserved +// +//////////////////////////////////////////////////////////////// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +////////////////////////////////////////////////////////////////// + +#include +#include + +#include + +#include + +#include "config.hpp" +#include "catch.hpp" +#include "test_utils.hpp" + +using namespace std; +using namespace ydk; +using namespace ydktest; + +string check_empty_str_value(string & v) +{ + if (v.empty()) + v = "exists"; + return v; +} + +void print_dictionary(const string & legend, map & ent_dict) +{ + cout << "\n------> DICTIONARY" << legend << endl; + for (map::iterator it = ent_dict.begin(); it != ent_dict.end(); it++) + { + cout << it->first << ": " << check_empty_str_value(it->second) << endl; + } +} + +void print_diffs(map> & diff) +{ + cout << "\n------> DIFFS:" << endl; + for (auto const & entry : diff) + { + auto value_pair = entry.second; + cout << entry.first << ": " << check_empty_str_value(value_pair.first) << " vs " + << check_empty_str_value(value_pair.second) << endl; + } +} + +TEST_CASE( "test_entity_diff_two_key" ) +{ + auto runner1 = ydktest_sanity::Runner{}; + auto l_1 = make_shared(); + l_1->first = "f1"; + l_1->second = 11; + l_1->property = "82"; + auto l_2 = make_shared(); + l_2->first = "f2"; + l_2->second = 22; + l_2->property = "83"; + runner1.two_key_list.extend({l_1, l_2}); + + auto ent_dict1 = entity_to_dict(runner1); + REQUIRE(ent_dict1.size() == 4); + print_dictionary("-LEFT", ent_dict1); + + auto runner2 = ydktest_sanity::Runner{}; + l_1 = make_shared(); + l_1->first = "f1"; + l_1->second = 11; + l_1->property = "82"; + l_2 = make_shared(); + l_2->first = "f2"; + l_2->second = 22; + l_2->property = "83"; + runner2.two_key_list.extend({l_1, l_2}); + + auto diff = entity_diff(runner1, runner2); + REQUIRE(diff.size() == 0); + + l_1->property = "83"; + auto ent_dict2 = entity_to_dict(runner2); + print_dictionary("-RIGHT", ent_dict2); + diff = entity_diff(runner1, runner2); + REQUIRE(diff.size() == 1); + print_diffs(diff); +} + +TEST_CASE( "test_entity_diff_two_key_not_equal" ) +{ + auto runner1 = ydktest_sanity::Runner{}; + auto l_1 = make_shared(); + l_1->first = "f1"; + l_1->second = 11; + l_1->property = "82"; + auto l_2 = make_shared(); + l_2->first = "f2"; + l_2->second = 22; + l_2->property = "83"; + runner1.two_key_list.extend({l_1, l_2}); + + auto ent_dict1 = entity_to_dict(runner1); + REQUIRE(ent_dict1.size() == 4); + print_dictionary("-LEFT", ent_dict1); + + auto runner2 = ydktest_sanity::Runner{}; + l_1 = make_shared(); + l_1->first = "f1"; + l_1->second = 11; + l_1->property = "82"; + l_2 = make_shared(); + l_2->first = "f3"; + l_2->second = 22; + l_2->property = "83"; + runner2.two_key_list.extend({l_1, l_2}); + + auto ent_dict2 = entity_to_dict(runner2); + print_dictionary("-RIGHT", ent_dict2); + + auto diff = entity_diff(runner1, runner2); + REQUIRE(diff.size() == 2); + print_diffs(diff); +} + +TEST_CASE( "test_entity_to_dict_aug_onelist" ) +{ + auto runner = ydktest_sanity::Runner{}; + auto e_1 = make_shared(); + auto e_2 = make_shared(); + 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; + + auto ent_dict = entity_to_dict(runner); + REQUIRE(ent_dict.size() == 5); + print_dictionary("", ent_dict); +} + +TEST_CASE( "test_entity_to_dict_enum_leaflist" ) +{ + auto runner = ydktest_sanity::Runner{}; + runner.ytypes->built_in_t->enum_llist.append(ydktest_sanity::YdkEnumTest::local); + runner.ytypes->built_in_t->enum_llist.append(ydktest_sanity::YdkEnumTest::remote); + + auto ent_dict = entity_to_dict(runner); + REQUIRE(ent_dict.size() == 2); + print_dictionary("", ent_dict); +} + +TEST_CASE( "test_entity_diff_presence" ) +{ + auto runner = ydktest_sanity::Runner(); + runner.runner_2 = make_shared(); + runner.runner_2->some_leaf = "some-leaf"; + + auto ent_dict = entity_to_dict(runner); + REQUIRE(ent_dict.size() == 2); + print_dictionary("-LEFT", ent_dict); + + auto empty_runner = ydktest_sanity::Runner(); + ent_dict = entity_to_dict(empty_runner); + REQUIRE(ent_dict.size() == 0); + print_dictionary("-RIGHT", ent_dict); + + auto diff = entity_diff(runner, empty_runner); + REQUIRE(diff.size() == 1); + print_diffs(diff); +} + +TEST_CASE( "test_entity_diff_two_list_pos" ) +{ + auto r_1 = ydktest_sanity::Runner(); + auto e_1 = make_shared(); + auto e_2 = make_shared(); + auto e_11 = make_shared(); + auto e_12 = make_shared(); + e_1->number = 21; + e_1->name = "runner:twolist:ldata[21]:name"; + e_11->number = 211; + e_11->name = "runner:twolist:ldata[21]:subl1[211]:name"; + e_12->number = 212; + e_12->name = "runner:twolist:ldata[21]:subl1[212]:name"; + e_1->subl1.extend({e_11, e_12}); + auto e_21 = make_shared(); + auto e_22 = make_shared(); + e_2->number = 22; + e_2->name = "runner:twolist:ldata[22]:name"; + e_21->number = 221; + e_21->name = "runner:twolist:ldata[22]:subl1[221]:name"; + e_22->number = 222; + e_22->name = "runner:twolist:ldata[22]:subl1[222]:name"; + e_2->subl1.extend({e_21, e_22}); + r_1.two_list->ldata.extend({e_1, e_2}); + + auto ent_dict = entity_to_dict(r_1); + REQUIRE(ent_dict.size() == 12); + print_dictionary("-LEFT", ent_dict); + + auto r_2 = ydktest_sanity::Runner(); + e_1 = make_shared(); + e_2 = make_shared(); + e_11 = make_shared(); + e_12 = make_shared(); + e_1->number = 21; + e_1->name = "runner:twolist:ldata[21]:name"; + e_11->number = 211; + e_11->name = "runner:twolist:ldata[21]:subl1[211]:name"; + e_12->number = 212; + e_12->name = "runner:twolist:ldata[21]:subl1[212]:name"; + e_1->subl1.extend({e_11, e_12}); + r_2.two_list->ldata.append(e_1); + + ent_dict = entity_to_dict(r_2); + REQUIRE(ent_dict.size() == 6); + print_dictionary("-RIGHT", ent_dict); + + auto diff = entity_diff(r_1, r_2); + REQUIRE(diff.size() == 1); + print_diffs(diff); +} diff --git a/sdk/python/core/tests/test_sanity_filter_read.py b/sdk/python/core/tests/test_sanity_filter_read.py index a6c9d5447..1eb0e9c98 100644 --- a/sdk/python/core/tests/test_sanity_filter_read.py +++ b/sdk/python/core/tests/test_sanity_filter_read.py @@ -212,7 +212,9 @@ def test_read_oc_pattern(self): obj_A.b.b = obj_A.a # 'world' --> YServiceProviderError: illegal reference self.crud.create(self.ncc, obj_A) - obj_A_read = self.crud.read(self.ncc, oc_pattern.OcA()) + filter = oc_pattern.OcA() + filter.b.b = 'hello' + obj_A_read = self.crud.read(self.ncc, filter) self.assertIsNotNone(obj_A_read) obj_A = oc_pattern.OcA() diff --git a/sdk/python/core/ydk/services/crud_service.py b/sdk/python/core/ydk/services/crud_service.py index ef3c79fa6..aafd2e25a 100644 --- a/sdk/python/core/ydk/services/crud_service.py +++ b/sdk/python/core/ydk/services/crud_service.py @@ -20,6 +20,7 @@ from ydk.types import EntityCollection, Config from ydk.entity_utils import _read_entities, _get_top_level_entity, _get_child_entity_from_top + class CRUDService(_CrudService): """ Python wrapper for CrudService diff --git a/sdk/python/core/ydk/types/py_types.py b/sdk/python/core/ydk/types/py_types.py index a1bad83bb..05b4f5c24 100644 --- a/sdk/python/core/ydk/types/py_types.py +++ b/sdk/python/core/ydk/types/py_types.py @@ -329,11 +329,11 @@ def path(self): return self.get_segment_path() def get_absolute_path(self): - path = self.get_segment_path() - if not self.is_top_level_class: - path = self.parent.get_absolute_path() + '/' + path - # elif '[' in path: - # path = path.split('[')[0] + path = self._absolute_path() + if len(path) == 0 and self.is_top_level_class: + path = self.get_segment_path() + if '[' in path: + path = path.split('[')[0] return path def _get_child_by_seg_name(self, segs): @@ -404,9 +404,16 @@ def __str__(self): return "{}.{}".format(self.__class__.__module__, self.__class__.__name__) +def absolute_path(entity): + path = entity.get_segment_path() + if not entity.is_top_level_class and entity.parent is not None: + path = absolute_path(entity.parent) + '/' + path + return path + + def entity_to_dict(entity): edict = {} - abs_path = entity.get_absolute_path() + abs_path = absolute_path(entity) if (hasattr(entity, 'is_presence_container') and entity.is_presence_container) or \ abs_path.endswith(']'): edict[abs_path] = ''