Browse Source

[fdunparse2] Added missing files

Francis Dupont 8 years ago
parent
commit
d04f904760

+ 282 - 0
src/bin/agent/tests/get_config_unittest.cc

@@ -0,0 +1,282 @@
+// Copyright (C) 2017 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 <cc/data.h>
+#include <cc/command_interpreter.h>
+#include <process/testutils/d_test_stubs.h>
+#include <agent/ca_cfg_mgr.h>
+#include <agent/parser_context.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <sstream>
+
+#include "test_data_files_config.h"
+#include "test_libraries.h"
+
+using namespace isc::agent;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::process;
+
+namespace {
+
+/// @name How to generate the testdata/get_config.json file
+///
+/// Define GENERATE_ACTION and recompile. Run ca_unittests on
+/// CtrlAgentGetCfgTest redirecting the standard error to a temporary
+/// file, e.g. by
+/// @code
+///    ./ca_unittests --gtest_filter="CtrlAgentGetCfg*" > /dev/null 2> u
+/// @endcode
+///
+/// Update testdata/get_config.json using the temporary file content,
+/// (removing head comment and restoring hook library path),
+/// recompile without GENERATE_ACTION.
+
+/// @brief the generate action
+/// false means do nothing, true means unparse extracted configurations
+#ifdef GENERATE_ACTION
+const bool generate_action = true;
+#else
+const bool generate_action = false;
+#endif
+
+/// @brief Read a file into a string
+std::string
+readFile(const std::string& file_path) {
+    std::ifstream ifs(file_path);
+    if (!ifs.is_open()) {
+        ADD_FAILURE() << "readFile cannot open " << file_path;
+        isc_throw(isc::Unexpected, "readFile cannot open " << file_path);
+    }
+    std::string lines;
+    std::string line;
+    while (std::getline(ifs, line)) {
+        lines += line + "\n";
+    }
+    ifs.close();
+    return (lines);
+}
+
+/// @brief Runs parser in JSON mode
+ElementPtr
+parseJSON(const std::string& in,  bool verbose = false) {
+    try {
+        ParserContext ctx;
+        return (ctx.parseString(in, ParserContext::PARSER_JSON));
+    } catch (const std::exception& ex) {
+        if (verbose) {
+            std::cout << "EXCEPTION: " << ex.what() << std::endl;
+        }
+        throw;
+    }
+}
+
+/// @brief Runs parser in AGENT mode
+ElementPtr
+parseAGENT(const std::string& in,  bool verbose = false) {
+    try {
+        ParserContext ctx;
+        return (ctx.parseString(in, ParserContext::PARSER_AGENT));
+    } catch (const std::exception& ex) {
+        if (verbose) {
+            std::cout << "EXCEPTION: " << ex.what() << std::endl;
+        }
+        throw;
+    }
+}
+
+/// @brief Replace the library path
+void
+pathReplacer(ConstElementPtr ca_cfg) {
+    ConstElementPtr hooks_libs = ca_cfg->get("hooks-libraries");
+    if (!hooks_libs || hooks_libs->empty()) {
+        return;
+    }
+    ElementPtr first_lib = hooks_libs->getNonConst(0);
+    std::string lib_path(BASIC_CALLOUT_LIBRARY);
+    first_lib->set("library", Element::create(lib_path));
+}
+
+/// @brief Almost regular agent CfgMgr with internal parse method exposed.
+class NakedAgentCfgMgr : public CtrlAgentCfgMgr {
+public:
+    using CtrlAgentCfgMgr::parse;
+};
+
+}
+
+/// Test fixture class
+class CtrlAgentGetCfgTest : public ConfigParseTest {
+public:
+    CtrlAgentGetCfgTest()
+    : rcode_(-1) {
+        srv_.reset(new NakedAgentCfgMgr());
+        // Create fresh context.
+        resetConfiguration();
+    }
+
+    ~CtrlAgentGetCfgTest() {
+        resetConfiguration();
+    }
+
+    /// @brief Parse and Execute configuration
+    ///
+    /// Parses a configuration and executes a configuration of the server.
+    /// If the operation fails, the current test will register a failure.
+    ///
+    /// @param config Configuration to parse
+    /// @param operation Operation being performed.  In the case of an error,
+    ///        the error text will include the string "unable to <operation>.".
+    ///
+    /// @return true if the configuration succeeded, false if not.
+    bool
+    executeConfiguration(const std::string& config, const char* operation) {
+        // try JSON parser
+        ConstElementPtr json;
+        try {
+            json = parseJSON(config, true);
+        } catch (const std::exception& ex) {
+            ADD_FAILURE() << "invalid JSON for " << operation
+                          << " failed with " << ex.what()
+                          << " on\n" << config << "\n";
+            return (false);
+        }
+
+        // try AGENT parser
+        try {
+            json = parseAGENT(config, true);
+        } catch (...) {
+            ADD_FAILURE() << "parsing failed for " << operation
+                          << " on\n" << prettyPrint(json) << "\n";
+            return (false);
+        }
+
+        // get Control-agent element
+        ConstElementPtr ca = json->get("Control-agent");
+        if (!ca) {
+            ADD_FAILURE() << "cannot get Control-agent for " << operation
+                          << " on\n" << prettyPrint(json) << "\n";
+            return (false);
+        }
+
+        // update hooks-libraries
+        pathReplacer(ca);
+
+        // try AGENT configure
+        ConstElementPtr status;
+        try {
+            status = srv_->parse(ca, true);
+        } catch (const std::exception& ex) {
+            ADD_FAILURE() << "configure for " << operation
+                          << " failed with " << ex.what()
+                          << " on\n" << prettyPrint(json) << "\n";
+            return (false);
+        }
+
+        // The status object must not be NULL
+        if (!status) {
+            ADD_FAILURE() << "configure for " << operation
+                          << " returned null on\n"
+                          << prettyPrint(json) << "\n";
+            return (false);
+        }
+
+        // Returned value should be 0 (configuration success)
+        comment_ = parseAnswer(rcode_, status);
+        if (rcode_ != 0) {
+            string reason = "";
+            if (comment_) {
+                reason = string(" (") + comment_->stringValue() + string(")");
+            }
+            ADD_FAILURE() << "configure for " << operation
+                          << " returned error code "
+                          << rcode_ << reason << " on\n"
+                          << prettyPrint(json) << "\n";
+            return (false);
+        }
+        return (true);
+    }
+
+    /// @brief Reset configuration database.
+    ///
+    /// This function resets configuration data base by
+    /// removing control sockets and hooks. Reset must
+    /// be performed after each test to make sure that
+    /// contents of the database do not affect result of
+    /// subsequent tests.
+    void resetConfiguration() {
+        string config = "{ \"Control-agent\": {"
+            " \"http-host\": \"\","
+            " \"http-port\": 0 } }";
+        EXPECT_TRUE(executeConfiguration(config, "reset config"));
+    }
+
+    boost::scoped_ptr<NakedAgentCfgMgr> srv_; ///< CA server under test
+    int rcode_;                       ///< Return code from element parsing
+    ConstElementPtr comment_;         ///< Reason for parse fail
+};
+
+/// Test a configuration
+TEST_F(CtrlAgentGetCfgTest, simple) {
+
+    // get the simple configuration
+    std::string simple_file = string(CFG_EXAMPLES) + "/" + "simple.json";
+    std::string config;
+    ASSERT_NO_THROW(config = readFile(simple_file));
+
+    // get the expected configuration
+    std::string expected_file =
+        std::string(CA_TEST_DATA_DIR) + "/" + "get_config.json";
+    std::string expected;
+    ASSERT_NO_THROW(expected = readFile(expected_file));
+
+    // execute the sample configuration
+    ASSERT_TRUE(executeConfiguration(config, "simple config"));
+
+    // unparse it
+    CtrlAgentCfgContextPtr context = srv_->getCtrlAgentCfgContext();
+    ConstElementPtr unparsed;
+    ASSERT_NO_THROW(unparsed = context->toElement());
+
+    // dump if wanted else check
+    if (generate_action) {
+        std::cerr << "// Generated Configuration (remove this line)\n";
+        ASSERT_NO_THROW(expected = prettyPrint(unparsed));
+        prettyPrint(unparsed, std::cerr, 0, 4);
+        std::cerr << "\n";
+    } else {
+        ConstElementPtr json;
+        ASSERT_NO_THROW(json = parseAGENT(expected, true));
+        ConstElementPtr ca;
+        ASSERT_NO_THROW(ca = json->get("Control-agent"));
+        ASSERT_TRUE(ca);
+        pathReplacer(ca);
+        EXPECT_TRUE(isEquivalent(unparsed, json));
+        std::string current = prettyPrint(unparsed, 0, 4);
+        std::string expected2 = prettyPrint(json, 0, 4);
+        EXPECT_EQ(expected2, current);
+        if (expected2 != current) {
+            expected = current + "\n";
+        }
+    }
+
+    // execute the control agent configuration
+    EXPECT_TRUE(executeConfiguration(expected, "unparsed config"));
+
+    // is it a fixed point?
+    CtrlAgentCfgContextPtr context2 = srv_->getCtrlAgentCfgContext();
+    ConstElementPtr unparsed2;
+    ASSERT_NO_THROW(unparsed2 = context2->toElement());
+    ASSERT_TRUE(unparsed2);
+    EXPECT_TRUE(isEquivalent(unparsed, unparsed2));
+}

+ 9 - 0
src/bin/agent/tests/test_data_files_config.h.in

@@ -0,0 +1,9 @@
+// Copyright (C) 2017 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/.
+
+/// @brief Path to agent source dir
+#define CA_SRC_DIR "@abs_top_srcdir@/src/bin/agent"
+#define CA_TEST_DATA_DIR "@abs_top_srcdir@/src/bin/agent/tests/testdata"

File diff suppressed because it is too large
+ 6105 - 0
src/bin/dhcp6/tests/get_config_unittest.cc


+ 348 - 0
src/bin/dhcp6/tests/get_config_unittest.cc.skel

@@ -0,0 +1,348 @@
+// Copyright (C) 2017 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 <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <cc/cfg_to_element.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/tests/get_config_unittest.h>
+#include <dhcp6/dhcp6_srv.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/simple_parser6.h>
+
+#include <boost/algorithm/string.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <list>
+
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @name How to fill configurations
+///
+/// Copy get_config_unittest.cc.skel into get_config_unittest.cc
+///
+/// For the extracted configurations define the EXTRACT_CONFIG and
+/// recompile this file. Run dhcp6_unittests on Dhcp6ParserTest
+/// redirecting the standard error to a temporary file, e.g. by
+/// @code
+///    ./dhcp6_unittests --gtest_filter="Dhcp6Parser*" > /dev/null 2> x
+/// @endcode
+///
+/// Update EXTRACTED_CONFIGS with the file content
+///
+/// When configurations have been extracted the corresponding unparsed
+/// configurations must be generated. To do that define GENERATE_ACTION
+/// and recompile this file. Run dhcp6_unittests on Dhcp6GetConfigTest
+/// redirecting the standard error to a temporary file, e.g. by
+/// @code
+///    ./dhcp6_unittests --gtest_filter="Dhcp6GetConfig*" > /dev/null 2> u
+/// @endcode
+///
+/// Update UNPARSED_CONFIGS with the file content, recompile this file
+/// without EXTRACT_CONFIG and GENERATE_ACTION.
+///
+/// @note Check for failures at each step!
+/// @note The tests of this file do not check if configs returned
+/// by @ref isc::dhcp::CfgToElement::ToElement() are complete.
+/// This has to be done manually.
+///
+///@{
+/// @brief extracted configurations
+const char* EXTRACTED_CONFIGS[] = {
+    // "to be replaced"
+};
+
+/// @brief unparsed configurations
+const char* UNPARSED_CONFIGS[] = {
+    // "to be replaced"
+};
+
+/// @brief the number of configurations
+const size_t max_config_counter = sizeof(EXTRACTED_CONFIGS) / sizeof(char*);
+///@}
+
+/// @brief the extraction counter
+///
+/// < 0 means do not extract, >= 0 means extract on extractConfig() calls
+/// and increment
+#ifdef EXTRACT_CONFIG
+int extract_count = 0;
+#else
+int extract_count = -1;
+#endif
+
+/// @brief the generate action
+/// false means do nothing, true means unparse extracted configurations
+#ifdef GENERATE_ACTION
+const bool generate_action = true;
+#else
+const bool generate_action = false;
+static_assert(max_config_counter == sizeof(UNPARSED_CONFIGS) / sizeof(char*),
+              "unparsed configurations must be generated");
+#endif
+
+/// @brief format and output a configuration
+void
+outputFormatted(const std::string& config) {
+    // pretty print it
+    ConstElementPtr json = parseJSON(config);
+    std::string prettier = prettyPrint(json, 4, 4);
+    // get it as a line array
+    std::list<std::string> lines;
+    boost::split(lines, prettier, boost::is_any_of("\n"));
+    // add escapes using again JSON
+    std::list<std::string> escapes;
+    while (!lines.empty()) {
+        const std::string& line = lines.front();
+        ConstElementPtr escaping = Element::create(line + "\n");
+        escapes.push_back(escaping->str());
+        lines.pop_front();
+    }
+    // output them on std::cerr
+    while (!escapes.empty()) {
+        std::cerr << "\n" << escapes.front();
+        escapes.pop_front();
+    }
+}
+
+};
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @ref isc::dhcp::test::extractConfig in the header
+void
+extractConfig(const std::string& config) {
+    // skip when disable
+    if (extract_count < 0) {
+        return;
+    }
+    // mark beginning
+    if (extract_count == 0) {
+        // header (note there is no trailer)
+        std::cerr << "/// put this after const char* EXTRACTED_CONFIGS[] = {\n";
+    } else {
+        // end of previous configuration
+        std::cerr << ",\n";
+    }
+    std::cerr << "    // CONFIGURATION " << extract_count;
+    try {
+        outputFormatted(config);
+    } catch (...) {
+        // mark error
+        std::cerr << "\n//// got an error\n";
+    }
+    ++extract_count;
+}
+
+};
+};
+};
+
+namespace {
+
+/// Test fixture class (code from Dhcp6ParserTest)
+class Dhcp6GetConfigTest : public ::testing::TestWithParam<size_t> {
+public:
+    Dhcp6GetConfigTest() : rcode_(-1), srv_(0) {
+        // srv_(0) means to not open any sockets. We don't want to
+        // deal with sockets here, just check if configuration handling
+        // is sane.
+
+        // Reset configuration for each test.
+        resetConfiguration();
+    }
+
+    ~Dhcp6GetConfigTest() {
+        // Reset configuration database after each test.
+        resetConfiguration();
+    };
+
+    /// @brief Parse and Execute configuration
+    ///
+    /// Parses a configuration and executes a configuration of the server.
+    /// If the operation fails, the current test will register a failure.
+    ///
+    /// @param config Configuration to parse
+    /// @param operation Operation being performed.  In the case of an error,
+    ///        the error text will include the string "unable to <operation>.".
+    ///
+    /// @return true if the configuration succeeded, false if not.
+    bool
+    executeConfiguration(const std::string& config, const char* operation) {
+        // clear config manager
+        CfgMgr::instance().clear();
+
+        // enable fake network interfaces
+        IfaceMgrTestConfig test_config(true);
+
+        // try JSON parser
+        ConstElementPtr json;
+        try {
+            json = parseJSON(config);
+        } catch (const std::exception& ex) {
+            ADD_FAILURE() << "invalid JSON for " << operation
+                          << " failed with " << ex.what()
+                          << " on\n" << config << "\n";
+            return (false);
+        }
+
+        // try DHCP6 parser
+        try {
+            json = parseDHCP6(config, true);
+        } catch (...) {
+            ADD_FAILURE() << "parsing failed for " << operation
+                          << " on\n" << prettyPrint(json) << "\n";
+            return (false);
+        }
+
+        // try DHCP6 configure
+        ConstElementPtr status;
+        try {
+            status = configureDhcp6Server(srv_, json);
+        } catch (const std::exception& ex) {
+            ADD_FAILURE() << "configure for " << operation
+                          << " failed with " << ex.what()
+                          << " on\n" << prettyPrint(json) << "\n";
+            return (false);
+        }
+
+        // The status object must not be NULL
+        if (!status) {
+            ADD_FAILURE() << "configure for " << operation
+                          << " returned null on\n"
+                          << prettyPrint(json) << "\n";
+            return (false);
+        }
+
+        // Returned value should be 0 (configuration success)
+        comment_ = parseAnswer(rcode_, status);
+        if (rcode_ != 0) {
+            string reason = "";
+            if (comment_) {
+                reason = string(" (") + comment_->stringValue() + string(")");
+            }
+            ADD_FAILURE() << "configure for " << operation
+                          << " returned error code "
+                          << rcode_ << reason << " on\n"
+                          << prettyPrint(json) << "\n";
+            return (false);
+        }
+        return (true);
+    }
+
+    /// @brief Reset configuration database.
+    ///
+    /// This function resets configuration data base by
+    /// removing all subnets and option-data. Reset must
+    /// be performed after each test to make sure that
+    /// contents of the database do not affect result of
+    /// subsequent tests.
+    void resetConfiguration() {
+        string config = "{"
+            "\"interfaces-config\": { \"interfaces\": [ \"*\" ] },"
+            "\"preferred-lifetime\": 3000,"
+            "\"rebind-timer\": 2000, "
+            "\"renew-timer\": 1000, "
+            "\"valid-lifetime\": 4000, "
+            "\"subnet6\": [ ], "
+            "\"dhcp-ddns\": { \"enable-updates\" : false }, "
+            "\"option-def\": [ ], "
+            "\"option-data\": [ ] }";
+        EXPECT_TRUE(executeConfiguration(config, "reset configuration"));
+        CfgMgr::instance().clear();
+        CfgMgr::instance().setFamily(AF_INET6);
+    }
+
+    int rcode_;          ///< Return code (see @ref isc::config::parseAnswer)
+    Dhcpv6Srv srv_;      ///< Instance of the Dhcp6Srv used during tests
+    ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer)
+};
+
+/// Test a configuration
+TEST_P(Dhcp6GetConfigTest, run) {
+    // configurations have not been extracted yet
+    if (max_config_counter == 0) {
+        return;
+    }
+
+    // get the index of configurations to test
+    size_t config_counter = GetParam();
+
+    // emit unparsed header if wanted
+    if ((config_counter == 0) && generate_action) {
+        std::cerr << "///put this after const char* UNPARSED_CONFIGS[] = {\n";
+    }
+
+    // get the extracted configuration
+    std::string config = EXTRACTED_CONFIGS[config_counter];
+    std::ostringstream ss;
+    ss << "extracted config #" << config_counter;
+
+    // execute the extracted configuration
+    ASSERT_TRUE(executeConfiguration(config, ss.str().c_str()));
+
+    // unparse it
+    ConstSrvConfigPtr extracted = CfgMgr::instance().getStagingCfg();
+    ConstElementPtr unparsed;
+    ASSERT_NO_THROW(unparsed = extracted->toElement());
+    ConstElementPtr dhcp;
+    ASSERT_NO_THROW(dhcp = unparsed->get("Dhcp6"));
+    ASSERT_TRUE(dhcp);
+
+    // dump if wanted else check
+    std::string expected;
+    if (generate_action) {
+        if (config_counter > 0) {
+            std::cerr << ",\n";
+        }
+        std::cerr << "    // CONFIGURATION " << config_counter;
+        ASSERT_NO_THROW(expected = prettyPrint(dhcp));
+        ASSERT_NO_THROW(outputFormatted(dhcp->str()));
+    } else {
+        expected = UNPARSED_CONFIGS[config_counter];
+        ConstElementPtr json;
+        ASSERT_NO_THROW(json = parseDHCP6(expected, true));
+        EXPECT_TRUE(isEquivalent(dhcp, json));
+        std::string current = prettyPrint(dhcp, 4, 4) + "\n";
+        EXPECT_EQ(expected, current);
+        if (expected != current) {
+            expected = current;
+        }
+    }
+
+    // execute the dhcp configuration
+    ss.str("");
+    ss << "unparsed config #" << config_counter;
+    EXPECT_TRUE(executeConfiguration(expected, ss.str().c_str()));
+
+    // is it a fixed point?
+    ConstSrvConfigPtr extracted2 = CfgMgr::instance().getStagingCfg();
+    ConstElementPtr unparsed2;
+    ASSERT_NO_THROW(unparsed2 = extracted2->toElement());
+    ASSERT_TRUE(unparsed2);
+    EXPECT_TRUE(isEquivalent(unparsed, unparsed2));
+}
+
+/// Define the parametrized test loop
+INSTANTIATE_TEST_CASE_P(Dhcp6GetConfigTest, Dhcp6GetConfigTest,
+                        ::testing::Range(0UL, max_config_counter));
+
+};

+ 27 - 0
src/bin/dhcp6/tests/get_config_unittest.h

@@ -0,0 +1,27 @@
+// Copyright (C) 2017 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 GET_CONFIG_UNITTEST_H
+#define GET_CONFIG_UNITTEST_H
+
+#include <string.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Extract a configuration as a C++ source for JSON on std::cerr
+///
+/// This function should be called when a configuration in an unit test
+/// is interesting and should be extracted. Do nothing when extract_count
+/// is negative.
+void extractConfig(const std::string& config);
+
+};
+};
+};
+
+#endif // GET_CONFIG_UNITTEST_H

+ 49 - 0
src/lib/dhcpsrv/cfg_4o6.cc

@@ -0,0 +1,49 @@
+// Copyright (C) 2017 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 <dhcp/option.h>
+#include <dhcpsrv/cfg_4o6.h>
+#include <string>
+#include <string.h>
+#include <sstream>
+#include <vector>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+ElementPtr
+Cfg4o6::toElement() const {
+    ElementPtr result = Element::createMap();
+    // Set 4o6-interface
+    result->set("4o6-interface", Element::create(iface4o6_));
+    // Set 4o6-subnet
+    if (!subnet4o6_.first.isV6Zero() || (subnet4o6_.second != 128u)) {
+        std::ostringstream oss;
+        oss << subnet4o6_.first << "/"
+            << static_cast<unsigned>(subnet4o6_.second);
+        result->set("4o6-subnet", Element::create(oss.str()));
+    } else {
+        result->set("4o6-subnet", Element::create(std::string()));
+    }
+    // Set 4o6-interface-id
+    if (interface_id_) {
+        std::vector<uint8_t> bin = interface_id_->toBinary();
+        std::string iid;
+        iid.resize(bin.size());
+        if (!bin.empty()) {
+            std::memcpy(&iid[0], &bin[0], bin.size());
+        }
+        result->set("4o6-interface-id", Element::create(iid));
+    } else {
+        result->set("4o6-interface-id", Element::create(std::string()));
+    }
+    return (result);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace

+ 34 - 0
src/lib/testutils/test_to_element.cc

@@ -0,0 +1,34 @@
+// Copyright (C) 2017 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 <testutils/test_to_element.h>
+#include <boost/algorithm/string.hpp>
+#include <gtest/gtest.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace test {
+
+#ifdef HAVE_CREATE_UNIFIED_DIFF
+std::string generateDiff(std::string left, std::string right) {
+    std::vector<std::string> left_lines;
+    boost::split(left_lines, left, boost::is_any_of("\n"));
+    std::vector<std::string> right_lines;
+    boost::split(right_lines, right, boost::is_any_of("\n"));
+    using namespace testing::internal;
+    return (edit_distance::CreateUnifiedDiff(left_lines, right_lines));
+}
+#else
+std::string generateDiff(std::string, std::string) {
+    return ("");
+}
+#endif
+
+}; // end of isc::test namespace
+}; // end of isc namespace

+ 91 - 0
src/lib/testutils/test_to_element.h

@@ -0,0 +1,91 @@
+// Copyright (C) 2017 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 TEST_TO_ELEMENT_H
+#define TEST_TO_ELEMENT_H
+
+#include <cc/data.h>
+#include <cc/cfg_to_element.h>
+#include <gtest/gtest.h>
+#include <string>
+#ifdef HAVE_IS_BASE_OF
+#include <type_traits>
+#endif
+
+#ifndef CONFIG_H_WAS_INCLUDED
+#error config.h must be included before test_to_element.h
+#endif
+
+namespace isc {
+namespace test {
+
+/// @brief Return the difference between two strings
+///
+/// Use the gtest >= 1.8.0 tool which builds the difference between
+/// two vectors of lines.
+///
+/// @param left left string
+/// @param right right string
+/// @return the unified diff between left and right
+std::string generateDiff(std::string left, std::string right);
+
+/// @brief Run a test using toElement() method with a string
+///
+/// @tparam Cfg the class implementing the toElement() method
+/// @param expected the expected textual value
+/// @param cfg an instance of the Cfg class
+template<typename Cfg>
+void runToElementTest(const std::string& expected, const Cfg& cfg) {
+    using namespace isc::data;
+#ifdef HAVE_IS_BASE_OF
+    static_assert(std::is_base_of<CfgToElement, Cfg>::value,
+                  "CfgToElement is not a base of the tenplate parameter");
+#endif
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = Element::fromJSON(expected)) << expected;
+    ConstElementPtr unparsed;
+    ASSERT_NO_THROW(unparsed = cfg.toElement());
+    if (!isEquivalent(json, unparsed)) {
+        std::string wanted = prettyPrint(json);
+        std::string got = prettyPrint(unparsed);
+        ADD_FAILURE() << "Expected:\n" << wanted << "\n"
+                      << "Actual:\n" << got
+#ifdef HAVE_CREATE_UNIFIED_DIFF
+                      << "\nDiff:\n" << generateDiff(wanted, got)
+#endif
+                      << "\n";
+    }
+}
+
+/// @brief Run a test using toElement() method with an Element
+///
+/// @tparam Cfg the class implementing the toElement() method
+/// @param expected the expected element value
+/// @param cfg an instance of the Cfg class
+template<typename Cfg>
+void runToElementTest(isc::data::ConstElementPtr expected, const Cfg& cfg) {
+#ifdef HAVE_IS_BASE_OF
+    static_assert(std::is_base_of<isc::data::CfgToElement, Cfg>::value,
+                  "CfgToElement is not a base of the tenplate parameter");
+#endif
+    isc::data::ConstElementPtr unparsed;
+    ASSERT_NO_THROW(unparsed = cfg.toElement());
+    if (!isEquivalent(expected, unparsed)) {
+        std::string wanted = prettyPrint(expected);
+        std::string got = prettyPrint(unparsed);
+        ADD_FAILURE() << "Expected:\n" << wanted << "\n"
+                      << "Actual:\n" << got
+#ifdef HAVE_CREATE_UNIFIED_DIFF
+                      << "\nDiff:\n" << generateDiff(wanted, got)
+#endif
+                      << "\n";
+    }
+}
+
+}; // end of isc::test namespace
+}; // end of isc namespace
+
+#endif // TEST_TO_ELEMENT_H