summaryrefslogtreecommitdiffstats
path: root/src/lib/testutils
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/testutils')
-rw-r--r--src/lib/testutils/Makefile.am30
-rw-r--r--src/lib/testutils/Makefile.in800
-rw-r--r--src/lib/testutils/README2
-rw-r--r--src/lib/testutils/dhcp_test_lib.sh.in1164
-rw-r--r--src/lib/testutils/gtest_utils.h92
-rw-r--r--src/lib/testutils/io_utils.cc117
-rw-r--r--src/lib/testutils/io_utils.h45
-rw-r--r--src/lib/testutils/lib_load_test_fixture.h68
-rw-r--r--src/lib/testutils/log_utils.cc131
-rw-r--r--src/lib/testutils/log_utils.h105
-rw-r--r--src/lib/testutils/multi_threading_utils.h38
-rw-r--r--src/lib/testutils/sandbox.h69
-rw-r--r--src/lib/testutils/test_to_element.cc34
-rw-r--r--src/lib/testutils/test_to_element.h91
-rw-r--r--src/lib/testutils/threaded_test.cc73
-rw-r--r--src/lib/testutils/threaded_test.h88
-rw-r--r--src/lib/testutils/unix_control_client.cc139
-rw-r--r--src/lib/testutils/unix_control_client.h66
-rw-r--r--src/lib/testutils/user_context_utils.cc137
-rw-r--r--src/lib/testutils/user_context_utils.h37
-rw-r--r--src/lib/testutils/xml_reporting_test_lib.sh.in353
21 files changed, 3679 insertions, 0 deletions
diff --git a/src/lib/testutils/Makefile.am b/src/lib/testutils/Makefile.am
new file mode 100644
index 0000000..03d78fb
--- /dev/null
+++ b/src/lib/testutils/Makefile.am
@@ -0,0 +1,30 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS=$(KEA_CXXFLAGS)
+
+noinst_SCRIPTS = dhcp_test_lib.sh xml_reporting_test_lib.sh
+
+if HAVE_GTEST
+noinst_LTLIBRARIES = libkea-testutils.la
+
+libkea_testutils_la_SOURCES = io_utils.cc io_utils.h
+libkea_testutils_la_SOURCES += sandbox.h
+libkea_testutils_la_SOURCES += log_utils.cc log_utils.h
+libkea_testutils_la_SOURCES += test_to_element.cc test_to_element.h
+libkea_testutils_la_SOURCES += threaded_test.cc threaded_test.h
+libkea_testutils_la_SOURCES += unix_control_client.cc unix_control_client.h
+libkea_testutils_la_SOURCES += user_context_utils.cc user_context_utils.h
+libkea_testutils_la_SOURCES += gtest_utils.h
+libkea_testutils_la_SOURCES += multi_threading_utils.h
+libkea_testutils_la_SOURCES += lib_load_test_fixture.h
+libkea_testutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libkea_testutils_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_testutils_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+endif
+
+# Include common libraries being used by shell-based tests.
+SHLIBS = dhcp_test_lib.sh.in xml_reporting_test_lib.sh.in
+
+EXTRA_DIST = $(SHLIBS)
+
+CLEANFILES = dhcp_test_lib.sh xml_reporting_test_lib.sh
diff --git a/src/lib/testutils/Makefile.in b/src/lib/testutils/Makefile.in
new file mode 100644
index 0000000..3196374
--- /dev/null
+++ b/src/lib/testutils/Makefile.in
@@ -0,0 +1,800 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib/testutils
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = dhcp_test_lib.sh xml_reporting_test_lib.sh
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+@HAVE_GTEST_TRUE@libkea_testutils_la_DEPENDENCIES = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la
+am__libkea_testutils_la_SOURCES_DIST = io_utils.cc io_utils.h \
+ sandbox.h log_utils.cc log_utils.h test_to_element.cc \
+ test_to_element.h threaded_test.cc threaded_test.h \
+ unix_control_client.cc unix_control_client.h \
+ user_context_utils.cc user_context_utils.h gtest_utils.h \
+ multi_threading_utils.h lib_load_test_fixture.h
+@HAVE_GTEST_TRUE@am_libkea_testutils_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libkea_testutils_la-io_utils.lo \
+@HAVE_GTEST_TRUE@ libkea_testutils_la-log_utils.lo \
+@HAVE_GTEST_TRUE@ libkea_testutils_la-test_to_element.lo \
+@HAVE_GTEST_TRUE@ libkea_testutils_la-threaded_test.lo \
+@HAVE_GTEST_TRUE@ libkea_testutils_la-unix_control_client.lo \
+@HAVE_GTEST_TRUE@ libkea_testutils_la-user_context_utils.lo
+libkea_testutils_la_OBJECTS = $(am_libkea_testutils_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+@HAVE_GTEST_TRUE@am_libkea_testutils_la_rpath =
+SCRIPTS = $(noinst_SCRIPTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/libkea_testutils_la-io_utils.Plo \
+ ./$(DEPDIR)/libkea_testutils_la-log_utils.Plo \
+ ./$(DEPDIR)/libkea_testutils_la-test_to_element.Plo \
+ ./$(DEPDIR)/libkea_testutils_la-threaded_test.Plo \
+ ./$(DEPDIR)/libkea_testutils_la-unix_control_client.Plo \
+ ./$(DEPDIR)/libkea_testutils_la-user_context_utils.Plo
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libkea_testutils_la_SOURCES)
+DIST_SOURCES = $(am__libkea_testutils_la_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/dhcp_test_lib.sh.in \
+ $(srcdir)/xml_reporting_test_lib.sh.in $(top_srcdir)/depcomp \
+ README
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+noinst_SCRIPTS = dhcp_test_lib.sh xml_reporting_test_lib.sh
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libkea-testutils.la
+@HAVE_GTEST_TRUE@libkea_testutils_la_SOURCES = io_utils.cc io_utils.h \
+@HAVE_GTEST_TRUE@ sandbox.h log_utils.cc log_utils.h \
+@HAVE_GTEST_TRUE@ test_to_element.cc test_to_element.h \
+@HAVE_GTEST_TRUE@ threaded_test.cc threaded_test.h \
+@HAVE_GTEST_TRUE@ unix_control_client.cc unix_control_client.h \
+@HAVE_GTEST_TRUE@ user_context_utils.cc user_context_utils.h \
+@HAVE_GTEST_TRUE@ gtest_utils.h multi_threading_utils.h \
+@HAVE_GTEST_TRUE@ lib_load_test_fixture.h
+@HAVE_GTEST_TRUE@libkea_testutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libkea_testutils_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la
+
+# Include common libraries being used by shell-based tests.
+SHLIBS = dhcp_test_lib.sh.in xml_reporting_test_lib.sh.in
+EXTRA_DIST = $(SHLIBS)
+CLEANFILES = dhcp_test_lib.sh xml_reporting_test_lib.sh
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/testutils/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/testutils/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+dhcp_test_lib.sh: $(top_builddir)/config.status $(srcdir)/dhcp_test_lib.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+xml_reporting_test_lib.sh: $(top_builddir)/config.status $(srcdir)/xml_reporting_test_lib.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libkea-testutils.la: $(libkea_testutils_la_OBJECTS) $(libkea_testutils_la_DEPENDENCIES) $(EXTRA_libkea_testutils_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(CXXLINK) $(am_libkea_testutils_la_rpath) $(libkea_testutils_la_OBJECTS) $(libkea_testutils_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_testutils_la-io_utils.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_testutils_la-log_utils.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_testutils_la-test_to_element.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_testutils_la-threaded_test.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_testutils_la-unix_control_client.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_testutils_la-user_context_utils.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+libkea_testutils_la-io_utils.lo: io_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_testutils_la-io_utils.lo -MD -MP -MF $(DEPDIR)/libkea_testutils_la-io_utils.Tpo -c -o libkea_testutils_la-io_utils.lo `test -f 'io_utils.cc' || echo '$(srcdir)/'`io_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_testutils_la-io_utils.Tpo $(DEPDIR)/libkea_testutils_la-io_utils.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_utils.cc' object='libkea_testutils_la-io_utils.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_testutils_la-io_utils.lo `test -f 'io_utils.cc' || echo '$(srcdir)/'`io_utils.cc
+
+libkea_testutils_la-log_utils.lo: log_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_testutils_la-log_utils.lo -MD -MP -MF $(DEPDIR)/libkea_testutils_la-log_utils.Tpo -c -o libkea_testutils_la-log_utils.lo `test -f 'log_utils.cc' || echo '$(srcdir)/'`log_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_testutils_la-log_utils.Tpo $(DEPDIR)/libkea_testutils_la-log_utils.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='log_utils.cc' object='libkea_testutils_la-log_utils.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_testutils_la-log_utils.lo `test -f 'log_utils.cc' || echo '$(srcdir)/'`log_utils.cc
+
+libkea_testutils_la-test_to_element.lo: test_to_element.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_testutils_la-test_to_element.lo -MD -MP -MF $(DEPDIR)/libkea_testutils_la-test_to_element.Tpo -c -o libkea_testutils_la-test_to_element.lo `test -f 'test_to_element.cc' || echo '$(srcdir)/'`test_to_element.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_testutils_la-test_to_element.Tpo $(DEPDIR)/libkea_testutils_la-test_to_element.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_to_element.cc' object='libkea_testutils_la-test_to_element.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_testutils_la-test_to_element.lo `test -f 'test_to_element.cc' || echo '$(srcdir)/'`test_to_element.cc
+
+libkea_testutils_la-threaded_test.lo: threaded_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_testutils_la-threaded_test.lo -MD -MP -MF $(DEPDIR)/libkea_testutils_la-threaded_test.Tpo -c -o libkea_testutils_la-threaded_test.lo `test -f 'threaded_test.cc' || echo '$(srcdir)/'`threaded_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_testutils_la-threaded_test.Tpo $(DEPDIR)/libkea_testutils_la-threaded_test.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='threaded_test.cc' object='libkea_testutils_la-threaded_test.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_testutils_la-threaded_test.lo `test -f 'threaded_test.cc' || echo '$(srcdir)/'`threaded_test.cc
+
+libkea_testutils_la-unix_control_client.lo: unix_control_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_testutils_la-unix_control_client.lo -MD -MP -MF $(DEPDIR)/libkea_testutils_la-unix_control_client.Tpo -c -o libkea_testutils_la-unix_control_client.lo `test -f 'unix_control_client.cc' || echo '$(srcdir)/'`unix_control_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_testutils_la-unix_control_client.Tpo $(DEPDIR)/libkea_testutils_la-unix_control_client.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='unix_control_client.cc' object='libkea_testutils_la-unix_control_client.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_testutils_la-unix_control_client.lo `test -f 'unix_control_client.cc' || echo '$(srcdir)/'`unix_control_client.cc
+
+libkea_testutils_la-user_context_utils.lo: user_context_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_testutils_la-user_context_utils.lo -MD -MP -MF $(DEPDIR)/libkea_testutils_la-user_context_utils.Tpo -c -o libkea_testutils_la-user_context_utils.lo `test -f 'user_context_utils.cc' || echo '$(srcdir)/'`user_context_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_testutils_la-user_context_utils.Tpo $(DEPDIR)/libkea_testutils_la-user_context_utils.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='user_context_utils.cc' object='libkea_testutils_la-user_context_utils.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_testutils_la-user_context_utils.lo `test -f 'user_context_utils.cc' || echo '$(srcdir)/'`user_context_utils.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(SCRIPTS)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-io_utils.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-log_utils.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-test_to_element.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-threaded_test.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-unix_control_client.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-user_context_utils.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-io_utils.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-log_utils.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-test_to_element.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-threaded_test.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-unix_control_client.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-user_context_utils.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/testutils/README b/src/lib/testutils/README
new file mode 100644
index 0000000..126f1b8
--- /dev/null
+++ b/src/lib/testutils/README
@@ -0,0 +1,2 @@
+Here is some code used by more than one test. No code is used for Kea
+itself, only for testing.
diff --git a/src/lib/testutils/dhcp_test_lib.sh.in b/src/lib/testutils/dhcp_test_lib.sh.in
new file mode 100644
index 0000000..d2c7ae8
--- /dev/null
+++ b/src/lib/testutils/dhcp_test_lib.sh.in
@@ -0,0 +1,1164 @@
+#!/bin/sh
+
+# Copyright (C) 2014-2021 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+
+# shellcheck disable=SC2039
+# SC2039: In POSIX sh, 'local' is undefined.
+
+# shellcheck disable=SC2153
+# SC2153: Possible misspelling: ... may not be assigned, but ... is.
+
+# shellcheck disable=SC2154
+# SC2154: bin_path is referenced but not assigned.
+
+# shellcheck disable=SC3043
+# SC3043: In POSIX sh, 'local' is undefined.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Include XML reporting library.
+. "@abs_top_builddir@/src/lib/testutils/xml_reporting_test_lib.sh"
+
+prefix="@prefix@"
+
+# Expected version
+EXPECTED_VERSION="@PACKAGE_VERSION@"
+
+# Kea environment variables for shell tests.
+# KEA_LOGGER_DESTINATION is set per test with set_logger.
+export KEA_LFC_EXECUTABLE="@abs_top_builddir@/src/bin/lfc/kea-lfc"
+export KEA_LOCKFILE_DIR="@abs_top_builddir@/test_lockfile_dir"
+export KEA_PIDFILE_DIR="@abs_top_builddir@/test_pidfile_dir"
+
+# A list of Kea processes, mainly used by the cleanup functions.
+KEA_PROCS="kea-dhcp4 kea-dhcp6 kea-dhcp-ddns kea-ctrl-agent"
+
+### Colors ###
+
+if test -t 1; then
+ green='\033[92m'
+ red='\033[91m'
+ reset='\033[0m'
+fi
+
+### Logging functions ###
+
+# Prints error message.
+test_lib_error() {
+ local s="${1-}" # Error message.
+ local no_new_line="${2-}" # If specified, the message is not terminated
+ # with new line.
+ printf "ERROR/test_lib: %s" "${s}"
+ if [ -z "${no_new_line}" ]; then
+ printf '\n'
+ fi
+}
+
+# Prints info message.
+test_lib_info() {
+ local s="${1-}" # Info message.
+ local no_new_line="${2-}" # If specified, the message is not terminated
+ # with new line.
+ printf "INFO/test_lib: %s" "${s}"
+ if [ -z "${no_new_line}" ]; then
+ printf '\n'
+ fi
+}
+
+### Assertions ###
+
+# Assertion that checks if two numbers are equal.
+# If numbers are not equal, the mismatched values are presented and the
+# detailed error is printed. The detailed error must use the printf
+# formatting like this:
+# "Expected that some value 1 %d is equal to some other value %d".
+assert_eq() {
+ val1=${1} # Reference value
+ val2=${2} # Tested value
+ detailed_err=${3-} # Optional detailed error format string
+ # If nothing found, present an error an exit.
+ if [ "${val1}" -ne "${val2}" ]; then
+ printf 'Assertion failure: %s != %s, expected %s, got %s\n' \
+ "${val1}" "${val2}" "${val1}" "${val2}"
+ # shellcheck disable=SC2059
+ # SC2059: Don't use variables in the printf format string. Use printf '..%s..' "$foo"
+ ERROR=$(printf "${detailed_err}" "${val1}" "${val2}")
+ printf '%s\n%s\n' "${ERROR}" "${OUTPUT}" >&2
+ clean_exit 1
+ fi
+}
+
+# Assertion that checks that two strings are equal.
+# If strings are not equal, the mismatched values are presented and the
+# detailed error is printed. The detailed error must use the printf
+# formatting like this:
+# "Expected that some value 1 %d is equal to some other value %d".
+assert_str_eq() {
+ val1=${1} # Reference value
+ val2=${2} # Tested value
+ detailed_err=${3-} # Optional detailed error format string
+ # If nothing found, present an error an exit.
+ if [ "${val1}" != "${val2}" ]; then
+ printf 'Assertion failure: %s != %s, expected "%s", got "%s"\n' \
+ "${val1}" "${val2}" "${val1}" "${val2}"
+ # shellcheck disable=SC2059
+ # SC2059: SC2059: Don't use variables in the printf format string. Use printf '..%s..' "$foo".
+ ERROR=$(printf "${detailed_err}" "${val1}" "${val2}")
+ printf '%s\n%s\n' "${ERROR}" "${OUTPUT}" >&2
+ clean_exit 1
+ fi
+}
+
+# Assertion that checks that two strings are NOT equal.
+# If strings are equal, the mismatched values are presented and the
+# optional detailed error, if any, is printed.
+assert_str_neq() {
+ reference=${1} # Reference value
+ tested=${2} # Tested value
+ detailed_error=${3-} # Optional detailed error format string
+ if test "${reference}" = "${tested}"; then
+ printf 'Assertion failure: expected different strings, but '
+ printf 'both variables have the value "%s".\n' "${reference}"
+ printf '%s\n%s\n' "${detailed_error}" "${OUTPUT}" >&2
+ clean_exit 1
+ fi
+}
+
+# Assertion that checks if one string contains another string.
+# If assertion fails, both strings are displayed and the detailed
+# error is printed. The detailed error must use the printf formatting
+# like this:
+# "Expected some string to contain this string: %s".
+assert_string_contains() {
+ pattern="${1}" # Substring or awk pattern
+ text="${2}" # Text to be searched for substring
+ detailed_err="${3}" # Detailed error format string
+ # Search for a pattern
+ match=$( printf "%s" "${text}" | awk /"${pattern}"/ )
+ # If nothing found, present an error and exit.
+ if [ -z "${match}" ]; then
+ ERROR=$(printf \
+"Assertion failure:
+\"%s\"
+
+does not contain pattern:
+\"%s\"
+
+${detailed_err}
+" "${text}" "${pattern}" "${pattern}")
+ printf '%s\n%s\n' "${ERROR}" "${OUTPUT}" >&2
+ clean_exit 1
+ fi
+}
+
+# Runs all the given arguments as a single command. Maintains quoting. Places
+# output in ${OUTPUT} and exit code in ${EXIT_CODE}. Does not support pipes and
+# redirections. Support for them could be added through eval and single
+# parameter assignment, but eval is not recommended.
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+run_command() {
+ if test -n "${DEBUG+x}"; then
+ printf '%s\n' "${*}" >&2
+ fi
+ set +e
+ OUTPUT=$("${@}")
+ EXIT_CODE=${?}
+ set -e
+}
+
+# Enable traps to print FAILED status when a command fails unexpectedly or when
+# the user sends a SIGINT. Used in `test_start`.
+traps_on() {
+ for t in HUP INT QUIT KILL TERM EXIT; do
+ # shellcheck disable=SC2064
+ # SC2064: Use single quotes, otherwise this expands now rather than when signalled.
+ # reason: we want ${red-} and ${reset-} to expand here, at trap-time
+ # they will be empty or have other values
+ trap "
+ exit_code=\${?}
+ printf '${red-}[ FAILED ]${reset-} %s (exit code: %d)\n' \
+ \"\${TEST_NAME}\" \"\${exit_code}\"
+ " "${t}"
+ done
+}
+
+# Disable traps so that a double status is not printed. Used in `test_finish`
+# after the status has been printed explicitly.
+traps_off() {
+ for t in HUP INT QUIT KILL TERM EXIT; do
+ trap - "${t}"
+ done
+}
+
+# Print UNIX time with millisecond resolution.
+get_current_time() {
+ local time
+ time=$(date +%s%3N)
+
+ # In some systems, particularly BSD-based, `+%3N` millisecond resolution is
+ # not supported. It instead prints the literal '3N', but we check for any
+ # alphabetical character. If we do find one, revert to second resolution and
+ # convert to milliseconds.
+ if printf '%s' "${time}" | grep -E '[A-Za-z]' > /dev/null 2>&1; then
+ time=$(date +%s)
+ time=$((1000 * time))
+ fi
+
+ printf '%s' "${time}"
+}
+
+# Begins a test by printing its name.
+test_start() {
+ TEST_NAME=${1-}
+ if [ -z "${TEST_NAME}" ]; then
+ test_lib_error "test_start requires test name as an argument"
+ clean_exit 1
+ fi
+
+ # Set traps first to fail if something goes wrong.
+ traps_on
+
+ # Announce test start.
+ printf "${green-}[ RUN ]${reset-} %s\n" "${TEST_NAME}"
+
+ # Remove dangling Kea instances and remove log files.
+ cleanup
+
+ # Make sure lockfile and pidfile directories exist. They are used in some
+ # tests.
+ mkdir -p "${KEA_LOCKFILE_DIR}"
+ # There are certain tests that intentionally run without a KEA_PIDFILE_DIR
+ # e.g. keactrl.status_test. Only create the directory if we test requires
+ # one.
+ if test -n "${KEA_PIDFILE_DIR+x}"; then
+ mkdir -p "${KEA_PIDFILE_DIR}"
+ fi
+
+ # Start timer in milliseconds.
+ START_TIME=$(get_current_time)
+}
+
+# Prints test result an cleans up after the test.
+test_finish() {
+ # Exit code to be returned by the exit function
+ local exit_code="${1}"
+
+ # Stop timer and set duration.
+ FINISH_TIME=$(get_current_time)
+ local duration
+ duration=$((FINISH_TIME - START_TIME))
+
+ # Add the test result to the XML.
+ report_test_result_in_xml "${TEST_NAME}" "${exit_code}" "${duration}"
+
+ if [ "${exit_code}" -eq 0 ]; then
+ printf "${green-}[ OK ]${reset-} %s\n" "${TEST_NAME}"
+ else
+ # Dump log file for debugging purposes if specified and exists.
+ # Otherwise the code below would simply call cat.
+ # Use ${var+x} to test if ${var} is defined.
+ if test -n "${LOG_FILE+x}" && test -s "${LOG_FILE}"; then
+ printf 'Log file dump:\n'
+ cat "${LOG_FILE}"
+ fi
+ printf "${red-}[ FAILED ]${reset-} %s\n" "${TEST_NAME}"
+ fi
+
+ # Remove dangling Kea instances and log files.
+ cleanup
+
+ # Reset traps.
+ traps_off
+
+ # Explicitly return ${exit_code}. The effect should be for `make check` to
+ # return with the exit same code or at least another non-zero exit code thus
+ # reporting a failure.
+ return "${exit_code}"
+}
+
+# Stores the configuration specified as a parameter in the configuration
+# file which name has been set in the ${CFG_FILE} variable.
+create_config() {
+ local cfg="${1-}" # Configuration string.
+ if [ -z "${CFG_FILE+x}" ]; then
+ test_lib_error "create_config requires CFG_FILE variable be set"
+ clean_exit 1
+
+ elif [ -z "${cfg}" ]; then
+ test_lib_error "create_config requires argument holding a configuration"
+ clean_exit 1
+ fi
+ printf 'Creating Kea configuration file: %s.\n' "${CFG_FILE}"
+ printf '%b' "${cfg}" > "${CFG_FILE}"
+}
+
+# Stores the DHCP4 configuration specified as a parameter in the
+# configuration file which name has been set in the ${DHCP4_CFG_FILE}
+# variable.
+create_dhcp4_config() {
+ local cfg="${1-}" # Configuration string.
+ if [ -z "${DHCP4_CFG_FILE+x}" ]; then
+ test_lib_error "create_dhcp4_config requires DHCP4_CFG_FILE \
+variable be set"
+ clean_exit 1
+
+ elif [ -z "${cfg}" ]; then
+ test_lib_error "create_dhcp4_config requires argument holding a \
+configuration"
+ clean_exit 1
+ fi
+ printf 'Creating Dhcp4 configuration file: %s.\n' "${DHCP4_CFG_FILE}"
+ printf '%b' "${cfg}" > "${DHCP4_CFG_FILE}"
+}
+
+# Stores the DHCP6 configuration specified as a parameter in the
+# configuration file which name has been set in the ${DHCP6_CFG_FILE}
+# variable.
+create_dhcp6_config() {
+ local cfg="${1-}" # Configuration string.
+ if [ -z "${DHCP6_CFG_FILE+x}" ]; then
+ test_lib_error "create_dhcp6_config requires DHCP6_CFG_FILE \
+variable be set"
+ clean_exit 1
+
+ elif [ -z "${cfg}" ]; then
+ test_lib_error "create_dhcp6_config requires argument holding a \
+configuration"
+ clean_exit 1
+ fi
+ printf 'Creating Dhcp6 configuration file: %s.\n' "${DHCP6_CFG_FILE}"
+ printf '%b' "${cfg}" > "${DHCP6_CFG_FILE}"
+}
+
+# Stores the D2 configuration specified as a parameter in the
+# configuration file which name has been set in the ${D2_CFG_FILE}
+# variable.
+create_d2_config() {
+ local cfg="${1-}" # Configuration string.
+ if [ -z "${D2_CFG_FILE+x}" ]; then
+ test_lib_error "create_d2_config requires D2_CFG_FILE \
+variable be set"
+ clean_exit 1
+
+ elif [ -z "${cfg}" ]; then
+ test_lib_error "create_d2_config requires argument holding a \
+configuration"
+ clean_exit 1
+ fi
+ printf 'Creating D2 configuration file: %s.\n' "${D2_CFG_FILE}"
+ printf '%b' "${cfg}" > "${D2_CFG_FILE}"
+}
+
+# Stores the CA configuration specified as a parameter in the
+# configuration file which name has been set in the ${CA_CFG_FILE}
+# variable.
+create_ca_config() {
+ local cfg="${1-}" # Configuration string.
+ if [ -z "${CA_CFG_FILE+x}" ]; then
+ test_lib_error "create_ca_config requires CA_CFG_FILE \
+variable be set"
+ clean_exit 1
+
+ elif [ -z "${cfg}" ]; then
+ test_lib_error "create_ca_config requires argument holding a \
+configuration"
+ clean_exit 1
+ fi
+ printf 'Creating Ca configuration file: %s.\n' "${CA_CFG_FILE}"
+ printf '%b' "${cfg}" > "${CA_CFG_FILE}"
+}
+
+# Stores the NC configuration specified as a parameter in the
+# configuration file which name has been set in the ${NC_CFG_FILE}
+# variable.
+create_nc_config() {
+ local cfg="${1-}" # Configuration string.
+ if [ -z "${NC_CFG_FILE+x}" ]; then
+ test_lib_error "create_nc_config requires NC_CFG_FILE \
+variable be set"
+ clean_exit 1
+
+ elif [ -z "${cfg}" ]; then
+ test_lib_error "create_nc_config requires argument holding a \
+configuration"
+ clean_exit 1
+ fi
+ printf 'Creating Nc configuration file: %s.\n' "${NC_CFG_FILE}"
+ printf '%b' "${cfg}" > "${NC_CFG_FILE}"
+}
+
+# Stores the keactrl configuration specified as a parameter in the
+# configuration file which name has been set in the ${KEACTRL_CFG_FILE}
+# variable.
+create_keactrl_config() {
+ local cfg="${1-}" # Configuration string.
+ if [ -z "${KEACTRL_CFG_FILE+x}" ]; then
+ test_lib_error "create_keactrl_config requires KEACTRL_CFG_FILE \
+variable be set"
+ clean_exit 1
+
+ elif [ -z "${cfg}" ]; then
+ test_lib_error "create_keactrl_config requires argument holding a \
+configuration"
+ clean_exit 1
+ fi
+ printf 'Creating keactrl configuration file: %s.\n' "${KEACTRL_CFG_FILE}"
+ printf '%b' "${cfg}" > "${KEACTRL_CFG_FILE}"
+}
+
+# Sets Kea logger to write to the file specified by the global value
+# ${LOG_FILE}.
+set_logger() {
+ if [ -z "${LOG_FILE+x}" ]; then
+ test_lib_error "set_logger requires LOG_FILE variable be set"
+ clean_exit 1
+ fi
+ printf 'Kea log will be stored in %s.\n' "${LOG_FILE}"
+ export KEA_LOGGER_DESTINATION=${LOG_FILE}
+}
+
+# Checks if specified process is running.
+#
+# This function uses PID file to obtain the PID and then calls
+# 'kill -0 <pid>' to check if the process is alive.
+# The PID files are expected to be located in the ${KEA_PIDFILE_DIR},
+# and their names should match the following pattern:
+# <cfg_file_name>.<proc_name>.pid. If the <cfg_file_name> is not
+# specified a 'test_config' is used by default.
+#
+# Return value:
+# _GET_PID: holds a PID if process is running
+# _GET_PIDS_NUM: holds 1 if process is running, 0 otherwise
+get_pid() {
+ local proc_name="${1-}" # Process name
+ local cfg_file_name="${2-}" # Configuration file name without extension.
+
+ # Reset PID results.
+ _GET_PID=0
+ _GET_PIDS_NUM=0
+
+ # PID file name includes process name. The process name is required.
+ if [ -z "${proc_name}" ]; then
+ test_lib_error "get_pid requires process name"
+ clean_exit 1
+ fi
+
+ # There are certain tests that intentionally run without a KEA_PIDFILE_DIR
+ # e.g. keactrl.status_test. We can't get the PID if KEA_PIDFILE_DIR is not
+ # defined. In this case, this function is reporting process not running
+ # (_GET_PID == 0).
+ if test -z "${KEA_PIDFILE_DIR+x}"; then
+ return
+ fi
+
+ # PID file name includes server configuration file name. For most of
+ # the tests it is 'test-config' (excluding .json extension). It is
+ # possible to specify custom name if required.
+ if [ -z "${cfg_file_name}" ]; then
+ cfg_file_name="test_config"
+ fi
+
+ # Get the absolute location of the PID file for the specified process
+ # name.
+ abs_pidfile_path="${KEA_PIDFILE_DIR}/${cfg_file_name}.${proc_name}.pid"
+
+ # If the PID file exists, get the PID and see if the process is alive.
+ pid=$(cat "${abs_pidfile_path}" 2> /dev/null || true)
+ if test -n "${pid}"; then
+ if kill -0 "${pid}" > /dev/null 2>&1; then
+ _GET_PID=${pid}
+ _GET_PIDS_NUM=1
+ fi
+ fi
+}
+
+# Get the name of the process identified by PID.
+get_process_name() {
+ local pid="${1-}"
+ if test -z "${pid}"; then
+ test_lib_error 'expected PID parameter in get_process_name'
+ clean_exit 1
+ fi
+
+ ps "${pid}" | tr -s ' ' | cut -d ' ' -f 6- | head -n 2 | tail -n 1
+}
+
+# Wait for file to be created.
+wait_for_file() {
+ local file="${1-}"
+ if test -z "${file}"; then
+ test_lib_error 'expected file parameter in wait_for_file'
+ clean_exit 1
+ fi
+
+ local timeout='4' # seconds
+ local deadline="$(($(date +%s) + timeout))"
+ while ! test -f "${file}"; do
+ if test "${deadline}" -lt "$(date +%s)"; then
+ # Time is up.
+ printf 'ERROR: file "%s" was not created in time.\n' "${file}" >&2
+ return 1
+ fi
+ printf 'Waiting for file "%s" to be created...\n' "${file}"
+ sleep 1
+ done
+}
+
+# Wait for process identified by PID to die.
+wait_for_process_to_stop() {
+ local pid="${1-}"
+ if test -z "${pid}"; then
+ test_lib_error 'expected PID parameter in wait_for_process_to_stop'
+ clean_exit 1
+ fi
+
+ local timeout='4' # seconds
+ local deadline="$(($(date +%s) + timeout))"
+ while ps "${pid}" >/dev/null; do
+ if test "${deadline}" -lt "$(date +%s)"; then
+ # Time is up.
+ printf 'ERROR: %s is not stopping.\n' "$(get_process_name "${pid}")" >&2
+ return 1
+ fi
+ printf 'Waiting for %s to stop...\n' "$(get_process_name "${pid}")"
+ sleep 1
+ done
+}
+
+# Kills processes specified by name.
+#
+# This function kills all processes having a specified name.
+# It uses 'pgrep' to obtain pids of those processes.
+# This function should be used when identifying process by
+# the value in its PID file is not relevant.
+#
+# Linux limitation for pgrep: The process name used for matching is
+# limited to the 15 characters. If you call this with long process
+# names, add this before pgrep:
+# proc_name=$(printf '%s' "${proc_name}" | cut -c1-15)
+kill_processes_by_name() {
+ local proc_name="${1-}" # Process name
+ if [ -z "${proc_name}" ]; then
+ test_lib_error "kill_processes_by_name requires process name"
+ clean_exit 1
+ fi
+
+ # Obtain PIDs of running processes.
+ local pids
+ pids=$(pgrep "${proc_name}" || true)
+ # For each PID found, send kill signal.
+ for pid in ${pids}; do
+ printf 'Shutting down Kea process %s with PID %d...\n' "${proc_name}" "${pid}"
+ kill -9 "${pid}" || true
+ done
+
+ # Wait for all processes to stop.
+ for pid in ${pids}; do
+ printf 'Waiting for Kea process %s with PID %d to stop...\n' "${proc_name}" "${pid}"
+ wait_for_process_to_stop "${pid}"
+ done
+}
+
+# Returns the number of occurrences of the Kea log message in the log file.
+# Return value:
+# _GET_LOG_MESSAGES: number of log message occurrences.
+get_log_messages() {
+ local msg="${1}" # Message id, e.g. DHCP6_SHUTDOWN
+ if [ -z "${msg}" ]; then
+ test_lib_error "get_log_messages require message identifier"
+ clean_exit 1
+ fi
+ _GET_LOG_MESSAGES=0
+ # If log file is not present, the number of occurrences is 0.
+ # Use ${var+x} to test if ${var} is defined.
+ if test -n "${LOG_FILE+x}" && test -s "${LOG_FILE}"; then
+ # Grep log file for the logger message occurrences and remove
+ # whitespaces, if any.
+ _GET_LOG_MESSAGES=$(grep -Fo "${msg}" "${LOG_FILE}" | wc -w | tr -d " ")
+ fi
+}
+
+# Returns the number of server configurations performed so far. Also
+# returns the number of configuration errors.
+# Return values:
+# _GET_RECONFIGS: number of configurations so far.
+# _GET_RECONFIG_ERRORS: number of configuration errors.
+get_reconfigs() {
+ # Grep log file for CONFIG_COMPLETE occurrences. There should
+ # be one occurrence per (re)configuration.
+ _GET_RECONFIGS=$(grep -Fo CONFIG_COMPLETE "${LOG_FILE}" | wc -w)
+ # Grep log file for CONFIG_LOAD_FAIL to check for configuration
+ # failures.
+ _GET_RECONFIG_ERRORS=$(grep -Fo CONFIG_LOAD_FAIL "${LOG_FILE}" | wc -w)
+ # Remove whitespaces
+ ${_GET_RECONFIGS##*[! ]}
+ ${_GET_RECONFIG_ERRORS##*[! ]}
+}
+
+# Remove the given directories or files if they exist.
+remove_if_exists() {
+ while test ${#} -gt 0; do
+ if test -e "${1}"; then
+ rm -rf "${1}"
+ fi
+ shift
+ done
+}
+
+# Performs cleanup after test.
+# It shuts down running Kea processes and removes temporary files.
+# The location of the log file and the configuration files should be set
+# in the ${LOG_FILE}, ${CFG_FILE} and ${KEACTRL_CFG_FILE} variables
+# respectively, prior to calling this function.
+cleanup() {
+ # If there is no KEA_PROCS set, just return
+ if [ -z "${KEA_PROCS}" ]; then
+ return
+ fi
+
+ # KEA_PROCS holds the name of all Kea processes. Shut down each
+ # of them if running.
+ for proc_name in ${KEA_PROCS}
+ do
+ get_pid "${proc_name}"
+ # Shut down running Kea process.
+ if [ "${_GET_PIDS_NUM}" -ne 0 ]; then
+ printf 'Shutting down Kea process having pid %d.\n' "${_GET_PID}"
+ kill -9 "${_GET_PID}"
+ fi
+ done
+
+ # Kill any running LFC processes. Even though 'kea-lfc' creates PID
+ # file we rather want to use 'pgrep' to find the process PID, because
+ # kea-lfc execution is not controlled from the test and thus there
+ # is possibility that process is already/still running but the PID
+ # file doesn't exist for it. As a result, the process will not
+ # be killed. This is not a problem for other processes because
+ # tests control launching them and monitor when they are shut down.
+ kill_processes_by_name "kea-lfc"
+
+ # Remove temporary files.
+ remove_if_exists \
+ "${CA_CFG_FILE-}" \
+ "${CFG_FILE-}" \
+ "${D2_CFG_FILE-}" \
+ "${DHCP4_CFG_FILE-}" \
+ "${DHCP6_CFG_FILE-}" \
+ "${KEACTRL_CFG_FILE-}" \
+ "${KEA_LOCKFILE_DIR-}" \
+ "${KEA_PIDFILE_DIR-}" \
+ "${NC_CFG_FILE-}"
+
+ # Use ${var+x} to test if ${var} is defined.
+ if test -n "${LOG_FILE+x}" && test -n "${LOG_FILE}"; then
+ rm -rf "${LOG_FILE}"
+ rm -rf "${LOG_FILE}.lock"
+ fi
+ # Use asterisk to remove all files starting with the given name,
+ # in case the LFC has been run. LFC creates files with postfixes
+ # appended to the lease file name.
+ if test -n "${LEASE_FILE+x}" && test -n "${LEASE_FILE}"; then
+ rm -rf "${LEASE_FILE}"*
+ fi
+}
+
+# Exists the test in the clean way.
+# It performs the cleanup and prints whether the test has passed or failed.
+# If a test fails, the Kea log is dumped.
+clean_exit() {
+ exit_code=${1-} # Exit code to be returned by the exit function.
+ case ${exit_code} in
+ ''|*[!0-9]*)
+ test_lib_error "argument passed to clean_exit must be a number" ;;
+ esac
+ # Print test result and perform a cleanup
+ test_finish "${exit_code}"
+ exit "${exit_code}"
+}
+
+# Starts Kea process in background using a configuration file specified
+# in the global variable ${CFG_FILE}.
+start_kea() {
+ local bin="${1-}"
+ if [ -z "${bin}" ]; then
+ test_lib_error "binary name must be specified for start_kea"
+ clean_exit 1
+ fi
+ printf "Running command %s.\n" "\"${bin} -c ${CFG_FILE}\""
+ "${bin}" -c "${CFG_FILE}" &
+}
+
+# Waits with timeout for Kea to start.
+# This function repeatedly checks if the Kea log file has been created
+# and is non-empty. If it is, the function assumes that Kea has started.
+# It doesn't check the contents of the log file though.
+# If the log file doesn't exist the function sleeps for a second and
+# checks again. This is repeated until timeout is reached or non-empty
+# log file is found. If timeout is reached, the function reports an
+# error.
+# Return value:
+# _WAIT_FOR_KEA: 0 if Kea hasn't started, 1 otherwise
+wait_for_kea() {
+ local timeout="${1-}" # Desired timeout in seconds.
+ if test -z "${timeout}"; then
+ test_lib_error 'expected timeout parameter in wait_for_kea'
+ clean_exit 1
+ fi
+ case ${timeout} in
+ ''|*[!0-9]*)
+ test_lib_error "argument passed to wait_for_kea must be a number"
+ clean_exit 1 ;;
+ esac
+ local loops=0 # Loops counter
+ _WAIT_FOR_KEA=0
+ test_lib_info "wait_for_kea " "skip-new-line"
+ while [ ! -s "${LOG_FILE}" ] && [ "${loops}" -le "${timeout}" ]; do
+ printf "."
+ sleep 1
+ loops=$(( loops + 1 ))
+ done
+ printf '\n'
+ if [ "${loops}" -le "${timeout}" ]; then
+ _WAIT_FOR_KEA=1
+ fi
+}
+
+# Waits for a specific message to occur in the Kea log file.
+# This function is called when the test expects specific message
+# to show up in the log file as a result of some action that has
+# been taken. Typically, the test expects that the message
+# is logged when the SIGHUP or SIGTERM signal has been sent to the
+# Kea process.
+# This function waits a specified number of seconds for the number
+# of message occurrences to show up. If the expected number of
+# message doesn't occur, the error status is returned.
+# Return value:
+# _WAIT_FOR_MESSAGE: 0 if the message hasn't occurred, 1 otherwise.
+wait_for_message() {
+ local timeout="${1-}" # Expected timeout value in seconds.
+ local message="${2-}" # Expected message id.
+ local occurrences="${3-}" # Number of expected occurrences.
+
+ # Validate timeout
+ case ${timeout} in
+ ''|*[!0-9]*)
+ test_lib_error "argument timeout passed to wait_for_message must \
+be a number"
+ clean_exit 1 ;;
+ esac
+
+ # Validate message
+ if [ -z "${message}" ]; then
+ test_lib_error "message id is a required argument for wait_for_message"
+ clean_exit 1
+ fi
+
+ # Validate occurrences
+ case ${occurrences} in
+ ''|*[!0-9]*)
+ test_lib_error "argument occurrences passed to wait_for_message \
+must be a number"
+ clean_exit 1 ;;
+ esac
+
+ local loops=0 # Number of loops performed so far.
+ _WAIT_FOR_MESSAGE=0
+ test_lib_info "wait_for_message ${message}: " "skip-new-line"
+ # Check if log file exists and if we reached timeout.
+ while [ "${loops}" -le "${timeout}" ]; do
+ printf "."
+ # Check if the message has been logged.
+ get_log_messages "${message}"
+ if [ "${_GET_LOG_MESSAGES}" -ge "${occurrences}" ]; then
+ printf '\n'
+ _WAIT_FOR_MESSAGE=1
+ return
+ fi
+ # Message not recorded. Keep going.
+ sleep 1
+ loops=$(( loops + 1 ))
+ done
+ printf '\n'
+ # Timeout.
+}
+
+# Waits for server to be down.
+# Return value:
+# _WAIT_FOR_SERVER_DOWN: 1 if server is down, 0 if timeout occurred and the
+# server is still running.
+wait_for_server_down() {
+ local timeout="${1-}" # Timeout specified in seconds.
+ local proc_name="${2-}" # Server process name.
+ if test -z "${proc_name}"; then
+ test_lib_error 'expected process name parameter in wait_for_server_down'
+ clean_exit 1
+ fi
+
+ case ${timeout} in
+ ''|*[!0-9]*)
+ test_lib_error "argument passed to wait_for_server_down must be a number"
+ clean_exit 1 ;;
+ esac
+ local loops=0 # Loops counter
+ _WAIT_FOR_SERVER_DOWN=0
+ test_lib_info "wait_for_server_down ${proc_name}: " "skip-new-line"
+ while [ "${loops}" -le "${timeout}" ]; do
+ printf "."
+ get_pid "${proc_name}"
+ if [ "${_GET_PIDS_NUM}" -eq 0 ]; then
+ printf '\n'
+ _WAIT_FOR_SERVER_DOWN=1
+ return
+ fi
+ sleep 1
+ loops=$(( loops + 1 ))
+ done
+ printf '\n'
+}
+
+# Sends specified signal to the Kea process.
+send_signal() {
+ local sig="${1-}" # Signal number.
+ local proc_name="${2-}" # Process name
+
+ # Validate signal
+ case ${sig} in
+ ''|*[!0-9]*)
+ test_lib_error "signal number passed to send_signal \
+must be a number"
+ clean_exit 1 ;;
+ esac
+ # Validate process name
+ if [ -z "${proc_name}" ]; then
+ test_lib_error "send_signal requires process name be passed as argument"
+ clean_exit 1
+ fi
+ # Get Kea pid.
+ get_pid "${proc_name}"
+ if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
+ printf "ERROR: expected one Kea process to be started.\
+ Found %d processes started.\n" ${_GET_PIDS_NUM}
+ clean_exit 1
+ fi
+ printf "Sending signal %s to Kea process (pid=%s).\n" "${sig}" "${_GET_PID}"
+ # Actually send a signal.
+ kill "-${sig}" "${_GET_PID}"
+}
+
+# Verifies that a server is up running by its PID file
+# The PID file is constructed from the given config file name and
+# binary name. If it exists and the PID it contains refers to a
+# live process it sets _SERVER_PID_FILE and _SERVER_PID to the
+# corresponding values. Otherwise, it emits an error and exits.
+verify_server_pid() {
+ local bin_name="${1-}" # binary name of the server
+ local cfg_file="${2-}" # config file name
+
+ # We will construct the PID file name based on the server config
+ # and binary name
+ if [ -z "${bin_name}" ]; then
+ test_lib_error "verify_server_pid requires binary name"
+ clean_exit 1
+ fi
+
+ if [ -z "${cfg_file}" ]; then
+ test_lib_error "verify_server_pid requires config file name"
+ clean_exit 1
+ fi
+
+ # Only the file name portion of the config file is used, try and
+ # extract it. NOTE if this "algorithm" changes this code will need
+ # to be updated.
+ fname=$(basename "${cfg_file}")
+ fname=$(echo "${fname}" | cut -f1 -d'.')
+
+ if [ -z "${fname}" ]; then
+ test_lib_error "verify_server_pid could not extract config name"
+ clean_exit 1
+ fi
+
+ # Now we can build the name:
+ pid_file="${KEA_PIDFILE_DIR}/${fname}.${bin_name}.pid"
+
+ if [ ! -e "${pid_file}" ]; then
+ printf "ERROR: PID file:[%s] does not exist\n" "${pid_file}"
+ clean_exit 1
+ fi
+
+ # File exists, does its PID point to a live process?
+ pid=$(cat "${pid_file}" 2> /dev/null || true)
+ if ! kill -0 "${pid}"; then
+ printf "ERROR: PID file:[%s] exists but PID:[%d] does not\n" \
+ "${pid_file}" "${pid}"
+ clean_exit 1
+ fi
+
+ # Make the values accessible to the caller
+ _SERVER_PID="${pid}"
+ _SERVER_PID_FILE="${pid_file}"
+}
+
+# This test verifies that the binary is reporting its version properly.
+version_test() {
+ test_name=${1} # Test name
+ long_version=${2-} # Test long version?
+
+ # Log the start of the test and print test name.
+ test_start "${test_name}"
+
+ # If set to anything other than empty string, reset it to the long version
+ # parameter.
+ if test -n "${long_version}"; then
+ long_version='--version'
+ fi
+
+ # Keep ${long_version} unquoted so that it is not included as an empty
+ # string if not given as argument.
+ for v in -v ${long_version}; do
+ run_command \
+ "${bin_path}/${bin}" "${v}"
+
+ if test "${OUTPUT}" != "${EXPECTED_VERSION}"; then
+ printf 'ERROR: Expected version "%s", got "%s" when calling "%s"\n' \
+ "${EXPECTED_VERSION}" "${OUTPUT}" "${bin} ${v}"
+ test_finish 1
+ fi
+ done
+
+ test_finish 0
+}
+
+# This test verifies that the server is using logger variable
+# KEA_LOCKFILE_DIR properly (it should be used to point out to the directory,
+# where lockfile should be created. Also, "none" value means to not create
+# the lockfile at all).
+logger_vars_test() {
+ test_name=${1} # Test name
+
+ # Log the start of the test and print test name.
+ test_start "${test_name}"
+
+ # Create bogus configuration file. We don't really want the server to start,
+ # just want it to log something and die. Empty config is an easy way to
+ # enforce that behavior.
+ create_config "{ }"
+ printf "Please ignore any config error messages.\n"
+
+ # Remember old KEA_LOCKFILE_DIR
+ KEA_LOCKFILE_DIR_OLD=${KEA_LOCKFILE_DIR}
+
+ # Set lockfile directory to current directory.
+ KEA_LOCKFILE_DIR=.
+
+ # Start Kea.
+ start_kea "${bin_path}/${bin}"
+
+ # Wait for Kea to process the invalid configuration and die.
+ sleep 1
+
+ # Check if it is still running. It should have terminated.
+ get_pid "${bin}"
+ if [ "${_GET_PIDS_NUM}" -ne 0 ]; then
+ printf 'ERROR: expected Kea process to not start. '
+ printf 'Found %d processes running.\n' "${_GET_PIDS_NUM}"
+
+ # Revert to the old KEA_LOCKFILE_DIR value
+ KEA_LOCKFILE_DIR=${KEA_LOCKFILE_DIR_OLD}
+ clean_exit 1
+ fi
+
+ if [ ! -f "./logger_lockfile" ]; then
+ printf 'ERROR: Expect %s to create logger_lockfile in the ' "${bin}"
+ printf 'current directory, but no such file exists.\n'
+
+ # Revert to the old KEA_LOCKFILE_DIR value
+ KEA_LOCKFILE_DIR=${KEA_LOCKFILE_DIR__OLD}
+ clean_exit 1
+ fi
+
+ # Remove the lock file
+ rm -f ./logger_lockfile
+
+ # Tell Kea to NOT create logfiles at all
+ KEA_LOCKFILE_DIR="none"
+
+ # Start Kea.
+ start_kea "${bin_path}/${bin}"
+
+ # Wait for Kea to process the invalid configuration and die.
+ sleep 1
+
+ # Check if it is still running. It should have terminated.
+ get_pid "${bin}"
+ if [ "${_GET_PIDS_NUM}" -ne 0 ]; then
+ printf 'ERROR: expected Kea process to not start. '
+ printf 'Found %d processes running.\n' "${_GET_PIDS_NUM}"
+
+ # Revert to the old KEA_LOCKFILE_DIR value
+ KEA_LOCKFILE_DIR=${KEA_LOCKFILE_DIR_OLD}
+
+ clean_exit 1
+ fi
+
+ if [ -f "./logger_lockfile" ]; then
+ printf 'ERROR: Expect %s to NOT create logger_lockfile in the ' "${bin}"
+ printf 'current directory, but the file exists.\n'
+
+ # Revert to the old KEA_LOCKFILE_DIR value
+ KEA_LOCKFILE_DIR=${KEA_LOCKFILE_DIR_OLD}
+
+ clean_exit 1
+ fi
+
+ # Revert to the old KEA_LOCKFILE_DIR value
+ printf 'Reverting KEA_LOCKFILE_DIR to %s\n' "${KEA_LOCKFILE_DIR_OLD}"
+ KEA_LOCKFILE_DIR=${KEA_LOCKFILE_DIR_OLD}
+
+ test_finish 0
+}
+
+# This test verifies server PID file management
+# 1. It verifies that upon startup, the server creates a PID file
+# 2. It verifies the an attempt to start a second instance fails
+# due to pre-existing PID File/PID detection
+server_pid_file_test() {
+ local server_cfg="${1}"
+ local log_id="${2}"
+
+ # Log the start of the test and print test name.
+ test_start "${bin}.server_pid_file_test"
+ # Create new configuration file.
+ create_config "${CONFIG}"
+ # Instruct server to log to the specific file.
+ set_logger
+ # Start server
+ start_kea "${bin_path}/${bin}"
+ # Wait up to 20s for server to start.
+ wait_for_kea 20
+ if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then
+ printf 'ERROR: timeout waiting for %s to start.\n' "${bin}"
+ clean_exit 1
+ fi
+
+ # Verify server is still running
+ verify_server_pid "${bin}" "${CFG_FILE}"
+
+ printf 'PID file is [%s], PID is [%d]\n' "${_SERVER_PID_FILE}" "${_SERVER_PID}"
+
+ # Now try to start a second one
+ start_kea "${bin_path}/${bin}"
+
+ wait_for_message 10 "${log_id}" 1
+ if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
+ printf 'ERROR: Second %s instance started? ' "${bin}"
+ printf 'PID conflict not reported.\n'
+ clean_exit 1
+ fi
+
+ # Verify server is still running
+ verify_server_pid "${bin}" "${CFG_FILE}"
+
+ # All ok. Shut down the server and exit.
+ test_finish 0
+}
+
+# This test verifies that passwords are redacted in logs.
+# This function takes 2 parameters:
+# test_name
+# config - string with a content of the config (will be written to a file)
+# expected_code - expected exit code returned by kea (0 - success, 1 - failure)
+password_redact_test() {
+ local test_name="${1}"
+ local config="${2}"
+ local expected_code="${3}"
+
+ # Log the start of the test and print test name.
+ test_start "${test_name}"
+ # Create correct configuration file.
+ create_config "${config}"
+ # Instruct Control Agent to log to the specific file.
+ set_logger
+ # Check it
+ printf "Running command %s.\n" "\"${bin_path}/${bin} -d -t ${CFG_FILE}\""
+ run_command \
+ "${bin_path}/${bin}" -d -t "${CFG_FILE}"
+ if [ "${EXIT_CODE}" -ne "${expected_code}" ]; then
+ printf 'ERROR: expected exit code %s, got %s\n' "${expected_code}" "${EXIT_CODE}"
+ clean_exit 1
+ fi
+ if grep -q 'sensitive' "${LOG_FILE}"; then
+ printf "ERROR: sensitive is present in logs\n"
+ clean_exit 1
+ fi
+ if ! grep -q 'superadmin' "${LOG_FILE}"; then
+ printf "ERROR: superadmin is not present in logs\n"
+ clean_exit 1
+ fi
+ test_finish 0
+}
+
+# kea-dhcp[46] configuration with a password
+# used for redact tests:
+# - sensitive should be hidden
+# - superadmin should be visible
+kea_dhcp_config() {
+ printf '
+{
+ "Dhcp%s": {
+ "config-control": {
+ "config-databases": [
+ {
+ "password": "sensitive",
+ "type": "mysql",
+ "user": "keatest"
+ }
+ ]
+ },
+ "hooks-libraries": [
+ {
+ "library": "@abs_top_builddir@/src/bin/dhcp%s/tests/.libs/libco1.so",
+ "parameters": {
+ "password": "sensitive",
+ "user": "keatest",
+ "nested-map": {
+ "password": "sensitive",
+ "user": "keatest"
+ }
+ }
+ }
+ ],
+ "hosts-database": {
+ "password": "sensitive",
+ "type": "mysql",
+ "user": "keatest"
+ },
+ "lease-database": {
+ "password": "sensitive",
+ "type": "mysql",
+ "user": "keatest"
+ },
+ "user-context": {
+ "password": "superadmin",
+ "secret": "superadmin",
+ "shared-info": {
+ "password": "superadmin",
+ "secret": "superadmin"
+ }
+ }
+ }
+}
+' "${1}" "${1}"
+}
diff --git a/src/lib/testutils/gtest_utils.h b/src/lib/testutils/gtest_utils.h
new file mode 100644
index 0000000..e7b10b2
--- /dev/null
+++ b/src/lib/testutils/gtest_utils.h
@@ -0,0 +1,92 @@
+// Copyright (C) 2019 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 GTEST_UTILS_H
+#define GTEST_UTILS_H
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace test {
+
+/// @brief Verifies an expected exception type and message
+///
+/// If the statement does not generate the expected exception
+/// containing the expected message it will generate a non-fatal
+/// failure.
+///
+/// @param statement - statement block to execute
+/// @param etype - type of exception expected
+/// @param emsg - exact content expected to be returned by ex.what()
+#define EXPECT_THROW_MSG(statement,etype,emsg) \
+{ \
+ try { \
+ statement; \
+ ADD_FAILURE() << "no exception, expected: " << #etype; \
+ } catch (const etype& ex) { \
+ EXPECT_EQ(std::string(ex.what()), emsg); \
+ } catch (...) { \
+ ADD_FAILURE() << "wrong exception type thrown, expected: " << #etype; \
+ } \
+} \
+
+/// @brief Verifies an expected exception type and message
+///
+/// If the statement does not generate the expected exception
+/// containing the expected message it will generate a fatal
+/// failure.
+///
+/// @param statement - statement block to execute
+/// @param etype - type of exception expected
+/// @param emsg - exact content expected to be returned by ex.what()
+#define ASSERT_THROW_MSG(statement,etype,emsg) \
+{ \
+ try { \
+ statement; \
+ GTEST_FAIL() << "no exception, expected: " << #etype; \
+ } catch (const etype& ex) { \
+ ASSERT_EQ(std::string(ex.what()), emsg); \
+ } catch (...) { \
+ GTEST_FAIL() << "wrong exception type thrown, expected: " << #etype; \
+ } \
+} \
+
+/// @brief Adds a non-fatal failure with exception info, if the given
+/// expression throws. Note the type name emitted may be mangled.
+///
+/// @param statement - statement block to execute
+#define EXPECT_NO_THROW_LOG(statement) \
+{ \
+ try { \
+ statement; \
+ } catch (const std::exception& ex) { \
+ ADD_FAILURE() << #statement << " threw type: " << typeid(ex).name() \
+ << ", what: " << ex.what(); \
+ } catch (...) { \
+ ADD_FAILURE() << #statement << "threw non-std::exception"; \
+ } \
+} \
+
+/// @brief Generates a fatal failure with exception info, if the given
+/// expression throws. Note the type name emitted may be mangled.
+///
+/// @param statement - statement block to execute
+#define ASSERT_NO_THROW_LOG(statement) \
+{ \
+ try { \
+ statement; \
+ } catch (const std::exception& ex) { \
+ GTEST_FAIL() << #statement << " threw type: " << typeid(ex).name() \
+ << ", what: " << ex.what(); \
+ } catch (...) { \
+ GTEST_FAIL() << #statement << " threw non-std::exception"; \
+ } \
+} \
+
+}; // end of isc::test namespace
+}; // end of isc namespace
+
+#endif // GTEST_UTILS_H
diff --git a/src/lib/testutils/io_utils.cc b/src/lib/testutils/io_utils.cc
new file mode 100644
index 0000000..4b08f16
--- /dev/null
+++ b/src/lib/testutils/io_utils.cc
@@ -0,0 +1,117 @@
+// Copyright (C) 2015-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 <exceptions/exceptions.h>
+#include <testutils/io_utils.h>
+#include <gtest/gtest.h>
+#include <fstream>
+#include <sstream>
+#include <string>
+
+namespace isc {
+namespace test {
+
+bool fileExists(const std::string& file_path) {
+ struct stat statbuf;
+ return(stat(file_path.c_str(), &statbuf) == 0);
+}
+
+std::string readFile(const std::string& file_path) {
+ std::ifstream ifs;
+ ifs.open(file_path.c_str(), std::ifstream::in);
+ if (!ifs.good()) {
+ return (std::string());
+ }
+ std::string buf;
+ std::ostringstream output;
+ while (!ifs.eof() && ifs.good()) {
+ ifs >> buf;
+ output << buf;
+ }
+ ifs.close();
+
+ return (output.str());
+}
+
+std::string decommentJSONfile(const std::string& input_file) {
+
+ using namespace std;
+
+ ifstream f(input_file);
+ if (!f.is_open()) {
+ isc_throw(isc::BadValue, "can't open input file for reading: " + input_file);
+ }
+
+ string outfile;
+ size_t last_slash = input_file.find_last_of("/");
+ if (last_slash != string::npos) {
+ outfile = input_file.substr(last_slash + 1);
+ } else {
+ outfile = input_file;
+ }
+ outfile += "-decommented";
+
+ ofstream out(outfile);
+ if (!out.is_open()) {
+ isc_throw(isc::BadValue, "can't open output file for writing: " + input_file);
+ }
+
+ bool in_comment = false;
+ string line;
+ while (std::getline(f, line)) {
+ // First, let's get rid of the # comments
+ size_t hash_pos = line.find("#");
+ if (hash_pos != string::npos) {
+ line = line.substr(0, hash_pos);
+ }
+
+ // Second, let's get rid of the // comments
+ // at the beginning or after a control character.
+ size_t dblslash_pos = line.find("//");
+ if ((dblslash_pos != string::npos) &&
+ ((dblslash_pos == 0) ||
+ ((unsigned) line[dblslash_pos - 1] <= 32))) {
+ line = line.substr(0, dblslash_pos);
+ }
+
+ // Now the tricky part: c comments.
+ size_t begin_pos = line.find("/*");
+ size_t end_pos = line.find("*/");
+ if (in_comment && end_pos == string::npos) {
+ // we continue through multiline comment
+ line = "";
+ } else {
+
+ if (begin_pos != string::npos) {
+ in_comment = true;
+ if (end_pos != string::npos) {
+ // single line comment. Let's get rid of the content in between
+ line = line.replace(begin_pos, end_pos + 2, end_pos + 2 - begin_pos, ' ');
+ in_comment = false;
+ } else {
+ line = line.substr(0, begin_pos);
+ }
+ } else {
+ if (in_comment && end_pos != string::npos) {
+ line = line.replace(0, end_pos +2 , end_pos + 2, ' ');
+ in_comment = false;
+ }
+ }
+ }
+
+ // Finally, write the line to the output file.
+ out << line << endl;
+ }
+ f.close();
+ out.close();
+
+ return (outfile);
+}
+
+}; // end of isc::test namespace
+}; // end of isc namespace
diff --git a/src/lib/testutils/io_utils.h b/src/lib/testutils/io_utils.h
new file mode 100644
index 0000000..58fd015
--- /dev/null
+++ b/src/lib/testutils/io_utils.h
@@ -0,0 +1,45 @@
+// Copyright (C) 2015-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_IO_UTILS_H
+#define TEST_IO_UTILS_H
+
+#include <string>
+#include <sys/stat.h>
+
+namespace isc {
+namespace test {
+
+/// @brief Checks if specified file exists.
+///
+/// @param file_path Path to a file.
+/// @return true if the file exists, false otherwise.
+bool fileExists(const std::string& file_path);
+
+/// @brief Reads contents of the specified file.
+///
+/// @param file_path Path to a file.
+/// @return File contents.
+std::string readFile(const std::string& file_path);
+
+/// @brief Removes comments from a JSON file
+///
+/// Removes //, # and /* */ comments from the input file and writes its content
+/// to another file. The comments are replaced with spaces, so the original
+/// token locations should remain unaffected. This is rather naive
+/// implementation, but it's probably sufficient for testing. It won't be able
+/// to pick any trickier cases, like # or // appearing in strings, nested C++
+/// comments etc at the exception of // in URLs.
+///
+/// @param input_file file to be stripped of comments
+/// @return filename of a new file that has comments stripped from it
+/// @throw BadValue if the input file cannot be opened
+std::string decommentJSONfile(const std::string& input_file);
+
+}; // end of isc::test namespace
+}; // end of isc namespace
+
+#endif // TEST_IO_UTILS_H
diff --git a/src/lib/testutils/lib_load_test_fixture.h b/src/lib/testutils/lib_load_test_fixture.h
new file mode 100644
index 0000000..5229acc
--- /dev/null
+++ b/src/lib/testutils/lib_load_test_fixture.h
@@ -0,0 +1,68 @@
+// Copyright (C) 2022 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_TESTUTILS_LIB_LOAD_TEST_FIXTURE_H
+#define ISC_TESTUTILS_LIB_LOAD_TEST_FIXTURE_H
+
+#include <cc/data.h>
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace test {
+
+/// @brief Test fixture for testing loading and unloading of hook libraries.
+struct LibLoadTest : ::testing::Test {
+ /// @brief Constructor. Unloads any previously loaded libraries.
+ LibLoadTest() {
+ unloadLibraries();
+ }
+
+ /// @brief Destructor. Unloads any previously loaded libraries.
+ ~LibLoadTest() {
+ unloadLibraries();
+ }
+
+ /// @brief Adds a library along with its parameters to the list of libraries to be loaded.
+ ///
+ /// @param library the path to the library to be loaded
+ /// @param parameters the library's parameters in Element format
+ void addLibrary(const std::string& library, isc::data::ConstElementPtr parameters) {
+ libraries_.push_back({library, parameters});
+ }
+
+ void clearLibraries() {
+ libraries_.clear();
+ }
+
+ /// @brief Load all libraries.
+ ///
+ /// @return true if all libraries loaded successfully, false if one or more
+ /// libraries failed to load.
+ bool loadLibraries() {
+ bool result(false);
+ EXPECT_NO_THROW(result = isc::hooks::HooksManager::loadLibraries(libraries_));
+ return result;
+ }
+
+ /// @brief Unloads all libraries.
+ ///
+ /// @return true if all libraries unloaded successfully, false if they
+ /// are still in memory.
+ bool unloadLibraries() {
+ bool result(false);
+ EXPECT_NO_THROW(result = isc::hooks::HooksManager::unloadLibraries());
+ return result;
+ }
+
+ /// @brief Libraries
+ isc::hooks::HookLibsCollection libraries_;
+};
+
+} // namespace test
+} // namespace isc
+
+#endif // ISC_TESTUTILS_LIB_LOAD_TEST_FIXTURE_H
diff --git a/src/lib/testutils/log_utils.cc b/src/lib/testutils/log_utils.cc
new file mode 100644
index 0000000..56b6d33
--- /dev/null
+++ b/src/lib/testutils/log_utils.cc
@@ -0,0 +1,131 @@
+// Copyright (C) 2016-2021 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/log_utils.h>
+#include <cstdlib>
+#include <iostream>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+LogContentTest::LogContentTest()
+ :verbose_(false) {
+ // Get rid of any old files
+ remFile();
+
+ // Set up the logger for use in checking the debug statements.
+ // We send the debug statements to a file which we can
+ // check after the evaluations have completed. We also
+ // set the log severity and debug levels so that the log
+ // statements are executed.
+ LoggerSpecification spec(getRootLoggerName(),
+ keaLoggerSeverity(isc::log::DEBUG),
+ keaLoggerDbglevel(isc::log::MAX_DEBUG_LEVEL));
+ OutputOption option;
+ option.destination = OutputOption::DEST_FILE;
+ option.filename = string(LogContentTest::LOG_FILE);
+ spec.addOutputOption(option);
+ LoggerManager manager;
+ manager.process(spec);
+
+ // Overwrite the verbose_ default is the KEA_LOG_CHECK_VERBOSE
+ // environment variable exists.
+ if (getenv(KEA_LOG_CHECK_VERBOSE)) {
+ verbose_ = true;
+ }
+}
+
+LogContentTest:: ~LogContentTest() {
+ remFile();
+}
+
+bool LogContentTest::checkFile() {
+ ifstream file(LOG_FILE);
+ EXPECT_TRUE(file.is_open());
+ string line, exp_line;
+ int i = 0;
+ bool found = true;
+
+ using namespace std;
+
+ while (getline(file, line) && (i != exp_strings_.size())) {
+ exp_line = exp_strings_[i];
+ if (verbose_) {
+ cout << "Read line : " << line << endl;
+ cout << "Looking for: " << exp_line << endl;
+ }
+ i++;
+ if (string::npos == line.find(exp_line)) {
+ if (verbose_) {
+ cout << "Verdict : not found" << endl;
+ }
+ found = false;
+ }
+ }
+
+ file.close();
+
+ if ((i != exp_strings_.size()) || (found == false)) {
+ if (verbose_) {
+ cout << "Final verdict: false" << endl;
+ }
+ return (false);
+ }
+
+ return (true);
+}
+
+size_t LogContentTest::countFile(const string& exp_string) {
+ ifstream file(LOG_FILE);
+ EXPECT_TRUE(file.is_open());
+ string line;
+ size_t cnt = 0;
+
+ using namespace std;
+
+ if (verbose_) {
+ cout << "Looking for:" << exp_string << endl;
+ }
+ while (getline(file, line)) {
+ if (verbose_) {
+ cout << "Read line :" << line << endl;
+ }
+ if (line.find(exp_string) != string::npos) {
+ ++cnt;
+ }
+ }
+
+ file.close();
+
+ if (verbose_) {
+ cout << "Final count: " << cnt << endl;
+ }
+
+ return (cnt);
+}
+
+void LogContentTest::remFile() {
+ static_cast<void>(remove(LOG_FILE));
+}
+
+void LogContentTest::addString(const string& new_string) {
+ exp_strings_.push_back(new_string);
+}
+
+// Set up the name of the LOG_FILE for use in checking
+// the debug statements.
+// Must not be the same file name used by test shell scripts.
+const char* LogContentTest::LOG_FILE = "logtest.log";
+
+// The environment variable to overwrite the verbose_ default value.
+const char* LogContentTest::KEA_LOG_CHECK_VERBOSE = "KEA_LOG_CHECK_VERBOSE";
+
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/testutils/log_utils.h b/src/lib/testutils/log_utils.h
new file mode 100644
index 0000000..7191e0e
--- /dev/null
+++ b/src/lib/testutils/log_utils.h
@@ -0,0 +1,105 @@
+// Copyright (C) 2016-2022 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_LOG_UTILS_H
+#define TEST_LOG_UTILS_H
+
+#include <string>
+#include <fstream>
+
+//#include <config.h>
+
+#include <log/logger_manager.h>
+#include <log/logger_name.h>
+#include <log/logger_support.h>
+
+//#include <boost/shared_ptr.hpp>
+//#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::log;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test class for testing things that emit log entries
+///
+/// This class provides a convenient method for testing out
+/// things that emit log entries. The class sets up the logging
+/// to log everything into a file and provides a routine to
+/// check the expected strings vs the actual log entries.
+/// The user needs to call the addString function for each of
+/// the strings they expect in the output in the order they
+/// will be emitted.
+
+class LogContentTest : public ::testing::Test {
+public:
+
+ /// @brief Initializes the logger setup for using
+ /// in checking log statements
+ ///
+ /// @todo add support to adjust the severity and debug level
+ /// to allow for better control over the statements that
+ /// get logged.
+ LogContentTest();
+
+ virtual ~LogContentTest();
+
+ /// @brief check that the requested strings are in the
+ /// test log file in the requested order.
+ ///
+ /// This routine expects that the caller has properly
+ /// set up the vector of expected strings by calling
+ /// addString() with the necessary strings.
+ ///
+ /// @return true if all of the strings match
+ bool checkFile();
+
+ /// @brief check that the requested string is in the
+ /// test log file.
+ ///
+ /// @param exp_string the string to be searched
+ /// @return count of matching lines
+ size_t countFile(const string& exp_string);
+
+ /// @brief remove the test log file
+ void remFile();
+
+ /// @brief Enables or disables verbose mode.
+ ///
+ /// See @ref verbose_ for details.
+ ///
+ /// @param talk_a_lot (true - as the name says, false - shut up)
+ void logCheckVerbose(bool talk_a_lot) {
+ verbose_ = talk_a_lot;
+ }
+
+ /// @brief Add a string to the vector of expected strings
+ ///
+ /// @param new_string the string to add to the end of the vector
+ void addString(const string& new_string);
+
+ vector<string> exp_strings_;
+ static const char* LOG_FILE;
+ static const char* KEA_LOG_CHECK_VERBOSE;
+
+ /// @brief controls whether the checkFile() should print more details.
+ ///
+ /// If set to true, checkFile() will print each expected line, each
+ /// logged line and will print out a failure message if those two do
+ /// not match. Also, a final verdict is printed. Everything is printed
+ /// on stdout.
+ /// The default is false but can be overwritten by setting the
+ /// KEA_LOG_CHECK_VERBOSE environment variable.
+ bool verbose_;
+};
+
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace
+#endif // TEST_LOG_UTILS_H
diff --git a/src/lib/testutils/multi_threading_utils.h b/src/lib/testutils/multi_threading_utils.h
new file mode 100644
index 0000000..8754208
--- /dev/null
+++ b/src/lib/testutils/multi_threading_utils.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2020 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 MULTI_THREADING_UTILS_H
+#define MULTI_THREADING_UTILS_H
+
+#include <util/multi_threading_mgr.h>
+
+namespace isc {
+namespace test {
+
+/// @brief A RAII class which disables the multi threading on exit of scope.
+///
+/// Usually the multi threading is disabled by the fixture destructor or
+/// TearDown but of course this works only when a fixture class is used.
+class MultiThreadingTest {
+public:
+
+ /// @brief Constructor (set multi threading mode).
+ ///
+ /// @param mode The mode to use in the body. Defaults to true / enabled.
+ MultiThreadingTest(bool mode = true) {
+ isc::util::MultiThreadingMgr::instance().setMode(mode);
+ }
+
+ /// @brief Destructor (disable multi threading).
+ ~MultiThreadingTest() {
+ isc::util::MultiThreadingMgr::instance().setMode(false);
+ }
+};
+
+} // end of isc::test namespace
+} // end of isc namespace
+
+#endif // MULTI_THREADING_UTILS_H
diff --git a/src/lib/testutils/sandbox.h b/src/lib/testutils/sandbox.h
new file mode 100644
index 0000000..8dfb572
--- /dev/null
+++ b/src/lib/testutils/sandbox.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2019,2021 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 SANDBOX_H
+#define SANDBOX_H
+
+#include <exceptions/exceptions.h>
+
+#include <iostream>
+#include <string>
+#include <cstdlib>
+#include <cstdio>
+#include <unistd.h>
+#include <ftw.h>
+
+namespace isc {
+namespace test {
+
+/// @brief A Sandbox class that provides access to unit test unique
+/// temporary folder.
+///
+/// The sandbox's temporary folder is created in constructor ie.
+/// in unit test setup phase, and then it is deleted with its content
+/// in destructor ie. in unit test tear down phase.
+class Sandbox {
+private:
+ /// Path to temporary folder
+ std::string path_;
+
+ /// @brief Method for deleting files and folders, used in nftw traversal function.
+ ///
+ /// @param fpath path to the file to be removed.
+ static int rmFile(const char *fpath, const struct stat *, int , struct FTW *) {
+ return(remove(fpath));
+ }
+
+public:
+ /// @brief Sandbox constructor.
+ Sandbox() {
+ char tmpl[] = {P_tmpdir "/kea-XXXXXX"};
+ path_ = mkdtemp(tmpl);
+ }
+
+ /// @brief Destructor, it deletes temporary folder with its content.
+ ~Sandbox() {
+ // Delete content of path_ recursively.
+ if (nftw(path_.c_str(), Sandbox::rmFile, 10, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) < 0) {
+ auto msg = "Some error occurred while deleting unit test sandbox " + path_;
+ std::perror(msg.c_str());
+ exit(1);
+ }
+ }
+
+ /// @brief Join sandbox path with indicated file subpath.
+ ///
+ /// @param file path to file that should be joined to base path of sandbox.
+ std::string join(std::string file) {
+ return (path_ + "/" + file);
+ }
+};
+
+
+}; // end of isc::test namespace
+}; // end of isc namespace
+
+#endif // SANDBOX_H
diff --git a/src/lib/testutils/test_to_element.cc b/src/lib/testutils/test_to_element.cc
new file mode 100644
index 0000000..115757a
--- /dev/null
+++ b/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
diff --git a/src/lib/testutils/test_to_element.h b/src/lib/testutils/test_to_element.h
new file mode 100644
index 0000000..1aa88fc
--- /dev/null
+++ b/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 template 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 template 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
diff --git a/src/lib/testutils/threaded_test.cc b/src/lib/testutils/threaded_test.cc
new file mode 100644
index 0000000..88cb846
--- /dev/null
+++ b/src/lib/testutils/threaded_test.cc
@@ -0,0 +1,73 @@
+// Copyright (C) 2018-2020 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/threaded_test.h>
+
+namespace isc {
+namespace test {
+
+ThreadedTest::ThreadedTest()
+ : thread_(), condvar_(), ready_(false), stopping_(false),
+ stopped_(false) {
+}
+
+void
+ThreadedTest::doSignal(bool& flag) {
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ flag = true;
+ }
+ condvar_.notify_one();
+}
+
+void
+ThreadedTest::signalReady() {
+ doSignal(ready_);
+}
+
+void
+ThreadedTest::signalStopping() {
+ doSignal(stopping_);
+}
+
+void
+ThreadedTest::signalStopped() {
+ doSignal(stopped_);
+}
+
+void
+ThreadedTest::doWait(bool& flag) {
+ std::unique_lock<std::mutex> lock(mutex_);
+ while (!flag) {
+ condvar_.wait(lock);
+ }
+}
+
+void
+ThreadedTest::waitReady() {
+ doWait(ready_);
+}
+
+void
+ThreadedTest::waitStopping() {
+ doWait(stopping_);
+}
+
+void
+ThreadedTest::waitStopped() {
+ doWait(stopped_);
+}
+
+bool
+ThreadedTest::isStopping() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ return (stopping_);
+}
+
+} // end of namespace isc::test
+} // end of namespace isc
diff --git a/src/lib/testutils/threaded_test.h b/src/lib/testutils/threaded_test.h
new file mode 100644
index 0000000..6f430e4
--- /dev/null
+++ b/src/lib/testutils/threaded_test.h
@@ -0,0 +1,88 @@
+// Copyright (C) 2018-2019,2021 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 THREADED_TEST_H
+#define THREADED_TEST_H
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <thread>
+#include <mutex>
+#include <condition_variable>
+
+namespace isc {
+namespace test {
+
+/// @brief Base class for tests requiring threads.
+///
+/// This class contains 3 flags to signal when the thread has
+/// started, is stopping and when it is stopped. The flags
+/// are accessed in thread safe manner.
+class ThreadedTest : public ::testing::Test {
+protected:
+
+ /// @brief Constructor.
+ ThreadedTest();
+
+ /// @brief Sets selected flag to true and signals condition
+ /// variable.
+ ///
+ /// @param flag Reference to flag which should be set to true.
+ void doSignal(bool& flag);
+
+ /// @brief Signal that thread is ready.
+ void signalReady();
+
+ /// @brief Signal that thread is stopping.
+ void signalStopping();
+
+ /// @brief Signal that thread is stopped.
+ void signalStopped();
+
+ /// @brief Wait for a selected flag to be set.
+ ///
+ /// @param flag Reference to a flag on which the thread is
+ /// waiting.
+ void doWait(bool& flag);
+
+ /// @brief Wait for the thread to be ready.
+ void waitReady();
+
+ /// @brief Wait for the thread to be stopping.
+ void waitStopping();
+
+ /// @brief Wait for the thread to stop.
+ void waitStopped();
+
+ /// @brief Checks if the thread is stopping.
+ ///
+ /// @return true if the thread is stopping, false otherwise.
+ bool isStopping();
+
+ /// @brief Pointer to server thread.
+ boost::shared_ptr<std::thread> thread_;
+
+ /// @brief Mutex used to synchronize threads.
+ std::mutex mutex_;
+
+ /// Conditional variable for thread waits.
+ std::condition_variable condvar_;
+
+ /// Flag indicating that the thread is ready.
+ bool ready_;
+
+ /// Flag indicating that the thread is stopping.
+ bool stopping_;
+
+ /// Flag indicating that the thread is stopped.
+ bool stopped_;
+};
+
+
+} // end of namespace isc::test
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/testutils/unix_control_client.cc b/src/lib/testutils/unix_control_client.cc
new file mode 100644
index 0000000..f0f8dfa
--- /dev/null
+++ b/src/lib/testutils/unix_control_client.cc
@@ -0,0 +1,139 @@
+// Copyright (C) 2015-2019 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 <gtest/gtest.h>
+#include <testutils/unix_control_client.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <string.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+UnixControlClient::UnixControlClient() {
+ socket_fd_ = -1;
+}
+
+UnixControlClient::~UnixControlClient() {
+ disconnectFromServer();
+}
+
+ /// @brief Closes the Control Channel socket
+void UnixControlClient::disconnectFromServer() {
+ if (socket_fd_ >= 0) {
+ static_cast<void>(close(socket_fd_));
+ socket_fd_ = -1;
+ }
+}
+
+bool UnixControlClient::connectToServer(const std::string& socket_path) {
+ // Create UNIX socket
+ socket_fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (socket_fd_ < 0) {
+ const char* errmsg = strerror(errno);
+ ADD_FAILURE() << "Failed to open unix stream socket: " << errmsg;
+ return (false);
+ }
+
+ struct sockaddr_un srv_addr;
+ if (socket_path.size() > sizeof(srv_addr.sun_path) - 1) {
+ ADD_FAILURE() << "Socket path specified (" << socket_path
+ << ") is larger than " << (sizeof(srv_addr.sun_path) - 1)
+ << " allowed.";
+ disconnectFromServer();
+ return (false);
+ }
+
+ // Prepare socket address
+ memset(&srv_addr, 0, sizeof(srv_addr));
+ srv_addr.sun_family = AF_UNIX;
+ strncpy(srv_addr.sun_path, socket_path.c_str(),
+ sizeof(srv_addr.sun_path) - 1);
+ socklen_t len = sizeof(srv_addr);
+
+ // Connect to the specified UNIX socket
+ int status = connect(socket_fd_, (struct sockaddr*)&srv_addr, len);
+ if (status == -1) {
+ const char* errmsg = strerror(errno);
+ ADD_FAILURE() << "Failed to connect unix socket: fd=" << socket_fd_
+ << ", path=" << socket_path << " : " << errmsg;
+ disconnectFromServer();
+ return (false);
+ }
+
+ return (true);
+}
+
+bool UnixControlClient::sendCommand(const std::string& command) {
+ // Send command
+ int bytes_sent = send(socket_fd_, command.c_str(), command.length(), 0);
+ if (bytes_sent < command.length()) {
+ const char* errmsg = strerror(errno);
+ ADD_FAILURE() << "Failed to send " << command.length()
+ << " bytes, send() returned " << bytes_sent
+ << " : " << errmsg;
+ return (false);
+ }
+
+ return (true);
+}
+
+bool UnixControlClient::getResponse(std::string& response,
+ const unsigned int timeout_sec) {
+ // Receive response
+ char buf[65536];
+ memset(buf, 0, sizeof(buf));
+ switch (selectCheck(timeout_sec)) {
+ case -1: {
+ const char* errmsg = strerror(errno);
+ ADD_FAILURE() << "getResponse - select failed: " << errmsg;
+ return (false);
+ }
+ case 0:
+ return (false);
+
+ default:
+ break;
+ }
+
+ int bytes_rcvd = recv(socket_fd_, buf, sizeof(buf), 0);
+ if (bytes_rcvd < 0) {
+ const char* errmsg = strerror(errno);
+ ADD_FAILURE() << "Failed to receive a response. recv() returned "
+ << bytes_rcvd << " : " << errmsg;
+ return (false);
+ }
+
+ // Convert the response to a string
+ response = std::string(buf, bytes_rcvd);
+ return (true);
+}
+
+int UnixControlClient::selectCheck(const unsigned int timeout_sec) {
+ int maxfd = 0;
+
+ fd_set read_fds;
+ FD_ZERO(&read_fds);
+
+ // Add this socket to listening set
+ FD_SET(socket_fd_, &read_fds);
+ maxfd = socket_fd_;
+
+ struct timeval select_timeout;
+ select_timeout.tv_sec = static_cast<time_t>(timeout_sec);
+ select_timeout.tv_usec = 0;
+
+ return (select(maxfd + 1, &read_fds, NULL, NULL, &select_timeout));
+}
+
+};
+};
+};
diff --git a/src/lib/testutils/unix_control_client.h b/src/lib/testutils/unix_control_client.h
new file mode 100644
index 0000000..225c949
--- /dev/null
+++ b/src/lib/testutils/unix_control_client.h
@@ -0,0 +1,66 @@
+// Copyright (C) 2015-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 UNIX_CONTROL_CLIENT_H
+#define UNIX_CONTROL_CLIENT_H
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Class that acts as a UnixCommandSocket client
+///
+/// This class is expected to be used unit-tests that attempt to communicate
+/// with the servers that use control channel (see src/lib/config/command_mgr.h)
+/// It can connect to an open UnixCommandSocket and exchange ControlChannel
+/// commands and responses.
+class UnixControlClient {
+public:
+
+ /// @brief Default constructor
+ UnixControlClient();
+
+ /// @brief Destructor
+ ~UnixControlClient();
+
+ /// @brief Closes the Control Channel socket
+ void disconnectFromServer();
+
+ /// @brief Connects to a Unix socket at the given path
+ /// @param socket_path pathname of the socket to open
+ /// @return true if the connect was successful, false otherwise
+ bool connectToServer(const std::string& socket_path);
+
+ /// @brief Sends the given command across the open Control Channel
+ /// @param command the command text to execute in JSON form
+ /// @return true if the send succeeds, false otherwise
+ bool sendCommand(const std::string& command);
+
+ /// @brief Reads the response text from the open Control Channel
+ /// @param response variable into which the received response should be
+ /// placed.
+ /// @param timeout_sec Timeout for receiving response in seconds.
+ /// @return true if data was successfully read from the socket,
+ /// false otherwise
+ bool getResponse(std::string& response, const unsigned int timeout_sec = 0);
+
+ /// @brief Uses select to poll the Control Channel for data waiting
+ ///
+ /// @param timeout_sec Select timeout in seconds
+ /// @return -1 on error, 0 if no data is available, 1 if data is ready
+ int selectCheck(const unsigned int timeout_sec);
+
+ /// @brief Retains the fd of the open socket
+ int socket_fd_;
+};
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // UNIX_CONTROL_CLIENT_H
diff --git a/src/lib/testutils/user_context_utils.cc b/src/lib/testutils/user_context_utils.cc
new file mode 100644
index 0000000..bad26bc
--- /dev/null
+++ b/src/lib/testutils/user_context_utils.cc
@@ -0,0 +1,137 @@
+// Copyright (C) 2017-2020 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/user_context_utils.h>
+
+using namespace isc::data;
+
+namespace {
+
+/// @brief Encapsulate either a modified copy or a unmodified value
+/// @tparam EP ElementPtr or ConstElementPtr (compiler can't infer which one)
+template<typename EP>
+class Value {
+public:
+ /// @brief Factory for modified copy
+ static Value mkCopy(EP value) { return (Value(value, false)); }
+
+ /// @brief Factory for unmodified original
+ static Value mkShare(EP value) { return (Value(value, true)); }
+
+ /// @brief Get the value
+ /// @return the value
+ EP get() const { return (value_); }
+
+ /// @brief Get the shared status
+ /// @return true if original, false if copy
+ bool isShared() const { return (shared_); }
+
+private:
+ /// @brief Constructor
+ /// @param value the modified copy or unmodified value
+ /// @param shared true if original, false if copy
+ Value(EP value, bool shared) : value_(value), shared_(shared) { }
+
+ /// @brief the value
+ EP value_;
+
+ /// @brief the shared status
+ bool shared_;
+};
+
+/// @brief Recursive helper
+///
+/// @tparam EP ElementPtr or ConstElementPtr (compiler will infer which one)
+/// @param element the element to traverse
+/// @return a modified copy where comment entries were moved to user-context
+/// or the unmodified original argument encapsulated into a Value
+template<typename EP>
+Value<EP> moveComments1(EP element) {
+ bool modified = false;
+
+ // On lists recurse on items
+ if (element->getType() == Element::list) {
+ ElementPtr result = ElementPtr(new ListElement());
+ typedef std::vector<ElementPtr> ListType;
+ const ListType& list = element->listValue();
+ for (ListType::const_iterator it = list.cbegin();
+ it != list.cend(); ++it) {
+ Value<ElementPtr> item = moveComments1(*it);
+ result->add(item.get());
+ if (!item.isShared()) {
+ modified = true;
+ }
+ }
+ if (!modified) {
+ return (Value<EP>::mkShare(element));
+ } else {
+ return (Value<EP>::mkCopy(result));
+ }
+ } else if (element->getType() != Element::map) {
+ return (Value<EP>::mkShare(element));
+ }
+
+ // Process maps: recurse on items
+ ElementPtr result = ElementPtr(new MapElement());
+ bool has_comment = false;
+ typedef std::map<std::string, ConstElementPtr> map_type;
+ const map_type& map = element->mapValue();
+ for (map_type::const_iterator it = map.cbegin(); it != map.cend(); ++it) {
+ if (it->first == "comment") {
+ // Note there is a comment entry to move
+ has_comment = true;
+ } else if (it->first == "user-context") {
+ // Do not traverse user-context entries
+ result->set("user-context", it->second);
+ } else {
+ // Not comment or user-context
+ Value<ConstElementPtr> item = moveComments1(it->second);
+ result->set(it->first, item.get());
+ if (!item.isShared()) {
+ modified = true;
+ }
+ }
+ }
+ // Check if the value should be not modified
+ if (!has_comment && !modified) {
+ return (Value<EP>::mkShare(element));
+ }
+
+ if (has_comment) {
+ // Move the comment entry
+ ConstElementPtr comment = element->get("comment");
+ ElementPtr moved = Element::createMap();
+ moved->set("comment", comment);
+ ConstElementPtr previous = element->get("user-context");
+ // If there is already a user context merge it
+ if (previous) {
+ merge(moved, previous);
+ }
+ result->set("user-context", moved);
+ }
+
+ return (Value<EP>::mkCopy(result));
+}
+
+} // anonymous namespace
+
+namespace isc {
+namespace test {
+
+ElementPtr moveComments(ElementPtr element) {
+ Value<ElementPtr> result = moveComments1(element);
+ return (result.get());
+}
+
+ConstElementPtr moveComments(ConstElementPtr element) {
+ Value<ConstElementPtr> result = moveComments1(element);
+ return (result.get());
+}
+
+} // end of isc::test namespace
+} // end of isc namespace
diff --git a/src/lib/testutils/user_context_utils.h b/src/lib/testutils/user_context_utils.h
new file mode 100644
index 0000000..3a04f41
--- /dev/null
+++ b/src/lib/testutils/user_context_utils.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2017-2020 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 USER_CONTEXT_UTILS_H
+#define USER_CONTEXT_UTILS_H
+
+#include <cc/data.h>
+
+namespace isc {
+namespace test {
+
+/// @brief Move comment entries to user-context
+///
+/// Process an element looking for comment entries in maps and
+/// moving them to user-context entries. As the common case is
+/// no comment and this routine tries to maximize sharing the
+/// standard behavior is just to return the argument unchanged.
+///
+/// @param element
+/// @return a processed copy of element or unmodified element
+isc::data::ElementPtr moveComments(isc::data::ElementPtr element);
+
+/// @brief Move comment entries to user-context (const variant)
+///
+/// @param element
+/// @return a processed copy of element or unmodified element
+isc::data::ConstElementPtr moveComments(isc::data::ConstElementPtr element);
+
+/// extractComments was removed.
+
+}; // end of isc::test namespace
+}; // end of isc namespace
+
+#endif // USER_CONTEXT_UTILS_H
diff --git a/src/lib/testutils/xml_reporting_test_lib.sh.in b/src/lib/testutils/xml_reporting_test_lib.sh.in
new file mode 100644
index 0000000..0d9b235
--- /dev/null
+++ b/src/lib/testutils/xml_reporting_test_lib.sh.in
@@ -0,0 +1,353 @@
+#!/bin/sh
+
+# Copyright (C) 2020-2021 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/.
+
+# shellcheck disable=SC2039
+# SC2039: In POSIX sh, 'local' is undefined.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+############################### Public functions ###############################
+
+# Add an entry to the XML test report.
+report_test_result_in_xml() {
+ # If GTEST_OUTPUT is not defined...
+ if ! test -n "${GTEST_OUTPUT+x}"; then
+ # There is nowhere to report.
+ return
+ fi
+
+ # Declarations
+ local test_name="${1}"; shift
+ local exit_code="${1}"; shift
+ local duration="${1}"; shift # milliseconds
+ local now
+ local test_case
+ local test_suite
+ local xml
+ now=$(date '+%FT%H:%M:%S')
+ test_suite=$(printf '%s' "${test_name}" | cut -d '.' -f 1)
+ test_case=$(printf '%s' "${test_name}" | cut -d '.' -f 2-)
+
+ # Strip the 'xml:' at the start of GTEST_OUTPUT if it is there.
+ xml="${GTEST_OUTPUT}"
+ if test "$(printf '%s' "${xml}" | cut -c 1-4)" = 'xml:'; then
+ xml=$(printf '%s' "${xml}" | cut -c 5-)
+ fi
+ xml="${xml}/${test_suite}.sh.xml"
+
+ # Convert to seconds, but keep the millisecond precision.
+ duration=$(_calculate "${duration} / 1000.0")
+
+ # For test suites that have a single test case and no name for the test
+ # case, name the test case after the test suite.
+ if test -z "${test_case}"; then
+ test_case="${test_suite}"
+ fi
+
+ # Determine result based on exit code. Googletest seems to omit the failed
+ # tests, instead we are explicitly adding them with a 'failed' result.
+ local result
+ if test "${exit_code}" -eq 0; then
+ result='success'
+ else
+ result='failed'
+ fi
+
+ _create_xml "${xml}" "${now}"
+
+ _add_test_suite "${test_suite}" "${xml}" "${now}"
+
+ _add_test_case "${test_suite}" "${test_case}" "${result}" "${duration}" \
+ "${xml}" "${now}"
+}
+
+############################## Private functions ###############################
+
+# Add ${string} after ${reference} in ${file}.
+_add_after() {
+ local string="${1}"; shift
+ local reference="${1}"; shift
+ local file="${1}"; shift
+
+ # Escape all slashes.
+ string=$(printf '%s' "${string}" | sed 's#\/#\\\/#g')
+ reference=$(printf '%s' "${reference}" | sed 's#\/#\\\/#g')
+
+ # Escape all spaces. Only trailing spaces need escaped, but that's harder
+ # and this still empirically works.
+ string=$(printf '%s' "${string}" | sed 's#\ #\\\ #g')
+ reference=$(printf '%s' "${reference}" | sed 's#\ #\\\ #g')
+
+ # Linearize. To avoid this change, add one line at a time.
+ string=$(printf '%s' "${string}" | tr '\n' ' ')
+
+ # Add ${string} after ${reference} in ${file}.
+ # The "\\" followed by newline is for BSD support.
+ sed "/${reference}/a\\
+${string}
+" "${file}" > "${file}.tmp"
+ mv "${file}.tmp" "${file}"
+}
+
+# Add ${string} before ${reference} in ${file}.
+_add_before() {
+ local string="${1}"; shift
+ local reference="${1}"; shift
+ local file="${1}"; shift
+
+ # Get the line number of the reference line.
+ local line_number
+ line_number=$(grep -Fn "${reference}" "${file}" | cut -d ':' -f 1)
+
+ # Escape all slashes.
+ string=$(printf '%s' "${string}" | sed 's#\/#\\\/#g')
+ reference=$(printf '%s' "${reference}" | sed 's#\/#\\\/#g')
+
+ # Escape all spaces. Only trailing spaces need escaped, but that's harder
+ # and this still empirically works.
+ string=$(printf '%s' "${string}" | sed 's#\ #\\\ #g')
+ reference=$(printf '%s' "${reference}" | sed 's#\ #\\\ #g')
+
+ # Linearize. To avoid this change, add one line at a time.
+ string=$(printf '%s' "${string}" | tr '\n' ' ')
+
+ # Add ${string} before ${reference} in ${file}.
+ # The "\\" followed by newline is for BSD support.
+ sed "${line_number}i\\
+${string}
+" "${file}" > "${file}.tmp"
+ mv "${file}.tmp" "${file}"
+}
+
+_add_failure_tag() {
+ local test_case_tag="${1}"; shift
+ local xml="${1}"; shift
+
+ local closing_tag=' </testcase>'
+ local failure_tag
+ local failure_text
+ local linearized_failure_text
+ # Remove characters which are suspected to not be allowed in:
+ # * sed
+ # * XML attribute values
+ # * XML CDATA
+ failure_text=$(printf '%s\n%s' "${ERROR}" "${OUTPUT}" | \
+ sed 's/"/ /g' | sed 's/\[/ /g' | sed 's/\]/ /g')
+ linearized_failure_text=$(printf '%s' "${failure_text}" | tr '\n' ' ')
+ failure_tag=$(printf ' <failure message="%s" type=""><![CDATA[%s]]></failure>' \
+ "${linearized_failure_text}" "${failure_text}")
+
+ # Add.
+ _add_after "${closing_tag}" "${test_case_tag}" "${xml}"
+ _add_after "${failure_tag}" "${test_case_tag}" "${xml}"
+}
+
+# Add test result if not in file.
+_add_test_case() {
+ local test_suite="${1}"; shift
+ local test_case="${1}"; shift
+ local result="${1}"; shift
+ local duration="${1}"; shift
+ local xml="${1}"; shift
+ local now="${1}"; shift
+
+ # Determine the test case tag.
+ local closing_backslash
+ local closing_tag
+ if test "${result}" = 'success'; then
+ closing_backslash=' /'
+ else
+ closing_backslash=
+ fi
+
+ # Create the test XML tag.
+ local test_case_line
+ test_case_line=$(printf ' <testcase name="%s" status="run" result="completed" time="%s" timestamp="%s" classname="%s"%s>' \
+ "${test_case}" "${duration}" "${now}" "${test_suite}" \
+ "${closing_backslash}")
+
+ # Add this test case to all the other test cases.
+ local all_test_cases
+ all_test_cases=$(_print_lines_between_matching_patterns \
+ " <testsuite name=\"${test_suite}\"" ' </testsuite>' "${xml}")
+ all_test_cases=$(printf '%s\n%s' "${all_test_cases}" "${test_case_line}")
+
+ # Find the test following this one.
+ local following_line
+ following_line=$(printf '%s' "${all_test_cases}" | \
+ grep -A1 -F "${test_case_line}" | \
+ grep -Fv "${test_case_line}" || true)
+ if test -n "${following_line}"; then
+ # If found, add it before.
+ _add_before "${test_case_line}" "${following_line}" "${xml}"
+ else
+ # Find the test before this one.
+ local previous_line
+ previous_line=$(printf '%s' "${all_test_cases}" | \
+ grep -B1 -F "${test_case_line}" | \
+ grep -Fv "${test_case_line}" || true)
+ if test -n "${previous_line}"; then
+ # If found, add it after.
+ _add_after "${test_case_line}" "${previous_line}" "${xml}"
+ else
+ # If neither were found, add it as the first test case following the test
+ # suite line.
+ _add_after "${test_case_line}" " <testsuite name=\"${test_suite}\"" "${xml}"
+ fi
+ fi
+
+ # Add the failure tag if it is the case.
+ if test "${result}" != 'success'; then
+ _add_failure_tag "${test_case_line}" "${xml}"
+ fi
+
+ # Retrieve again to include the failure tag that may have just been added
+ # among other tags or lines.
+ all_test_cases=$(_print_lines_between_matching_patterns \
+ " <testsuite name=\"${test_suite}\"" ' </testsuite>' "${xml}")
+
+ # Update attributes for the parent <testsuite> and the global <testsuites>.
+ _update_test_suite_metrics "${test_suite}" "${all_test_cases}" "${xml}" "${now}"
+}
+
+# Add a set of test suite tags if not already present in the XML.
+_add_test_suite() {
+ local test_suite="${1}"; shift
+ local xml="${1}"; shift
+ local now="${1}"; shift
+ local test_suite_line
+ local all_test_suites
+
+ # If test suite tag is already there, then there is nothing to do.
+ if grep -F "<testsuite name=\"${test_suite}\"" "${xml}" \
+ > /dev/null 2>&1; then
+ return
+ fi
+
+ # Create the test suite XML tag.
+ local test_suite_line
+ test_suite_line=$(printf ' <testsuite name="%s" tests="0" failures="0" disabled="0" errors="0" time="0" timestamp="%s">' \
+ "${test_suite}" "${now}")
+
+ # Add this test suite to all the other test suites and sort them.
+ local all_test_suites
+ all_test_suites=$(printf '%s\n%s' " ${test_suite_line}" \
+ "$(grep -E ' <testsuite name=|</testsuites>' "${xml}")")
+
+ # Find the test suite following this one.
+ local following_line
+ following_line=$(printf '%s' "${all_test_suites}" | \
+ grep -A1 -F "${test_suite_line}" | \
+ grep -Fv "${test_suite_line}" || true)
+
+ # Add the test suite tag to the XML.
+ _add_before "${test_suite_line}" "${following_line}" "${xml}"
+ _add_after ' </testsuite>' "${test_suite_line}" "${xml}"
+}
+
+# Calculate the given mathematical expression and print it in a format that
+# matches googletest's time in the XML attribute time="..." which is seconds
+# rounded to 3 decimals.
+_calculate() {
+ awk "BEGIN{print ${*}}";
+}
+
+# Create XML with header and top-level tags if the file doesn't exist.
+_create_xml() {
+ # If file exists and we have set GTEST_OUTPUT_CREATED previously, then there
+ # is nothing to do.
+ if test -f "${xml}" && test -n "${GTEST_OUTPUT_CREATED+x}"; then
+ return;
+ fi
+
+ local xml="${1}"; shift
+ local now="${1}"; shift
+
+ mkdir -p "$(dirname "${xml}")"
+ printf \
+'<?xml version="1.0" encoding="UTF-8"?>
+<testsuites tests="0" failures="0" disabled="0" errors="0" time="0" timestamp="%s" name="AllTests">
+</testsuites>
+' "${now}" > "${xml}"
+
+ # GTEST_OUTPUT_CREATED is not a googletest variable, but our way of allowing
+ # to overwrite XMLs created in a previous test run. The lifetime of
+ # GTEST_OUTPUT_CREATED is extended to the oldest ancestor file who has
+ # sourced this script i.e. the *_test.sh file. So it gets lost from one
+ # *_test.sh to another. The consensus that need to be kept so that this
+ # works correctly are:
+ # * Needless to say, don't set this variable on your own.
+ # * Always call these scripts directly or through `make check`.
+ # Never source test files e.g. `source memfile_tests.sh` or
+ # `. memfile_tests.sh`.
+ # * The ${xml} passed here must be deterministically and uniquely
+ # attributed to the *_test.sh. At the time of this writing, ${xml} is the
+ # part of the name before the dot. So for example, for memfile, all tests
+ # should start with the same thing e.g. `memfile.*`.
+ export GTEST_OUTPUT_CREATED=true
+}
+
+# Print the lines between two matching regex patterns from a file. Excludes the
+# lines that contain the patterns themselves. Matches only the first occurrence.
+_print_lines_between_matching_patterns() {
+ local start_pattern="${1}"; shift
+ local end_pattern="${1}"; shift
+ local file="${1}"; shift
+
+ # Escape all slashes.
+ start_pattern=$(printf '%s' "${start_pattern}" | sed 's#\/#\\\/#g')
+ end_pattern=$(printf '%s' "${end_pattern}" | sed 's#\/#\\\/#g')
+
+ # Print with sed.
+ sed -n "/${start_pattern}/,/${end_pattern}/p;/${end_pattern}/q" "${file}" \
+ | sed '$d' | tail -n +2
+}
+
+# Update the test suite XML attributes with metrics collected from the child
+# test cases.
+_update_test_suite_metrics() {
+ local test_suite="${1}"; shift
+ local all_test_cases="${1}"; shift
+ local xml="${1}"; shift
+ local now="${1}"; shift
+
+ # Get the metrics on the parent test suite.
+ local duration
+ local durations_summed
+ local failures
+ local tests
+ tests=$(printf '%s' "${all_test_cases}" | \
+ grep -Fc '<testcase' || true)
+ failures=$(printf '%s' "${all_test_cases}" | \
+ grep -Fc '<failure' || true)
+ durations_summed=$(printf '%s' "${all_test_cases}" | \
+ grep -Eo 'time="[0-9.]+"' | cut -d '"' -f 2 | xargs | sed 's/ / + /g')
+ duration=$(_calculate "${durations_summed}")
+
+ # Create the test suite XML tag.
+ local test_suite_line
+ test_suite_line=$(printf ' <testsuite name="%s" tests="%s" failures="%s" disabled="0" errors="0" time="%s" timestamp="%s">' \
+ "${test_suite}" "${tests}" "${failures}" "${duration}" "${now}")
+
+ # Update the test suite with the collected metrics.
+ sed "s# <testsuite name=\"${test_suite}\".*>#${test_suite_line}#g" \
+ "${xml}" > "${xml}.tmp"
+ mv "${xml}.tmp" "${xml}"
+
+ # Create the test suites XML tag.
+ local test_suites_line
+ test_suites_line=$(printf '<testsuites tests="%s" failures="%s" disabled="0" errors="0" time="%s" timestamp="%s" name="AllTests">' \
+ "${tests}" "${failures}" "${duration}" "${now}")
+
+ # Update the test suites with the collected metrics.
+ sed "s#<testsuites .*>#${test_suites_line}#g" \
+ "${xml}" > "${xml}.tmp"
+ mv "${xml}.tmp" "${xml}"
+}