Commit df38c26f authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[master] Merge branch 'trac5114' (toElement aka unparse)

# Conflicts:
#	configure.ac
#	src/bin/agent/tests/Makefile.am
parents e45e16b8 86995f8b
......@@ -167,12 +167,11 @@ for retry in "none" "--std=c++11" "--std=c++0x" "--std=c++1x" "fail"; do
feature="final method"
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM(
[],
[class Foo {
public:
virtual ~Foo() {};
virtual void bar() final;
};])],
};],[])],
[AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])
continue])
......@@ -201,6 +200,31 @@ for retry in "none" "--std=c++11" "--std=c++0x" "--std=c++1x" "fail"; do
[AC_MSG_RESULT([no])
continue])
AC_MSG_CHECKING(static_assert support)
feature="static_assert"
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM(
[static_assert(1 + 1 == 2, "");],
[])],
[AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])
continue])
AC_MSG_CHECKING(template alias)
feature="template alias"
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM(
[template<int i>
class I {
public: int get() { return i; };
};
using Zero = I<0>;],
[Zero Z;
return Z.get();])],
[AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])
continue])
AC_MSG_CHECKING(lambda support)
feature="lambda"
AC_COMPILE_IFELSE(
......@@ -213,6 +237,19 @@ for retry in "none" "--std=c++11" "--std=c++0x" "--std=c++1x" "fail"; do
continue])
done
# Check for std::is_base_of support
AC_MSG_CHECKING([for std::is_base_of])
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM(
[#include <type_traits>
class A {};
class B : A {};]
[static_assert(std::is_base_of<A, B>::value, "");])],
[AC_MSG_RESULT(yes)
AC_DEFINE([HAVE_IS_BASE_OF], [1],
[Define to 1 if std::is_base_of is available])],
[AC_MSG_RESULT(no)])
dnl Determine if we are using GNU sed
GNU_SED=no
$SED --version 2> /dev/null | grep GNU > /dev/null 2>&1
......@@ -1407,6 +1444,31 @@ if test $enable_gtest != "no"; then
CPPFLAGS=$CPPFLAGS_SAVED
fi
# Check for CreateUnifiedDiff from gtest >= 1.8.0
if test $enable_gtest != "no"; then
AC_MSG_CHECKING([for CreateUnifiedDiff in $GTEST_INCLUDES/gtest.h])
CPPFLAGS_SAVED=$CPPFLAGS
CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES $GTEST_INCLUDES"
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM(
[#include <boost/algorithm/string.hpp>
#include <gtest/gtest.h>
#include <string>
#include <vector>
std::string nodiff(std::string text) {
std::vector<std::string> lines;
boost::split(lines, text, boost::is_any_of("\n"));
using namespace testing::internal;
return (edit_distance::CreateUnifiedDiff(lines, lines));
}],
[return 0;])],
[AC_MSG_RESULT(yes)
AC_DEFINE([HAVE_CREATE_UNIFIED_DIFF], [1],
[Define to 1 if gtest defines edit_distance::CreateUnifiedDiff])],
[AC_MSG_RESULT(no)])
CPPFLAGS=$CPPFLAGS_SAVED
fi
#
# ASIO: we extensively use it as the C++ event management module.
#
......@@ -1459,11 +1521,11 @@ AC_ARG_ENABLE(generate_parser, [AC_HELP_STRING([--enable-generate-parser],
enable_generate_parser=$enableval, enable_generate_parser=no)
# Check if flex is avaible. Flex is not needed for building Kea sources,
# unless you want to regenerate grammar in src/lib/eval
# unless you want to regenerate grammars
AC_PROG_LEX
# Check if bison is available. Bison is not needed for building Kea sources,
# unless you want to regenerate grammar in src/lib/eval
# unless you want to regenerate grammars
AC_PROG_YACC
if test "x$enable_generate_parser" != "xno"; then
......@@ -1477,7 +1539,7 @@ if test "x$enable_generate_parser" != "xno"; then
fi
# Ok, let's check if we have at least 3.0.0 version of the bison. The code used
# to generate src/lib/eval parser is roughly based on bison 3.0 examples.
# to generate parsers is roughly based on bison 3.0 examples.
cat > bisontest.y << EOF
%require "3.0.0"
%token X
......@@ -1591,23 +1653,33 @@ AM_COND_IF([HAVE_OPTRESET], [AC_DEFINE([HAVE_OPTRESET], [1], [Check for optreset
AC_DEFINE([CONFIG_H_WAS_INCLUDED], [1], [config.h inclusion marker])
AC_CONFIG_FILES([compatcheck/Makefile
AC_CONFIG_FILES([Makefile
compatcheck/Makefile
dns++.pc
doc/design/datasrc/Makefile
doc/Makefile
doc/design/Makefile
doc/design/datasrc/Makefile
doc/guide/Makefile
doc/Makefile
doc/version.ent
ext/Makefile
ext/coroutine/Makefile
ext/gtest/Makefile
ext/Makefile
m4macros/Makefile
Makefile
src/Makefile
src/bin/Makefile
src/bin/admin/Makefile
src/bin/admin/kea-admin
src/bin/admin/tests/Makefile
src/bin/admin/tests/cql_tests.sh
src/bin/admin/tests/data/Makefile
src/bin/admin/tests/memfile_tests.sh
src/bin/admin/tests/mysql_tests.sh
src/bin/admin/tests/pgsql_tests.sh
src/bin/agent/Makefile
src/bin/agent/tests/Makefile
src/bin/agent/tests/ca_process_tests.sh
src/bin/agent/tests/test_data_files_config.h
src/bin/agent/tests/test_libraries.h
src/bin/d2/Makefile
src/bin/d2/tests/Makefile
src/bin/d2/tests/d2_process_tests.sh
......@@ -1636,15 +1708,6 @@ AC_CONFIG_FILES([compatcheck/Makefile
src/bin/perfdhcp/Makefile
src/bin/perfdhcp/tests/Makefile
src/bin/perfdhcp/tests/testdata/Makefile
src/bin/admin/Makefile
src/bin/admin/kea-admin
src/bin/admin/tests/Makefile
src/bin/admin/tests/data/Makefile
src/bin/admin/tests/memfile_tests.sh
src/bin/admin/tests/mysql_tests.sh
src/bin/admin/tests/pgsql_tests.sh
src/bin/admin/tests/cql_tests.sh
src/bin/agent/tests/test_libraries.h
src/bin/shell/Makefile
src/bin/shell/kea-shell
src/bin/shell/tests/Makefile
......@@ -1682,6 +1745,8 @@ AC_CONFIG_FILES([compatcheck/Makefile
src/lib/dns/gen-rdatacode.py
src/lib/dns/tests/Makefile
src/lib/dns/tests/testdata/Makefile
src/lib/eval/Makefile
src/lib/eval/tests/Makefile
src/lib/exceptions/Makefile
src/lib/exceptions/tests/Makefile
src/lib/hooks/Makefile
......@@ -1707,10 +1772,10 @@ AC_CONFIG_FILES([compatcheck/Makefile
src/lib/process/spec_config.h.pre
src/lib/process/tests/Makefile
src/lib/process/testutils/Makefile
src/lib/testutils/Makefile
src/lib/testutils/dhcp_test_lib.sh
src/lib/stats/Makefile
src/lib/stats/tests/Makefile
src/lib/testutils/Makefile
src/lib/testutils/dhcp_test_lib.sh
src/lib/util/Makefile
src/lib/util/io/Makefile
src/lib/util/python/Makefile
......@@ -1720,11 +1785,10 @@ AC_CONFIG_FILES([compatcheck/Makefile
src/lib/util/threads/Makefile
src/lib/util/threads/tests/Makefile
src/lib/util/unittests/Makefile
src/lib/eval/Makefile
src/lib/eval/tests/Makefile
src/share/Makefile
src/share/database/Makefile
src/share/database/scripts/Makefile
src/share/database/scripts/cql/Makefile
src/share/database/scripts/mysql/Makefile
src/share/database/scripts/mysql/upgrade_1.0_to_2.0.sh
src/share/database/scripts/mysql/upgrade_2.0_to_3.0.sh
......@@ -1734,12 +1798,11 @@ AC_CONFIG_FILES([compatcheck/Makefile
src/share/database/scripts/pgsql/Makefile
src/share/database/scripts/pgsql/upgrade_1.0_to_2.0.sh
src/share/database/scripts/pgsql/upgrade_2.0_to_3.0.sh
src/share/database/scripts/cql/Makefile
tools/Makefile
tools/path_replacer.sh
])
AC_CONFIG_COMMANDS([permissions], [
AC_CONFIG_COMMANDS([permissions], [
chmod +x src/bin/admin/kea-admin
chmod +x src/bin/dhcp4/tests/dhcp4_process_tests.sh
chmod +x src/bin/dhcp6/tests/dhcp6_process_tests.sh
......
......@@ -158,6 +158,38 @@ CtrlAgentCfgContext::setControlSocketInfo(const isc::data::ConstElementPtr& cont
ctrl_sockets_[static_cast<uint8_t>(type)] = control_socket;
}
ElementPtr
CtrlAgentCfgContext::toElement() const {
ElementPtr ca = Element::createMap();
// Set http-host
ca->set("http-host", Element::create(http_host_));
// Set http-port
ca->set("http-port", Element::create(static_cast<int64_t>(http_port_)));
// Set hooks-libraries
ca->set("hooks-libraries", hooks_config_.toElement());
// Set control-sockets
ElementPtr control_sockets = Element::createMap();
// Set dhcp4-server
if (ctrl_sockets_[TYPE_DHCP4]) {
control_sockets->set("dhcp4-server", ctrl_sockets_[TYPE_DHCP4]);
}
// Set dhcp6-server
if (ctrl_sockets_[TYPE_DHCP6]) {
control_sockets->set("dhcp6-server", ctrl_sockets_[TYPE_DHCP6]);
}
// Set d2-server
if (ctrl_sockets_[TYPE_D2]) {
control_sockets->set("d2-server", ctrl_sockets_[TYPE_D2]);
}
ca->set("control-sockets", control_sockets);
// Set Control-agent
ElementPtr result = Element::createMap();
result->set("Control-agent", ca);
// Set Logging (not yet)
return (result);
}
} // namespace isc::agent
} // namespace isc
......@@ -116,6 +116,17 @@ public:
return (hooks_config_);
}
/// @brief Unparse a configuration object
///
/// Returns an element which must parse into the same object, i.e.
/// @code
/// for all valid config C parse(parse(C)->toElement()) == parse(C)
/// @endcode
///
/// @return a pointer to a configuration which can be parsed into
/// the initial configuration object
virtual isc::data::ElementPtr toElement() const;
private:
/// @brief Private copy constructor
......
/ca_unittests
/ca_process_tests.sh
/test_data_files_config.h
/test_libraries.h
......@@ -4,7 +4,11 @@ SHTESTS =
SHTESTS += ca_process_tests.sh
noinst_SCRIPTS = ca_process_tests.sh
EXTRA_DIST = ca_process_tests.sh.in
EXTRA_DIST += testdata/get_config.json
noinst_LTLIBRARIES = libbasic.la
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
......@@ -39,8 +43,6 @@ TESTS_ENVIRONMENT = \
TESTS =
if HAVE_GTEST
noinst_LTLIBRARIES = libbasic.la
TESTS += ca_unittests
ca_unittests_SOURCES = ca_cfg_mgr_unittests.cc
......@@ -51,6 +53,7 @@ ca_unittests_SOURCES += ca_response_creator_unittests.cc
ca_unittests_SOURCES += ca_response_creator_factory_unittests.cc
ca_unittests_SOURCES += ca_unittests.cc
ca_unittests_SOURCES += parser_unittests.cc
ca_unittests_SOURCES += get_config_unittest.cc
ca_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
ca_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
......@@ -88,7 +91,7 @@ libbasic_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
libbasic_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
libbasic_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
nodist_ca_unittests_SOURCES = test_libraries.h
nodist_ca_unittests_SOURCES = test_data_files_config.h test_libraries.h
endif
......
// 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));
}
// 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"
{
"Control-agent": {
"control-sockets": {
"d2-server": {
"socket-name": "/path/to/the/unix/socket-d2",
"socket-type": "unix"
},
"dhcp4-server": {
"socket-name": "/path/to/the/unix/socket-v4",
"socket-type": "unix"
},