]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[65-libyang-testutils] Filled files, ready for review
authorFrancis Dupont <fdupont@isc.org>
Thu, 13 Sep 2018 14:02:28 +0000 (16:02 +0200)
committerFrancis Dupont <fdupont@isc.org>
Wed, 24 Oct 2018 13:50:51 +0000 (15:50 +0200)
configure.ac
src/lib/yang/Makefile.am
src/lib/yang/tests/Makefile.am
src/lib/yang/tests/translator_utils_unittests.cc [new file with mode: 0644]
src/lib/yang/tests/yang_configs.h [new file with mode: 0644]
src/lib/yang/testutils/Makefile.am [new file with mode: 0644]
src/lib/yang/testutils/translator_test.cc [new file with mode: 0644]
src/lib/yang/testutils/translator_test.h [new file with mode: 0644]

index 46e2fc941cd8b09c2295e05d7b0fa287aa7be0c9..ba86b3e0ade097c65c211a2976863eb6fbc6297d 100644 (file)
@@ -1621,6 +1621,7 @@ AC_CONFIG_FILES([Makefile
                  src/lib/yang/Makefile
                  src/lib/yang/pretests/Makefile
                  src/lib/yang/tests/Makefile
+                 src/lib/yang/testutils/Makefile
                  src/share/Makefile
                  src/share/database/Makefile
                  src/share/database/scripts/Makefile
index 80ba2fc3773321a2859e3a92856da8271fffb518..27a8fe0adfbc72a2203d1a500b1cd9bb8998722e 100644 (file)
@@ -1,4 +1,4 @@
-SUBDIRS = . pretests tests
+SUBDIRS = . testutils pretests tests
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES) $(SYSREPO_CPPFLAGS)
index 9297c9c5977b11fc8d799789e554675ac66977b2..4a8f091ece4147e4839560d496e126a53954989c 100644 (file)
@@ -17,7 +17,8 @@ EXTRA_DIST = keatest-module.yang
 TESTS =
 if HAVE_GTEST
 TESTS += run_unittests
-run_unittests_SOURCES  = adaptor_unittests.cc
+run_unittests_SOURCES  = yang_configs.h
+run_unittests_SOURCES += adaptor_unittests.cc
 run_unittests_SOURCES += adaptor_option_unittests.cc
 run_unittests_SOURCES += adaptor_pool_unittests.cc
 run_unittests_SOURCES += adaptor_host_unittests.cc
@@ -36,11 +37,13 @@ run_unittests_SOURCES += translator_pd_pool_unittests.cc
 run_unittests_SOURCES += translator_host_unittests.cc
 run_unittests_SOURCES += translator_subnet_unittests.cc
 run_unittests_SOURCES += translator_shared_network_unittests.cc
+run_unittests_SOURCES += translator_utils_unittests.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 
-run_unittests_LDADD  = $(top_builddir)/src/lib/yang/libkea-yang.la
+run_unittests_LDADD =  $(top_builddir)/src/lib/yang/testutils/libyangtest.la
+run_unittests_LDADD += $(top_builddir)/src/lib/yang/libkea-yang.la
 run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
 run_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
 run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
diff --git a/src/lib/yang/tests/translator_utils_unittests.cc b/src/lib/yang/tests/translator_utils_unittests.cc
new file mode 100644 (file)
index 0000000..4019515
--- /dev/null
@@ -0,0 +1,206 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <yang/tests/yang_configs.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::yang;
+using namespace isc::yang::test;
+
+namespace {
+
+// Test sr_type_t print.
+TEST(YangReprTest, type) {
+    ostringstream os;
+
+    // Verify that string is "string" (vs a number).
+    sr_type_t t = SR_STRING_T;
+    os << t;
+    EXPECT_EQ("string", os.str());
+    os.str("");
+
+    // Compiler does not let to create an invalid value...
+}
+
+// Test YangReprItem basic stuff.
+TEST(YangReprTest, item) {
+    // An item.
+    YRItem item1("/foo", "bar", SR_STRING_T, true);
+    EXPECT_EQ("/foo", item1.xpath_);
+    EXPECT_EQ("bar", item1.value_);
+    EXPECT_EQ(SR_STRING_T, item1.type_);
+    EXPECT_TRUE(item1.settable_);
+
+    // Another one.
+    YRItem item2("/foo", "bar", SR_STRING_T, false);
+    EXPECT_EQ("/foo", item2.xpath_);
+    EXPECT_EQ("bar", item2.value_);
+    EXPECT_EQ(SR_STRING_T, item2.type_);
+    EXPECT_FALSE(item2.settable_);
+
+    // Equality.
+    EXPECT_TRUE(item1 == item2);
+    EXPECT_TRUE(item2 == item1);
+    EXPECT_FALSE(item1 != item2);
+    EXPECT_FALSE(item2 != item1);
+    EXPECT_EQ(item1, item2);
+    EXPECT_EQ(item2, item1);
+}
+
+// Test get with example module.
+TEST(YangReprTest, getExample) {
+    // Get a translator object to play with.
+    S_Connection conn(new Connection("utils unittests"));
+    S_Session sess(new Session(conn, SR_DS_CANDIDATE));
+
+    // Create a list.
+    string xpath = "/example-module:container/list";
+    S_Val s_val;
+    EXPECT_NO_THROW(sess->set_item(xpath.c_str(), s_val));
+
+    // Get it.
+    YangRepr repr(exampleModel);
+    YRTree tree;
+    EXPECT_NO_THROW(tree = repr.get(sess));
+
+    // Verify.
+    EXPECT_TRUE(repr.verify(exampleTree, sess, cerr));
+}
+
+// Test get with test module.
+TEST(YangReprTest, getTest) {
+    // Get a translator object to play with.
+    S_Connection conn(new Connection("utils unittests"));
+    S_Session sess(new Session(conn, SR_DS_CANDIDATE));
+
+    // Fill the test module.
+    string xpath;
+    S_Val s_val;
+
+    xpath = "/test-module:main/string";
+    s_val.reset(new Val("str", SR_STRING_T));
+    EXPECT_NO_THROW(sess->set_item(xpath.c_str(), s_val));
+
+    xpath = "/test-module:main/boolean";
+    s_val.reset(new Val(true, SR_BOOL_T));
+    EXPECT_NO_THROW(sess->set_item(xpath.c_str(), s_val));
+
+    xpath = "/test-module:main/ui8";
+    uint8_t u8(8);
+    s_val.reset(new Val(u8, SR_UINT8_T));
+    EXPECT_NO_THROW(sess->set_item(xpath.c_str(), s_val));
+
+    xpath = "/test-module:main/ui16";
+    uint16_t u16(16);
+    s_val.reset(new Val(u16, SR_UINT16_T));
+    EXPECT_NO_THROW(sess->set_item(xpath.c_str(), s_val));
+
+    xpath = "/test-module:main/ui32";
+    uint32_t u32(32);
+    s_val.reset(new Val(u32, SR_UINT32_T));
+    EXPECT_NO_THROW(sess->set_item(xpath.c_str(), s_val));
+
+    xpath = "/test-module:main/i8";
+    int8_t s8(8);
+    s_val.reset(new Val(s8, SR_INT8_T));
+    EXPECT_NO_THROW(sess->set_item(xpath.c_str(), s_val));
+
+    xpath = "/test-module:main/i16";
+    int16_t s16(16);
+    s_val.reset(new Val(s16, SR_INT16_T));
+    EXPECT_NO_THROW(sess->set_item(xpath.c_str(), s_val));
+
+    xpath = "/test-module:main/i32";
+    int32_t s32(32);
+    s_val.reset(new Val(s32, SR_INT32_T));
+    EXPECT_NO_THROW(sess->set_item(xpath.c_str(), s_val));
+
+    xpath = "/test-module:main/id_ref";
+    s_val.reset(new Val("test-module:id_1", SR_IDENTITYREF_T));
+    EXPECT_NO_THROW(sess->set_item(xpath.c_str(), s_val));
+
+    xpath = "/test-module:main/enum";
+    s_val.reset(new Val("maybe", SR_ENUM_T));
+    EXPECT_NO_THROW(sess->set_item(xpath.c_str(), s_val));
+
+    // Binary.
+    xpath = "/test-module:main/raw";
+    s_val.reset(new Val("Zm9vYmFy", SR_BINARY_T));
+    EXPECT_NO_THROW(sess->set_item(xpath.c_str(), s_val));
+
+    // Get it.
+    YangRepr repr(testModel);
+    YRTree tree;
+    EXPECT_NO_THROW(tree = repr.get(sess));
+
+    // Verify.
+    EXPECT_TRUE(repr.verify(testTree, sess, cerr));
+
+    // Some error messages will be displayed.
+
+    // Change a path.
+    YRTree badpath = testTree;
+    badpath[20].xpath_ = "/test-module:kernel-module"; // removed final 's'
+    EXPECT_FALSE(repr.verify(badpath, sess, cerr));
+
+    // Change a value.
+    YRTree badvalue = testTree;
+    badvalue[1].value_ = "Str"; // was "str"
+    EXPECT_FALSE(repr.verify(badvalue, sess, cerr));
+
+    // Change a type.
+    YRTree badtype = testTree;
+    badtype[8].type_ = SR_UINT32_T; // was SR_INT32_T
+    EXPECT_FALSE(repr.verify(badtype, sess, cerr));
+
+    // Add a record at the end.
+    YRTree badmissing = testTree;
+    const string& xpathpc = "/test-module:presence-container";
+    badmissing.push_back(YRItem(xpathpc, "", SR_CONTAINER_PRESENCE_T, false));
+    EXPECT_FALSE(repr.verify(badmissing, sess, cerr));
+
+    // Delete last record.
+    YRTree badextra = testTree;
+    badextra.pop_back();
+    EXPECT_FALSE(repr.verify(badextra, sess, cerr));
+}
+
+// Test set with example module.
+TEST(YangReprTest, setExample) {
+    // Get a translator object to play with.
+    S_Connection conn(new Connection("utils unittests"));
+    S_Session sess(new Session(conn, SR_DS_CANDIDATE));
+
+    // Set the module content.
+    YangRepr repr(exampleModel);
+    EXPECT_NO_THROW(repr.set(exampleTree, sess));
+
+    // Verify it.
+    EXPECT_TRUE(repr.verify(exampleTree, sess, cerr));
+}
+
+// Test set with test module.
+TEST(YangReprTest, setTest) {
+    // Get a translator object to play with.
+    S_Connection conn(new Connection("utils unittests"));
+    S_Session sess(new Session(conn, SR_DS_CANDIDATE));
+
+    // Set the module content.
+    YangRepr repr(testModel);
+    EXPECT_NO_THROW(repr.set(testTree, sess));
+
+    // Verify it.
+    EXPECT_TRUE(repr.verify(testTree, sess, cerr));
+}
+
+}; // end of anonymous namespace
diff --git a/src/lib/yang/tests/yang_configs.h b/src/lib/yang/tests/yang_configs.h
new file mode 100644 (file)
index 0000000..32b7e23
--- /dev/null
@@ -0,0 +1,408 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef ISC_YANG_CONFIGS_H
+#define ISC_YANG_CONFIGS_H
+
+#include <yang/testutils/translator_test.h>
+
+namespace isc {
+namespace yang {
+namespace test {
+
+/// @brief The example module from sysrepo tests.
+const std::string exampleModel = "example-module";
+const YRTree exampleTree = {
+    { "/example-module:container",      "", SR_CONTAINER_T, false },
+    { "/example-module:container/list", "", SR_LIST_T,      true }
+};
+
+/// @brief The test module from sysrepo tests.
+const std::string testModel = "test-module";
+const YRTree testTree = {
+    { "/test-module:main", "", SR_CONTAINER_T, false },
+    { "/test-module:main/string", "str", SR_STRING_T, true },
+    { "/test-module:main/boolean", "true", SR_BOOL_T, true },
+    { "/test-module:main/ui8", "8", SR_UINT8_T, true },
+    { "/test-module:main/ui16", "16", SR_UINT16_T, true },
+    { "/test-module:main/ui32", "32", SR_UINT32_T, true },
+    { "/test-module:main/i8", "8", SR_INT8_T, true },
+    { "/test-module:main/i16", "16", SR_INT16_T, true },
+    { "/test-module:main/i32", "32", SR_INT32_T, true },
+    { "/test-module:main/id_ref", "test-module:id_1", SR_IDENTITYREF_T, true },
+    { "/test-module:main/enum", "maybe", SR_ENUM_T, true },
+    { "/test-module:main/raw", "Zm9vYmFy", SR_BINARY_T, true },
+    { "/test-module:transfer", "", SR_CONTAINER_T, false },
+    { "/test-module:transfer/interval", "30", SR_UINT16_T, false },
+    { "/test-module:interface", "", SR_CONTAINER_T, false },
+    { "/test-module:top-level-default", "default value", SR_STRING_T, false },
+    { "/test-module:university", "", SR_CONTAINER_T, false },
+    { "/test-module:university/students", "", SR_CONTAINER_T, false },
+    { "/test-module:university/classes", "", SR_CONTAINER_T, false },
+    { "/test-module:leafref-chain", "", SR_CONTAINER_T, false },
+    { "/test-module:kernel-modules", "", SR_CONTAINER_T, false },
+    { "/test-module:tpdfs", "", SR_CONTAINER_T, false },
+    { "/test-module:tpdfs/unival", "disabled", SR_STRING_T, false }
+};
+
+/// @brief A subnet with two pools with ietf-dhcpv6-server model.
+const std::string subnetTwoPoolsModelIetf6 = "ietf-dhcpv6-server";
+const YRTree subnetTwoPoolsTreeIetf6 = {
+    { "/ietf-dhcpv6-server:server", "", SR_CONTAINER_PRESENCE_T, false },
+    { "/ietf-dhcpv6-server:server/server-config", "", SR_CONTAINER_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges",
+      "", SR_CONTAINER_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']", "", SR_LIST_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/network-range-id",
+      "111", SR_UINT32_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/network-prefix",
+      "2001:db8::/48", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools",
+      "", SR_CONTAINER_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']", "", SR_LIST_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/pool-id", "0", SR_UINT32_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/pool-prefix",
+      "2001:db8::1:0/112", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/start-address",
+      "2001:db8::1:0", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/end-address",
+      "2001:db8::1:ffff", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/max-address-count",
+      "disabled", SR_ENUM_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']", "", SR_LIST_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/pool-id", "1", SR_UINT32_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/pool-prefix",
+      "2001:db8::2:0/112", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/start-address",
+      "2001:db8::2:0", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/end-address",
+      "2001:db8::2:ffff", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/max-address-count",
+      "disabled", SR_ENUM_T, true }
+};
+
+/// @brief A subnet with timers with ietf-dhcpv6-server model.
+const std::string subnetTimersModel = "ietf-dhcpv6-server";
+const YRTree subnetTimersIetf6 = {
+    { "/ietf-dhcpv6-server:server", "", SR_CONTAINER_PRESENCE_T, false },
+    { "/ietf-dhcpv6-server:server/server-config", "", SR_CONTAINER_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges",
+      "", SR_CONTAINER_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']", "", SR_LIST_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/network-range-id",
+      "111", SR_UINT32_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/network-prefix",
+      "2001:db8::/48", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools",
+      "", SR_CONTAINER_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']", "", SR_LIST_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/pool-id", "0", SR_UINT32_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/pool-prefix",
+      "2001:db8::1:0/112", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/start-address",
+      "2001:db8::1:0", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/end-address",
+      "2001:db8::1:ffff", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/renew-time", "1000", SR_UINT32_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/rebind-time", "2000", SR_UINT32_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/max-address-count",
+      "disabled", SR_ENUM_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']", "", SR_LIST_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/pool-id", "1", SR_UINT32_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/pool-prefix",
+      "2001:db8::2:0/112", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/start-address",
+      "2001:db8::2:0", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/end-address",
+      "2001:db8::2:ffff", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/renew-time", "1000", SR_UINT32_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/rebind-time", "2000", SR_UINT32_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/max-address-count",
+      "disabled", SR_ENUM_T, true }
+};
+
+/// @brief A subnet with two pools with ietf-dhcpv6-server model
+/// which validates.
+const std::string validModelIetf6 = "ietf-dhcpv6-server";
+const YRTree validTreeIetf6 = {
+    { "/ietf-dhcpv6-server:server", "", SR_CONTAINER_PRESENCE_T, false },
+    { "/ietf-dhcpv6-server:server/server-config", "", SR_CONTAINER_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/serv-attributes",
+      "", SR_CONTAINER_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/serv-attributes/vendor-info",
+      "", SR_CONTAINER_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/serv-attributes"
+      "/vendor-info/ent-num", "2495", SR_UINT32_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges",
+      "", SR_CONTAINER_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']", "", SR_LIST_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/network-range-id",
+      "111", SR_UINT32_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/network-description",
+      "Subnet#111", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/network-prefix",
+      "2001:db8::/48", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools",
+      "", SR_CONTAINER_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']", "", SR_LIST_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/pool-id", "0", SR_UINT32_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/pool-prefix",
+      "2001:db8::1:0/112", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/start-address",
+      "2001:db8::1:0", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/end-address",
+      "2001:db8::1:ffff", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/valid-lifetime", "4000", SR_UINT32_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/preferred-lifetime",
+      "3000", SR_UINT32_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/renew-time", "1000", SR_UINT32_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/rebind-time", "2000", SR_UINT32_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/max-address-count",
+      "disabled", SR_ENUM_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='0']/option-set-id", "0", SR_UINT32_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']", "", SR_LIST_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/pool-id", "1", SR_UINT32_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/pool-prefix",
+      "2001:db8::2:0/112", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/start-address",
+      "2001:db8::2:0", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/end-address",
+      "2001:db8::2:ffff", SR_STRING_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/valid-lifetime", "4000", SR_UINT32_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/preferred-lifetime",
+      "3000", SR_UINT32_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/renew-time", "1000", SR_UINT32_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/rebind-time", "2000", SR_UINT32_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/max-address-count",
+      "disabled", SR_ENUM_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+      "network-range[network-range-id='111']/address-pools/"
+      "address-pool[pool-id='1']/option-set-id", "0", SR_UINT32_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/option-sets",
+      "", SR_CONTAINER_T, false },
+    { "/ietf-dhcpv6-server:server/server-config/option-sets"
+      "/option-set[option-set-id='0']", "", SR_LIST_T, true },
+    { "/ietf-dhcpv6-server:server/server-config/option-sets"
+      "/option-set[option-set-id='0']/option-set-id",
+      "0", SR_UINT32_T, false }
+};
+
+/// @brief A subnet with a pool and option data lists with
+/// kea-dhcp4:config model.
+const std::string subnetOptionsModelKeaDhcp4 = "kea-dhcp4";
+const YRTree subnetOptionsTreeKeaDhcp4 = {
+    { "/kea-dhcp4:config", "", SR_CONTAINER_T, false },
+    { "/kea-dhcp4:config/subnet4", "", SR_CONTAINER_T, false },
+    { "/kea-dhcp4:config/subnet4/subnet4[id='111']", "", SR_LIST_T, true },
+    { "/kea-dhcp4:config/subnet4/subnet4[id='111']/id",
+      "111", SR_UINT32_T, false },
+    { "/kea-dhcp4:config/subnet4/subnet4[id='111']/option-data-list",
+      "", SR_CONTAINER_T, false },
+    { "/kea-dhcp4:config/subnet4/subnet4[id='111']/option-data-list/"
+      "option-data[code='100'][space='dns']", "", SR_LIST_T, true },
+    { "/kea-dhcp4:config/subnet4/subnet4[id='111']/option-data-list/"
+      "option-data[code='100'][space='dns']/code",
+      "100", SR_UINT8_T, false },
+    { "/kea-dhcp4:config/subnet4/subnet4[id='111']/option-data-list/"
+      "option-data[code='100'][space='dns']/space",
+      "dns", SR_STRING_T, false },
+    { "/kea-dhcp4:config/subnet4/subnet4[id='111']/option-data-list/"
+      "option-data[code='100'][space='dns']/data",
+      "12121212", SR_STRING_T, true },
+    { "/kea-dhcp4:config/subnet4/subnet4[id='111']/option-data-list/"
+      "option-data[code='100'][space='dns']/csv-format",
+      "false", SR_BOOL_T, true },
+    { "/kea-dhcp4:config/subnet4/subnet4[id='111']/option-data-list/"
+      "option-data[code='100'][space='dns']/always-send",
+      "false", SR_BOOL_T, true },
+    { "/kea-dhcp4:config/subnet4/subnet4[id='111']/pools",
+      "", SR_CONTAINER_T, false },
+    { "/kea-dhcp4:config/subnet4/subnet4[id='111']/pools/"
+      "pool[start-address='10.0.1.0'][end-address='10.0.1.255']",
+      "", SR_LIST_T, true },
+    { "/kea-dhcp4:config/subnet4/subnet4[id='111']/pools/"
+      "pool[start-address='10.0.1.0'][end-address='10.0.1.255']/start-address",
+      "10.0.1.0", SR_STRING_T, false },
+    { "/kea-dhcp4:config/subnet4/subnet4[id='111']/pools/"
+      "pool[start-address='10.0.1.0'][end-address='10.0.1.255']/end-address",
+      "10.0.1.255", SR_STRING_T, false },
+    { "/kea-dhcp4:config/subnet4/subnet4[id='111']/pools/"
+      "pool[start-address='10.0.1.0'][end-address='10.0.1.255']/prefix",
+      "10.0.1.0/24", SR_STRING_T, true },
+    { "/kea-dhcp4:config/subnet4/subnet4[id='111']/subnet",
+      "10.0.0.0/8", SR_STRING_T, true }
+};
+
+/// @brief A subnet with a pool and option data lists with
+/// kea-dhcp6:config model.
+const std::string subnetOptionsModelKeaDhcp6 = "kea-dhcp6";
+const YRTree subnetOptionsTreeKeaDhcp6 = {
+    { "/kea-dhcp6:config", "", SR_CONTAINER_T, false },
+    { "/kea-dhcp6:config/subnet6", "", SR_CONTAINER_T, false },
+    { "/kea-dhcp6:config/subnet6/subnet6[id='111']", "", SR_LIST_T, true },
+    { "/kea-dhcp6:config/subnet6/subnet6[id='111']/id",
+      "111", SR_UINT32_T, false },
+    { "/kea-dhcp6:config/subnet6/subnet6[id='111']/pools",
+      "", SR_CONTAINER_T, false },
+    { "/kea-dhcp6:config/subnet6/subnet6[id='111']/pools/"
+      "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']",
+      "", SR_LIST_T, true },
+    { "/kea-dhcp6:config/subnet6/subnet6[id='111']/pools/"
+      "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+      "start-address", "2001:db8::1:0", SR_STRING_T, false },
+    { "/kea-dhcp6:config/subnet6/subnet6[id='111']/pools/"
+      "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+      "end-address", "2001:db8::1:ffff", SR_STRING_T, false },
+    { "/kea-dhcp6:config/subnet6/subnet6[id='111']/pools/"
+      "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+      "prefix", "2001:db8::1:0/112", SR_STRING_T, true },
+    { "/kea-dhcp6:config/subnet6/subnet6[id='111']/pools/"
+      "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+      "option-data-list", "", SR_CONTAINER_T, false },
+    { "/kea-dhcp6:config/subnet6/subnet6[id='111']/pools/"
+      "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+      "option-data-list/option-data[code='100'][space='dns']",
+      "", SR_LIST_T, true },
+    { "/kea-dhcp6:config/subnet6/subnet6[id='111']/pools/"
+      "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+      "option-data-list/option-data[code='100'][space='dns']/code",
+      "100", SR_UINT16_T, false },
+    { "/kea-dhcp6:config/subnet6/subnet6[id='111']/pools/"
+      "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+      "option-data-list/option-data[code='100'][space='dns']/space",
+      "dns", SR_STRING_T, false },
+    { "/kea-dhcp6:config/subnet6/subnet6[id='111']/pools/"
+      "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+      "option-data-list/option-data[code='100'][space='dns']/data",
+      "12121212", SR_STRING_T, true },
+    { "/kea-dhcp6:config/subnet6/subnet6[id='111']/pools/"
+      "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+      "option-data-list/option-data[code='100'][space='dns']/csv-format",
+      "false", SR_BOOL_T, true },
+    { "/kea-dhcp6:config/subnet6/subnet6[id='111']/pools/"
+      "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+      "option-data-list/option-data[code='100'][space='dns']/always-send",
+      "false", SR_BOOL_T, true },
+    { "/kea-dhcp6:config/subnet6/subnet6[id='111']/subnet",
+      "2001:db8::/48", SR_STRING_T, true }
+};
+
+}; // end of namespace isc::yang::test
+}; // end of namespace isc::yang
+}; // end of namespace isc
+
+#endif // ISC_YANG_CONFIGS_H
diff --git a/src/lib/yang/testutils/Makefile.am b/src/lib/yang/testutils/Makefile.am
new file mode 100644 (file)
index 0000000..aa0ac23
--- /dev/null
@@ -0,0 +1,22 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(SYSREPO_CPPFLAGS)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+if HAVE_GTEST
+
+noinst_LTLIBRARIES = libyangtest.la
+
+libyangtest_la_SOURCES  = translator_test.cc translator_test.h
+
+libyangtest_la_CXXFLAGS = $(AM_CXXFLAGS)
+libyangtest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libyangtest_la_LDFLAGS  = $(AM_LDFLAGS)
+
+libyangtest_la_LIBADD   = $(top_builddir)/src/lib/yang/libkea-yang.la
+libyangtest_la_LIBADD  += $(top_builddir)/src/lib/log/libkea-log.la
+
+endif
diff --git a/src/lib/yang/testutils/translator_test.cc b/src/lib/yang/testutils/translator_test.cc
new file mode 100644 (file)
index 0000000..304554a
--- /dev/null
@@ -0,0 +1,392 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <yang/testutils/translator_test.h>
+#include <boost/lexical_cast.hpp>
+#include <sstream>
+
+using namespace std;
+using namespace isc::data;
+
+namespace isc {
+namespace yang {
+namespace test {
+
+YangRepr::YangReprItem
+YangRepr::YangReprItem::get(const string& xpath, S_Session session) {
+    string val_xpath = xpath;
+    string value = "";
+    sr_type_t type = SR_UNKNOWN_T;
+    bool settable = true;
+    try {
+        S_Val s_val = session->get_item(xpath.c_str());
+        if (!s_val) {
+            isc_throw(BadValue, "YangReprItem failed at '" << xpath << "'");
+        }
+        val_xpath = string(s_val->xpath());
+        type = s_val->type();
+        ostringstream int_value;
+        switch (type) {
+        case SR_CONTAINER_T:
+        case SR_CONTAINER_PRESENCE_T:
+            settable = false;
+            break;
+
+        case SR_LIST_T:
+            break;
+
+        case SR_STRING_T:
+            value = s_val->data()->get_string();
+            break;
+
+        case SR_BOOL_T:
+            value = s_val->data()->get_bool() ? "true" : "false";
+            break;
+
+        case SR_UINT8_T:
+            int_value << static_cast<unsigned>(s_val->data()->get_uint8());
+            value = int_value.str();
+            break;
+
+        case SR_UINT16_T:
+            int_value << s_val->data()->get_uint16();
+            value = int_value.str();
+            break;
+
+        case SR_UINT32_T:
+            int_value << s_val->data()->get_uint32();
+            value = int_value.str();
+            break;
+
+        case SR_INT8_T:
+            int_value << static_cast<unsigned>(s_val->data()->get_int8());
+            value = int_value.str();
+            break;
+
+        case SR_INT16_T:
+            int_value << s_val->data()->get_int16();
+            value = int_value.str();
+            break;
+
+        case SR_INT32_T:
+            int_value << s_val->data()->get_int32();
+            value = int_value.str();
+            break;
+
+        case SR_IDENTITYREF_T:
+            value = s_val->data()->get_identityref();
+            break;
+
+        case SR_ENUM_T:
+            value = s_val->data()->get_enum();
+            break;
+
+        case SR_BINARY_T:
+            value = s_val->data()->get_binary();
+            break;
+
+        default:
+            isc_throw(NotImplemented,
+                      "YangReprItem called with unupported type: " << type);
+        }
+    } catch (const sysrepo_exception& ex) {
+        isc_throw(SysrepoError,
+                  "sysrepo error in YangReprItem: " << ex.what());
+    }
+    return (YangReprItem(val_xpath, value, type, settable));
+}
+
+YangRepr::Tree
+YangRepr::get(S_Session session) const {
+    Tree result;
+    try {
+        const string& xpath0 = "/" + model_ + ":*//.";
+        TranslatorBasic tb(session);
+        S_Iter_Value iter = tb.getIter(xpath0);
+        for (;;) {
+            const string& xpath = tb.getNext(iter);
+            if (xpath.empty()) {
+                break;
+            }
+            result.push_back(YangReprItem::get(xpath, session));
+        }
+    } catch (const sysrepo_exception& ex) {
+        isc_throw(SysrepoError,
+                  "sysrepo error in  YangRepr::getTree: " << ex.what());
+    }
+    return (result);
+}
+
+bool
+YangRepr::verify(const Tree& expected, S_Session session,
+                 ostream& errs) const {
+    const Tree& got = get(session);
+    for (size_t i = 0; (i < expected.size()) && (i < got.size()); ++i) {
+        if (expected[i] == got[i]) {
+            continue;
+        }
+        errs << "expected[" << i << "]: " << expected[i] << endl;
+        errs << "got[" << i << "]: " << got[i] << endl;
+        return (false);
+    }
+    if (expected.size() == got.size()) {
+        return (true);
+    }
+    if (expected.size() > got.size()) {
+        errs << "missings " << (expected.size() - got.size());
+        errs << " beginning by:" << endl << expected[got.size()] << endl;
+    } else {
+        errs << "extras " << (got.size() - expected.size());
+        errs << " beginning by:" << endl << got[expected.size()] << endl;
+    }
+    return (false);
+}
+
+void
+YangRepr::set(const Tree& tree, S_Session session) const {
+    for (auto item : tree) {
+        if (!item.settable_) {
+            continue;
+        }
+        try {
+            S_Val s_val;
+            switch (item.type_) {
+            case SR_CONTAINER_T:
+            case SR_CONTAINER_PRESENCE_T:
+                isc_throw(NotImplemented,
+                          "YangRepr::set called for a container");
+
+            case SR_LIST_T:
+                break;
+
+            case SR_STRING_T:
+            case SR_IDENTITYREF_T:
+            case SR_ENUM_T:
+            case SR_BINARY_T:
+                s_val.reset(new Val(item.value_.c_str(), item.type_));
+                break;
+
+            case SR_BOOL_T:
+                if (item.value_ == "true") {
+                    s_val.reset(new Val(true, SR_BOOL_T));
+                } else if (item.value_ == "false") {
+                    s_val.reset(new Val(false, SR_BOOL_T));
+                } else {
+                    isc_throw(BadValue, "'" << item.value_ << "' not a bool");
+                }
+                break;
+
+            case SR_UINT8_T:
+                try {
+                    uint8_t u8 = boost::lexical_cast<unsigned>(item.value_);
+                    s_val.reset(new Val(u8, SR_UINT8_T));
+                } catch (const boost::bad_lexical_cast&) {
+                    isc_throw(BadValue,
+                              "'" << item.value_ << "' not an uint8");
+                }
+                break;
+
+            case SR_UINT16_T:
+                try {
+                    uint16_t u16 = boost::lexical_cast<uint16_t>(item.value_);
+                    s_val.reset(new Val(u16, SR_UINT16_T));
+                } catch (const boost::bad_lexical_cast&) {
+                    isc_throw(BadValue,
+                              "'" << item.value_ << "' not an uint16");
+                }
+                break;
+
+            case SR_UINT32_T:
+                try {
+                    uint32_t u32 = boost::lexical_cast<uint32_t>(item.value_);
+                    s_val.reset(new Val(u32, SR_UINT32_T));
+                } catch (const boost::bad_lexical_cast&) {
+                    isc_throw(BadValue,
+                              "'" << item.value_ << "' not an uint32");
+                }
+                break;
+
+            case SR_INT8_T:
+                try {
+                    int8_t i8 = boost::lexical_cast<int>(item.value_);
+                    s_val.reset(new Val(i8, SR_INT8_T));
+                } catch (const boost::bad_lexical_cast&) {
+                    isc_throw(BadValue,
+                              "'" << item.value_ << "' not an int8");
+                }
+                break;
+
+            case SR_INT16_T:
+                try {
+                    int16_t i16 = boost::lexical_cast<int16_t>(item.value_);
+                    s_val.reset(new Val(i16, SR_INT16_T));
+                } catch (const boost::bad_lexical_cast&) {
+                    isc_throw(BadValue,
+                              "'" << item.value_ << "' not an int16");
+                }
+                break;
+
+            case SR_INT32_T:
+                try {
+                    int32_t i32 = boost::lexical_cast<int32_t>(item.value_);
+                    s_val.reset(new Val(i32, SR_INT32_T));
+                } catch (const boost::bad_lexical_cast&) {
+                    isc_throw(BadValue,
+                              "'" << item.value_ << "' not an int32");
+                }
+                break;
+
+            default:
+                isc_throw(NotImplemented,
+                          "YangRepr::set called with unupported type: "
+                          << item.type_);
+            }
+            session->set_item(item.xpath_.c_str(), s_val);
+        } catch (const sysrepo_exception& ex) {
+            isc_throw(SysrepoError,
+                      "sysrepo error in YangRepr::set for " << item
+                      << ", error: " << ex.what());
+        }
+    }
+}
+
+bool
+YangRepr::validate(S_Session session, std::ostream& errs) const {
+    try {
+        session->validate();
+        return (true);
+    } catch (const std::exception& ex) {
+        errs << "validate fails with " << ex.what() << endl;
+    }
+    try {
+        S_Errors s_errors = session->get_last_errors();
+        if (!s_errors) {
+            errs << "no errors" << endl;
+            return (false);
+        }
+        size_t cnt = s_errors->error_cnt();
+        errs << "got " << cnt << " errors" << endl;
+        for (size_t i = 0; i < cnt; ++i) {
+            S_Error s_error = s_errors->error(i);
+            if (!s_error) {
+                continue;
+            }
+            const char* xpath = s_error->xpath();
+            const char* message = s_error->message();
+            if (!xpath || !message) {
+                continue;
+            }
+            // Bug in sysrepo returning message for xpath().
+            if (xpath == message) {
+                errs << message << endl;
+            } else {
+                errs << message << endl
+                     << "At " << xpath << endl;
+            }
+        }
+    } catch (const std::exception& ex) {
+        // Bug in sysrepo rethrowing the last error when trying to get it.
+        errs << "double error " << ex.what();
+    }
+    return (false);
+}
+
+ostream&
+operator<<(ostream& os, sr_type_t type) {
+    switch (type) {
+    case SR_CONTAINER_T:
+        os << "container";
+        break;
+    case SR_CONTAINER_PRESENCE_T:
+        os << "container presence";
+        break;
+    case SR_LIST_T:
+        os << "list";
+        break;
+    case SR_STRING_T:
+        os << "string";
+        break;
+    case SR_BOOL_T:
+        os << "bool";
+        break;
+    case SR_UINT8_T:
+        os << "uint8";
+        break;
+    case SR_UINT16_T:
+        os << "uint16";
+        break;
+    case SR_UINT32_T:
+        os << "uint32";
+        break;
+    case SR_INT8_T:
+        os << "int8";
+        break;
+    case SR_INT16_T:
+        os << "int16";
+        break;
+    case SR_INT32_T:
+        os << "int32";
+        break;
+    case SR_IDENTITYREF_T:
+        os << "identity ref";
+        break;
+    case SR_ENUM_T:
+        os << "enum";
+        break;
+    case SR_BINARY_T:
+        os << "binary";
+        break;
+    case SR_LEAF_EMPTY_T:
+        os << "leaf empty";
+        break;
+    case SR_BITS_T:
+        os << "bits";
+        break;
+    case SR_DECIMAL64_T:
+        os << "decimal64";
+        break;
+    case SR_INSTANCEID_T:
+        os << "instance id";
+        break;
+    case SR_INT64_T:
+        os << "int64";
+        break;
+    case SR_UINT64_T:
+        os << "uint64";
+        break;
+    case SR_ANYXML_T:
+        os << "any xml";
+        break;
+    case SR_ANYDATA_T:
+        os << "any data";
+        break;
+#ifdef SR_UNION_T
+    case SR_UNION_T:
+        os << "union";
+        break;
+#endif
+    default:
+        os << type;
+        break;
+    }
+    return (os);
+}
+
+ostream& operator<<(ostream& os, const YangRepr::YangReprItem& item) {
+    os << item.xpath_ << " = (" << item.type_ << ") '" << item.value_ << "'";
+    return (os);
+}
+
+ostream& operator<<(ostream& os, const YangRepr::Tree& tree) {
+    for (auto item : tree) {
+        os << item << endl;
+    }
+    return (os);
+}
+
+}; // end of namespace isc::yang::test
+}; // end of namespace isc::yang
+}; // end of namespace isc
diff --git a/src/lib/yang/testutils/translator_test.h b/src/lib/yang/testutils/translator_test.h
new file mode 100644 (file)
index 0000000..1036021
--- /dev/null
@@ -0,0 +1,127 @@
+// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef ISC_TRANSLATOR_TEST_H
+#define ISC_TRANSLATOR_TEST_H 1
+
+#include <yang/translator.h>
+#include <vector>
+#include <iostream>
+
+namespace isc {
+namespace yang {
+namespace test {
+
+/// @brief Yang/sysrepo datastore textual representation class.
+class YangRepr {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param model The model name.
+    YangRepr(const std::string& model) : model_(model) {
+    }
+
+    /// @brief The item class.
+    struct YangReprItem {
+        /// @brief Constructor with content.
+        ///
+        /// @param xpath The xpath.
+        /// @param value The textual value.
+        /// @param type The type of the value.
+        /// @param settable The settable flag.
+        YangReprItem(std::string xpath, std::string value,
+                     sr_type_t type, bool settable)
+            : xpath_(xpath), value_(value), type_(type), settable_(settable) {
+        }
+
+        /// @brief Factory from session.
+        ///
+        /// @param xpath The xpath.
+        /// @param session Sysrepo session.
+        static YangReprItem get(const std::string& xpath, S_Session session);
+
+        /// @brief The xpath.
+        std::string xpath_;
+
+        /// @brief The textual value.
+        std::string value_;
+
+        /// @brief The type of the value.
+        sr_type_t type_;
+
+        /// @brief The settable flag.
+        bool settable_;
+
+        /// @brief The equal operator ignoring settable.
+        bool operator==(const YangReprItem& other) const {
+            return ((xpath_ == other.xpath_) &&
+                    (value_ == other.value_) &&
+                    (type_ == other.type_));
+        }
+
+        /// @brief The unequal operator ignoring settable.
+        bool operator!=(const YangReprItem& other) const {
+            return (!(*this == other));
+        }
+    };
+
+    /// @brief Tree type.
+    typedef std::vector<YangReprItem> Tree;
+
+    /// @brief Get tree from session.
+    ///
+    /// @param session Sysrepo session.
+    Tree get(S_Session session) const;
+
+    /// @brief Verify tree.
+    ///
+    /// @param expected The expected value.
+    /// @param session Sysrepo session.
+    /// @param errs Error stream.
+    /// @return True if verification succeeds, false with errors displayed
+    /// on errs if it fails.
+    bool verify(const Tree& expected, S_Session session,
+                std::ostream& errs) const;
+
+    /// @brief Set tree to session.
+    ///
+    /// @param tree The tree to install.
+    /// @param session Sysrepo session.
+    void set(const Tree& tree, S_Session session) const;
+
+    /// @brief Validate.
+    ///
+    /// @param session Sysrepo session.
+    /// @param errs Error stream.
+    /// @return True if validation succeeds, false with errors displayed
+    /// on errs if it fails.
+    bool validate(S_Session session, std::ostream& errs) const;
+
+    /// @brief The model name.
+    std::string model_;
+};
+
+/// @brief Alias for Items.
+typedef YangRepr::YangReprItem YRItem;
+
+/// @brief Alias for Trees.
+typedef YangRepr::Tree YRTree;
+
+/// @brief Overrides standard output operator for sr_type_t.
+std::ostream& operator<<(std::ostream& os, sr_type_t type);
+
+/// @brief Overrides standard output operator for @c YangReprItem object.
+std::ostream& operator<<(std::ostream& os, const YRItem& item);
+
+/// @brief Overrides standard output operator for @c Tree object.
+std::ostream& operator<<(std::ostream& os, const YRTree& tree);
+
+}; // end of namespace isc::yang::test
+}; // end of namespace isc::yang
+}; // end of namespace isc
+
+#endif // ISC_TRANSLATOR_TEST_H