diff options
Diffstat (limited to 'src/lib/testutils')
-rw-r--r-- | src/lib/testutils/Makefile.am | 30 | ||||
-rw-r--r-- | src/lib/testutils/Makefile.in | 824 | ||||
-rw-r--r-- | src/lib/testutils/README | 2 | ||||
-rw-r--r-- | src/lib/testutils/dhcp_test_lib.sh.in | 1176 | ||||
-rw-r--r-- | src/lib/testutils/gtest_utils.h | 92 | ||||
-rw-r--r-- | src/lib/testutils/io_utils.cc | 117 | ||||
-rw-r--r-- | src/lib/testutils/io_utils.h | 45 | ||||
-rw-r--r-- | src/lib/testutils/lib_load_test_fixture.h | 148 | ||||
-rw-r--r-- | src/lib/testutils/log_utils.cc | 131 | ||||
-rw-r--r-- | src/lib/testutils/log_utils.h | 105 | ||||
-rw-r--r-- | src/lib/testutils/multi_threading_utils.h | 38 | ||||
-rw-r--r-- | src/lib/testutils/sandbox.h | 69 | ||||
-rw-r--r-- | src/lib/testutils/test_to_element.cc | 34 | ||||
-rw-r--r-- | src/lib/testutils/test_to_element.h | 91 | ||||
-rw-r--r-- | src/lib/testutils/threaded_test.cc | 73 | ||||
-rw-r--r-- | src/lib/testutils/threaded_test.h | 88 | ||||
-rw-r--r-- | src/lib/testutils/unix_control_client.cc | 139 | ||||
-rw-r--r-- | src/lib/testutils/unix_control_client.h | 66 | ||||
-rw-r--r-- | src/lib/testutils/user_context_utils.cc | 137 | ||||
-rw-r--r-- | src/lib/testutils/user_context_utils.h | 37 | ||||
-rw-r--r-- | src/lib/testutils/xml_reporting_test_lib.sh.in | 353 |
21 files changed, 3795 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..d7d2ce6 --- /dev/null +++ b/src/lib/testutils/Makefile.in @@ -0,0 +1,824 @@ +# 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_cpp20.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_netconf.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_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@ +DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@ +DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@ +DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@ +DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_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_NETCONF = @HAVE_NETCONF@ +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@ +LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@ +LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@ +LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@ +LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@ +LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@ +LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@ +LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@ +LIBYANG_LIBS = @LIBYANG_LIBS@ +LIBYANG_PREFIX = @LIBYANG_PREFIX@ +LIBYANG_VERSION = @LIBYANG_VERSION@ +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_PLUGINS_PATH = @SR_PLUGINS_PATH@ +SR_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@ +SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@ +SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@ +SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +SYSREPO_PREFIX = @SYSREPO_PREFIX@ +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..6260d2d --- /dev/null +++ b/src/lib/testutils/dhcp_test_lib.sh.in @@ -0,0 +1,1176 @@ +#!/bin/sh + +# Copyright (C) 2014-2023 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" +KEA_DHCP4_LOAD_MARKER_FILE="@abs_top_builddir@/src/bin/dhcp4/tests/load_marker.txt" +KEA_DHCP4_UNLOAD_MARKER_FILE="@abs_top_builddir@/src/bin/dhcp4/tests/unload_marker.txt" +KEA_DHCP4_SRV_CONFIG_MARKER_FILE="@abs_top_builddir@/src/bin/dhcp4/tests/srv_config_marker_file.txt" +KEA_DHCP6_LOAD_MARKER_FILE="@abs_top_builddir@/src/bin/dhcp6/tests/load_marker.txt" +KEA_DHCP6_UNLOAD_MARKER_FILE="@abs_top_builddir@/src/bin/dhcp6/tests/unload_marker.txt" +KEA_DHCP6_SRV_CONFIG_MARKER_FILE="@abs_top_builddir@/src/bin/dhcp6/tests/srv_config_marker_file.txt" + +# 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-}" \ + "${KEA_DHCP4_LOAD_MARKER_FILE-}" \ + "${KEA_DHCP4_UNLOAD_MARKER_FILE-}" \ + "${KEA_DHCP4_SRV_CONFIG_MARKER_FILE-}" \ + "${DHCP6_CFG_FILE-}" \ + "${KEA_DHCP6_LOAD_MARKER_FILE-}" \ + "${KEA_DHCP6_UNLOAD_MARKER_FILE-}" \ + "${KEA_DHCP6_SRV_CONFIG_MARKER_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..182d0ef --- /dev/null +++ b/src/lib/testutils/lib_load_test_fixture.h @@ -0,0 +1,148 @@ +// 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 <dhcpsrv/cfgmgr.h> +#include <process/daemon.h> +#include <testutils/gtest_utils.h> + +namespace isc { +namespace test { + +/// @brief Test fixture for testing loading and unloading of hook libraries. +class LibLoadTest : public ::testing::Test { +public: + /// @brief Constructor. Unloads any previously loaded libraries. + /// + /// @param lib_so_name_ full pathname to the library so file under test + LibLoadTest(const std::string lib_so_name = "no-lib-specified") + : lib_so_name_(lib_so_name) { + unloadLibraries(); + } + + virtual void SetUp() { + valid_params_ = validConfigParams(); + } + + /// @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 Verifies that a valid daemon can load and unload a + /// library multiple times. + /// + /// @param daemon_name name of the daemon that should try to load the library + /// @param family Protocol family of the loading daemon, either + /// AF_INET or AF_INET6. Defaults to AF_INET. + /// @param params ElementPtr to set of parameters that are valid for the library. + /// Defaults to an empty pointer. + /// + /// @note: implemented here to avoid dependency with the dhcpsrv library. + void validDaemonTest(const std::string& daemon_name, + uint16_t family = AF_INET, + const isc::data::ElementPtr& params = isc::data::ElementPtr()) { + // Set family and daemon's proc name. + isc::dhcp::CfgMgr::instance().setFamily(family); + isc::process::Daemon::setProcName(daemon_name); + + clearLibraries(); + + // Adding the library to the list of libraries should work. + ASSERT_NO_THROW_LOG(addLibrary(lib_so_name_, params)); + + // Should be able to load and unload the library more than once. + ASSERT_NO_THROW_LOG(loadLibraries()); + ASSERT_NO_THROW_LOG(unloadLibraries()); + + ASSERT_NO_THROW_LOG(loadLibraries()); + ASSERT_NO_THROW_LOG(unloadLibraries()); + } + + /// @brief Verifies that an invalid daemon cannot load the library. + /// + /// @param libname full path to the library's SO. Typically this + /// value is defined in the Makefile (e.g. -DLIBDHCP_BOOTP_SO=...) + /// @param daemon_name name of the daemon that should try to load the library + /// @param family Protocol family of the loading daemon, either + /// AF_INET or AF_INET6. Defaults to AF_INET. + /// @param params ElementPtr to set of parameters that are valid + /// for the library. Defaults to an empty pointer. + /// + /// @note: implemented here to avoid dependency with the dhcpsrv library. + void invalidDaemonTest(const std::string& daemon_name, + uint16_t family = AF_INET, + const isc::data::ElementPtr& params = isc::data::ElementPtr()) { + // Set family and daemon's proc name. + isc::dhcp::CfgMgr::instance().setFamily(family); + isc::process::Daemon::setProcName(daemon_name); + + clearLibraries(); + + // Adding the library to the list of libraries should work. + ASSERT_NO_THROW_LOG(addLibrary(lib_so_name_, params)); + + // Loading the library should fail. + ASSERT_FALSE(loadLibraries()) << "library: " << lib_so_name_ + << ", should not have loaded for: " << daemon_name; + } + + /// @brief Creates a set configuration parameters valid for the library. + virtual isc::data::ElementPtr validConfigParams() { + return (isc::data::Element::createMap()); + } + + /// @brief full pathname to the library so file under test; + std::string lib_so_name_; + + /// @brief Libraries + isc::hooks::HookLibsCollection libraries_; + + /// @brief Contains a set configuration parameters valid for the library. + isc::data::ElementPtr valid_params_; +}; + +} // 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..7c6dc0f --- /dev/null +++ b/src/lib/testutils/xml_reporting_test_lib.sh.in @@ -0,0 +1,353 @@ +#!/bin/sh + +# Copyright (C) 2020-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/. + +# 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}" +} |