diff --git a/configure.ac b/configure.ac index 3bf41b3..a6ad86e 100644 --- a/configure.ac +++ b/configure.ac @@ -1,621 +1,631 @@ # -*- Autoconf -*- # Process this file with autoconf to produce a configure script. # bootstrap / init AC_PREREQ([2.61]) AC_INIT([booth], [1.0], [users@clusterlabs.org]) AM_INIT_AUTOMAKE([-Wno-portability subdir-objects]) AC_CONFIG_SRCDIR([src/main.c]) AC_CONFIG_HEADER([src/b_config.h src/booth_config.h]) AC_CANONICAL_HOST AC_LANG([C]) AC_SUBST(WITH_LIST, [""]) dnl Fix default variables - "prefix" variable if not specified if test "$prefix" = "NONE"; then prefix="/usr" dnl Fix "localstatedir" variable if not specified if test "$localstatedir" = "\${prefix}/var"; then localstatedir="/var" fi dnl Fix "sysconfdir" variable if not specified if test "$sysconfdir" = "\${prefix}/etc"; then sysconfdir="/etc" fi dnl Fix "libdir" variable if not specified if test "$libdir" = "\${exec_prefix}/lib"; then if test -e /usr/lib64; then libdir="/usr/lib64" else libdir="/usr/lib" fi fi fi if test "$srcdir" = "."; then AC_MSG_NOTICE([building in place srcdir:$srcdir]) AC_DEFINE([BUILDING_IN_PLACE], 1, [building in place]) else AC_MSG_NOTICE([building out of tree srcdir:$srcdir]) fi # Checks for programs. # check stolen from gnulib/m4/gnu-make.m4 if ! ${MAKE-make} --version /cannot/make/this >/dev/null 2>&1; then AC_MSG_ERROR([you don't seem to have GNU make; it is required]) fi AC_PROG_CC AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MAKE_SET AC_PROG_RANLIB AC_PATH_PROGS(PKGCONFIG, pkg-config) AC_PATH_PROGS(ASCIIDOC, asciidoc) AC_PATH_PROGS(ASCIIDOCTOR, asciidoctor) AC_PATH_PROGS(A2X, a2x) AC_PATH_PROGS(XML2CONFIG, xml2-config) AM_CONDITIONAL(IS_ASCIIDOC, test x"${ASCIIDOC}" != x"") AM_CONDITIONAL(IS_A2X, test x"${A2X}" != x"") AM_CONDITIONAL(BUILD_ASCIIDOC, test x"${A2X}" != x"" || test x"${ASCIIDOCTOR}" != x"") AM_CONDITIONAL(BUILD_ASCIIDOC_HTML_MAN, (test "x${ASCIIDOC}" != "x" || test x"${ASCIIDOCTOR}" != x"") && test "x$with_html_man" = "xyes") # libgcrypt or mhash for hmac libgcrypt_installed="yes" AC_CHECK_HEADERS(gcrypt.h, , [libgcrypt_installed="no"],) AC_CHECK_LIB(gcrypt, gcry_md_open, , [libgcrypt_installed="no"]) AM_CONDITIONAL(BUILD_AUTH_C, test "x${libgcrypt_installed}" = "xyes") if test "x$libgcrypt_installed" = "xno"; then mhash_installed="yes" AC_CHECK_HEADERS(mhash.h, , [mhash_installed="no"],) AC_CHECK_LIB(mhash, mhash_init, , [mhash_installed="no"]) AM_CONDITIONAL(BUILD_AUTH_C, test "x${mhash_installed}" = "xyes") fi AC_MSG_CHECKING(for special libxml2 includes) if test "x$XML2CONFIG" = "x"; then AC_MSG_ERROR(libxml2 config not found) else XML2HEAD="`$XML2CONFIG --cflags`" AC_MSG_RESULT($XML2HEAD) AC_CHECK_LIB(xml2, xmlReadMemory) fi CPPFLAGS="$CPPFLAGS $XML2HEAD" PKG_CHECK_MODULES(GLIB, [glib-2.0]) +# Python casing, prefer 3.3+ to 2.{6...} +AM_PATH_PYTHON([3.3], , [PYTHON=:]) +if test "x$PYTHON" = x:; then + AM_PATH_PYTHON([2.6]) +fi +PYTHON_SHEBANG="$PYTHON ${PYTHON_OPTS--Es}" +AC_ARG_VAR([PYTHON_SHEBANG], [Python invocation used in shebangs]) + # Checks for header files. AC_FUNC_ALLOCA AC_HEADER_DIRENT AC_HEADER_STDC AC_HEADER_SYS_WAIT AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h netdb.h netinet/in.h stdint.h \ stdlib.h string.h sys/ioctl.h sys/param.h sys/socket.h \ sys/time.h syslog.h unistd.h sys/types.h getopt.h malloc.h \ sys/sockio.h utmpx.h]) AC_CHECK_HEADERS(heartbeat/glue_config.h) AC_CHECK_HEADERS(mhash.h) AC_CHECK_HEADER([zlib.h], [AC_SUBST(ZLIB_LIBS, ["-lz"])], [AC_MSG_ERROR([zlib development files required])]) saved_CPPFLAGS="${CPPFLAGS}" CPPFLAGS="${CPPFLAGS} ${GLIB_CFLAGS}" AC_CHECK_HEADER([pacemaker/crm/services.h], [], [AC_MSG_ERROR([pacemaker/crm/services.h header required])]) CPPFLAGS="${saved_CPPFLAGS}" # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST AC_TYPE_UID_T AC_C_INLINE AC_TYPE_INT16_T AC_TYPE_INT32_T AC_TYPE_INT64_T AC_TYPE_INT8_T AC_TYPE_SIZE_T AC_TYPE_SSIZE_T AC_HEADER_TIME AC_TYPE_UINT16_T AC_TYPE_UINT32_T AC_TYPE_UINT64_T AC_TYPE_UINT8_T AC_C_VOLATILE # Checks for library functions. AC_FUNC_CLOSEDIR_VOID AC_FUNC_ERROR_AT_LINE AC_REPLACE_FNMATCH AC_FUNC_FORK AC_PROG_GCC_TRADITIONAL AC_FUNC_MALLOC AC_FUNC_MEMCMP AC_FUNC_REALLOC AC_FUNC_SELECT_ARGTYPES AC_TYPE_SIGNAL AC_FUNC_VPRINTF AC_CHECK_FUNCS([alarm alphasort atexit bzero dup2 endgrent endpwent fcntl \ getcwd getpeerucred getpeereid gettimeofday inet_ntoa memmove \ memset mkdir scandir select socket strcasecmp strchr strdup \ strerror strrchr strspn strstr \ sched_get_priority_max sched_setscheduler]) AC_CONFIG_FILES([Makefile src/Makefile docs/Makefile conf/Makefile]) AC_CONFIG_FILES([conf/booth-arbitrator.service conf/booth@.service]) - +AC_CONFIG_FILES([script/unit-test.py test/runtests.py], + dnl Following required at least for "make check" + [chmod +x test/runtests.py]) AC_CONFIG_FILES([script/service-runnable], [chmod +x script/service-runnable]) # =============================================== # Helpers # =============================================== ## helper for CC stuff cc_supports_flag() { local CFLAGS="-Werror $@" AC_MSG_CHECKING(whether $CC supports "$@") AC_COMPILE_IFELSE([AC_LANG_SOURCE([[int main(){return 0;}]])], [RC=0; AC_MSG_RESULT(yes)],[RC=1; AC_MSG_RESULT(no)]) return $RC } ## extract header or define try_extract_header_define() { AC_MSG_CHECKING(if $2 in $1 exists) Cfile=$srcdir/extract_define.$2.${$} printf "#include \n" > ${Cfile}.c printf "#include <%s>\n" $1 >> ${Cfile}.c printf "int main(int argc, char **argv) {\n" >> ${Cfile}.c printf "#ifdef %s\n" $2 >> ${Cfile}.c printf "printf(\"%%s\", %s);\n" $2 >> ${Cfile}.c printf "#endif \n return 0; }\n" >> ${Cfile}.c $CC $CFLAGS ${Cfile}.c -o ${Cfile} 2>/dev/null value= if test -x ${Cfile}; then value=`${Cfile} 2>/dev/null` fi if test x"${value}" == x""; then value=$3 AC_MSG_RESULT(default: $value) else AC_MSG_RESULT($value) fi printf $value rm -rf ${Cfile}.c ${Cfile} ${Cfile}.dSYM ${Cfile}.gcno } ## cleanup AC_MSG_NOTICE(Sanitizing prefix: ${prefix}) case $prefix in NONE) prefix=/usr/local;; esac AC_MSG_NOTICE(Sanitizing exec_prefix: ${exec_prefix}) case $exec_prefix in dnl For consistency with Corosync, map NONE->$prefix NONE) exec_prefix=$prefix;; prefix) exec_prefix=$prefix;; esac ## local defines PACKAGE_FEATURES="" LINT_FLAGS="-weak -unrecog +posixlib +ignoresigns -fcnuse \ -badflag -D__gnuc_va_list=va_list -D__attribute\(x\)=" # local options AC_ARG_ENABLE([fatal-warnings], [ --enable-fatal-warnings : enable fatal warnings. ], [ default="no" ]) AC_ARG_ENABLE([debug], [ --enable-debug : enable debug build. ], [ default="no" ]) AC_ARG_ENABLE([user-flags], [ --enable-user-flags : rely on user environment. ], [ default="no" ]) AC_ARG_ENABLE([coverage], [ --enable-coverage : coverage analysis of the codebase. ], [ default="no" ]) AC_ARG_WITH([initddir], [ --with-initddir=DIR : path to init script directory. ], [ INITDDIR="$withval" ], [ INITDDIR="$sysconfdir/init.d" ]) test -s .git_info && GIT_VER="`cat .git_info`" AC_ARG_WITH([build-version], [ --with-build-version=STR : build version ], [ BOOTH_BUILD_VERSION="$withval" ], [ BOOTH_BUILD_VERSION="${GIT_VER:-$PACKAGE_VERSION}" ]) AC_ARG_WITH([html_man], [ --without-html_man : Avoid generating man pages in HTML.], [], [with_html_man=yes]) AC_ARG_WITH([glue], [ --without-glue : Avoid libraries from (cluster-)glue project.], [], [with_glue=yes]) # figure out logging provider logging_provider="" if test "x$logging_provider" = "x" && test "x$with_glue" = "xyes"; then AC_CHECK_LIB([plumb], [cl_log], [logging_provider="libplumb"]) fi if test "x$logging_provider" = "x" && test "x$with_glue" = "xno"; then AC_CHECK_LIB([qb], [qb_log_real_], [logging_provider="libqb"]) fi case "$logging_provider" in libplumb) LOGGER="ha_logger" ;; libqb) PKG_CHECK_MODULES([LIBQB], [libqb]) AC_DEFINE([LOGGING_LIBQB], [], [use libqb as a logging provider]) PKG_CHECK_MODULES([LIBQB1], [libqb >= 1.0], [AC_DEFINE([LOGGING_LIBQB_MAJOR], [1], [libqb major version lower bound])], [AC_MSG_WARN([[syslog identifier will not get changed]])]) LOGGER="logger -t booth-script" ;; *) AC_MSG_ERROR([logging provider required (libplumb, or libqb when --without-glue)]) ;; esac AM_CONDITIONAL([LOGGING_LIBQB], [test "x$logging_provider" = "xlibqb"]) AC_SUBST([LOGGER]) # figure out range2random provider range2random_provider="" if test "x$range2random_provider" = "x" && test "x$with_glue" = "xyes"; then AC_CHECK_LIB([plumb], [get_next_random], [range2random_provider="libplumb"]) AC_CHECK_DECL([cl_rand_from_interval], [], [range2random_provider=""], [#include ]) fi if test "x$range2random_provider" = "x" && test "x$with_glue" = "xno"; then AC_CHECK_LIB([glib-2.0], [g_random_int_range], [range2random_provider="glib"]) fi case "$range2random_provider" in libplumb) ;; glib) PKG_CHECK_MODULES([GLIB], [glib-2.0]) AC_DEFINE([RANGE2RANDOM_GLIB], [], [use glib as a range2random provider]) ;; *) AC_MSG_ERROR([range2random provider required (libplumb, or glib when --without-glue)]) ;; esac AM_CONDITIONAL([RANGE2RANDOM_GLIB], [test "x$range2random_provider" = "xglib"]) # figure out nametag/distinguished-role provider nametag_provider="" if test "x$nametag_provider" = "x" && test "x$with_glue" != "xno"; then AC_CHECK_LIB([plumbgpl], [set_proc_title], [nametag_provider="libplumbgpl"]) fi if test "x$nametag_provider" = "x" && test "x$with_glue" = "xno"; then AC_SEARCH_LIBS([sd_notify], [systemd systemd-daemon], [nametag_provider="libsystemd"]) fi NOTIFY_ACCESS_SWITCH='# ' case "$nametag_provider" in libplumbgpl) ;; libsystemd) PKG_CHECK_MODULES([LIBSYSTEMD], [libsystemd],, [ PKG_CHECK_MODULES([LIBSYSTEMD], [libsystemd-daemon]) ]) AC_DEFINE([NAMETAG_LIBSYSTEMD], [], [use libsystemd as a nametag provider]) NOTIFY_ACCESS_SWITCH= ;; *) AC_MSG_ERROR([nametag provider required (libplumbgpl, or libsystemd when --without-glue)]) ;; esac AM_CONDITIONAL([NAMETAG_LIBSYSTEMD], [test "x$nametag_provider" = "xlibsystemd"]) AC_SUBST([NOTIFY_ACCESS_SWITCH]) # figure out if "coredump nursing" supported and desired coredump_nursing="no" if test "x$with_glue" != "xno"; then AC_CHECK_LIB([plumb], [cl_enable_coredumps], [coredump_nursing="libplumb"]) fi if test "x$coredump_nursing" != "xno"; then AC_DEFINE(COREDUMP_NURSING, [], [eligible for coredump nursing]) fi AM_CONDITIONAL([COREDUMP_NURSING], [test "x$coredump_nursing" != "xno"]) # define CRM daemon user & group CRM_DAEMON_USER=`try_extract_header_define glue_config.h HA_CCMUSER hacluster` AC_DEFINE_UNQUOTED(CRM_DAEMON_USER,"$CRM_DAEMON_USER", User to run Booth daemon as) AC_SUBST(CRM_DAEMON_USER) CRM_DAEMON_GROUP=`try_extract_header_define glue_config.h HA_APIGROUP haclient` AC_DEFINE_UNQUOTED(CRM_DAEMON_GROUP,"$CRM_DAEMON_GROUP", Group to run Booth daemon as) AC_SUBST(CRM_DAEMON_GROUP) # OS detection # THIS SECTION MUST DIE! CP=cp OS_LDL="-ldl" have_linux="no" case "$host_os" in *linux*) AC_DEFINE_UNQUOTED([BOOTH_LINUX], [1], [Compiling for Linux platform]) OS_CFLAGS="" OS_CPPFLAGS="-D_GNU_SOURCE" OS_LDFLAGS="" OS_DYFLAGS="-rdynamic" DARWIN_OPTS="" have_linux="yes" ;; darwin*) AC_DEFINE_UNQUOTED([BOOTH_DARWIN], [1], [Compiling for Darwin platform]) CP=rsync OS_CFLAGS="" OS_CPPFLAGS="" OS_LDFLAGS="" OS_DYFLAGS="" DARWIN_OPTS="-dynamiclib -bind_at_load \ -current_version ${SONAME} \ -compatibility_version ${SONAME} -install_name \$(libdir)/\$(@)" AC_DEFINE_UNQUOTED([MAP_ANONYMOUS], [MAP_ANON], [Shared memory define for Darwin platform]) AC_DEFINE_UNQUOTED([PATH_MAX], [4096], [Number of chars in a path name including nul]) AC_DEFINE_UNQUOTED([NAME_MAX], [255], [Number of chars in a file name]) ;; *bsd*) AC_DEFINE_UNQUOTED([BOOTH_BSD], [1], [Compiling for BSD platform]) AC_DEFINE_UNQUOTED([MAP_ANONYMOUS], [MAP_ANON], [Shared memory define for Darwin platform]) OS_CFLAGS="" OS_CPPFLAGS="-I/usr/local/include" OS_LDFLAGS="-L/usr/local/lib" OS_DYFLAGS="-export-dynamic" DARWIN_OPTS="" OS_LDL="" case "$host_os" in *freebsd[[234567]]*) ;; *freebsd*) AC_DEFINE_UNQUOTED([BOOTH_FREEBSD_GE_8], [1], [Compiling for FreeBSD >= 8 platform]) ;; esac ;; *solaris*) AC_DEFINE_UNQUOTED([BOOTH_SOLARIS], [1], [Compiling for Solaris platform]) AC_DEFINE_UNQUOTED([TS_CLASS], [1], [Prevent being scheduled RR]) AC_DEFINE_UNQUOTED([_SEM_SEMUN_UNDEFINED], [1], [The semun structure is undefined]) CP=rsync OS_CFLAGS="" OS_CPPFLAGS="-D_REENTRANT" OS_LDFLAGS="" OS_DYFLAGS="-Wl,-z,lazyload" DARWIN_OPTS="" SOLARIS_OPTS=" " ;; *) AC_MSG_ERROR([Unsupported OS? hmmmm]) ;; esac AC_SUBST(CP) # *FLAGS handling goes here ENV_CFLAGS="$CFLAGS" ENV_CPPFLAGS="$CPPFLAGS" ENV_LDFLAGS="$LDFLAGS" # debug build stuff if test "x${enable_debug}" = xyes; then AC_DEFINE_UNQUOTED([DEBUG], [1], [Compiling Debugging code]) OPT_CFLAGS="-O0 -U_FORTIFY_SOURCE" PACKAGE_FEATURES="$PACKAGE_FEATURES debug" else OPT_CFLAGS="-O3" fi # gdb flags if test "x${GCC}" = xyes; then GDB_FLAGS="-ggdb3" else GDB_FLAGS="-g" fi dnl Check for POSIX clock_gettime dnl AC_CACHE_CHECK([have clock_gettime],ac_cv_HAVE_CLOCK_GETTIME,[ AC_TRY_COMPILE([ #include ], [ struct timespec tv; clock_gettime(CLOCK_REALTIME, &tv); return 0;], ac_cv_HAVE_CLOCK_GETTIME=yes,ac_cv_HAVE_CLOCK_GETTIME=no,ac_cv_HAVE_CLOCK_GETTIME=cross)]) AM_CONDITIONAL(BUILD_TIMER_C, test x"$ac_cv_HAVE_CLOCK_GETTIME" = x"yes") # extra warnings EXTRA_WARNINGS="" WARNLIST=" all shadow missing-prototypes missing-declarations strict-prototypes declaration-after-statement pointer-arith write-strings bad-function-cast missing-format-attribute format=2 format-security format-nonliteral no-long-long unsigned-char gnu89-inline no-strict-aliasing " for j in $WARNLIST; do if cc_supports_flag -W$j; then EXTRA_WARNINGS="$EXTRA_WARNINGS -W$j"; fi done if test "x${enable_coverage}" = xyes && \ cc_supports_flag -ftest-coverage && \ cc_supports_flag -fprofile-arcs ; then AC_MSG_NOTICE([Enabling Coverage (enable -O0 by default)]) OPT_CFLAGS="-O0" COVERAGE_CFLAGS="-ftest-coverage -fprofile-arcs" COVERAGE_LDFLAGS="-ftest-coverage -fprofile-arcs" PACKAGE_FEATURES="$PACKAGE_FEATURES coverage" else COVERAGE_CFLAGS="" COVERAGE_LDFLAGS="" fi if test "x${enable_ansi}" = xyes && \ cc_supports_flag -std=iso9899:199409 ; then AC_MSG_NOTICE([Enabling ANSI Compatibility]) ANSI_CPPFLAGS="-ansi -D_GNU_SOURCE -DANSI_ONLY" PACKAGE_FEATURES="$PACKAGE_FEATURES ansi" else ANSI_CPPFLAGS="" fi if test "x${enable_fatal_warnings}" = xyes && \ cc_supports_flag -Werror ; then AC_MSG_NOTICE([Enabling Fatal Warnings (-Werror)]) WERROR_CFLAGS="-Werror" PACKAGE_FEATURES="$PACKAGE_FEATURES fatal-warnings" else WERROR_CFLAGS="" fi # don't add addtional cflags if test "x${enable_user_flags}" = xyes; then OPT_CFLAGS="" GDB_FLAGS="" EXTRA_WARNINGS="" fi # final build of *FLAGS CFLAGS="$ENV_CFLAGS $OPT_CFLAGS $GDB_FLAGS $OS_CFLAGS \ $COVERAGE_CFLAGS $EXTRA_WARNINGS $WERROR_CFLAGS $NSS_CFLAGS" CPPFLAGS="$ENV_CPPFLAGS $ANSI_CPPFLAGS $OS_CPPFLAGS $GLIB_CFLAGS $RESMON_CFLAGS" LDFLAGS="$ENV_LDFLAGS $COVERAGE_LDFLAGS $OS_LDFLAGS" # substitute what we need: AC_SUBST([INITDDIR]) AC_SUBST([OS_DYFLAGS]) AC_SUBST([OS_LDL]) AM_CONDITIONAL(BUILD_DARWIN, test -n "${DARWIN_OPTS}") AM_CONDITIONAL(BUILD_SOLARIS, test -n "${SOLARIS_OPTS}") AC_SUBST([DARWIN_OPTS]) AC_SUBST([SOLARIS_OPTS]) AM_CONDITIONAL(BUILD_HTML_DOCS, test -n "${GROFF}") AC_SUBST([LINT_FLAGS]) AC_DEFINE_UNQUOTED([SOCKETDIR], "$(eval echo ${SOCKETDIR})", [Socket directory]) BOOTH_LIB_DIR=${localstatedir}/lib/booth BOOTH_CORE_DIR=${localstatedir}/lib/booth/cores BOOTHSYSCONFDIR=${sysconfdir}/booth AC_SUBST([HAVE_LOG_CIB_DIFF]) AC_SUBST([HAVE_XML_LOG_PATCHSET]) AC_SUBST([BOOTH_LIB_DIR]) AC_SUBST([BOOTH_CORE_DIR]) AC_SUBST([BOOTHSYSCONFDIR]) AC_SUBST([BOOTH_BUILD_VERSION]) AC_DEFINE_UNQUOTED([BOOTH_LIB_DIR], "$(eval echo ${BOOTH_LIB_DIR})", [booth lib directory]) AC_DEFINE_UNQUOTED([BOOTH_CORE_DIR], "$(eval echo ${BOOTH_CORE_DIR})", [booth working directory]) AC_DEFINE_UNQUOTED([BOOTHSYSCONFDIR], "$(eval echo ${BOOTHSYSCONFDIR})", [booth config directory]) AC_DEFINE_UNQUOTED([PACKAGE_FEATURES], "${PACKAGE_FEATURES}", [booth built-in features]) AC_DEFINE_UNQUOTED([BOOTH_BUILD_VERSION], "${BOOTH_BUILD_VERSION}", [booth build version]) AC_OUTPUT AC_MSG_RESULT([]) AC_MSG_RESULT([$PACKAGE configuration:]) AC_MSG_RESULT([ Version = ${VERSION} (Build: ${BOOTH_BUILD_VERSION})]) AC_MSG_RESULT([ Prefix = ${prefix}]) AC_MSG_RESULT([ Executables = ${sbindir}]) AC_MSG_RESULT([ Man pages = ${mandir}]) AC_MSG_RESULT([ Doc dir = ${docdir}]) AC_MSG_RESULT([ Libraries = ${libdir}]) AC_MSG_RESULT([ Header files = ${includedir}]) AC_MSG_RESULT([ Arch-independent files = ${datadir}]) AC_MSG_RESULT([ State information = ${localstatedir}]) AC_MSG_RESULT([ System configuration = ${sysconfdir}]) AC_MSG_RESULT([ System init.d directory = ${INITDDIR}]) AC_MSG_RESULT([ booth config dir = ${BOOTHSYSCONFDIR}]) AC_MSG_RESULT([ SOCKETDIR = ${SOCKETDIR}]) AC_MSG_RESULT([ Features = ${PACKAGE_FEATURES}]) AC_MSG_RESULT([ Logging provider = ${logging_provider}]) AC_MSG_RESULT([ Range2random provider = ${range2random_provider}]) AC_MSG_RESULT([ Nametag provider = ${nametag_provider}]) AC_MSG_RESULT([ Coredump nursing = ${coredump_nursing}]) AC_MSG_RESULT([ Working directory = ${BOOTH_CORE_DIR}]) AC_MSG_RESULT([ HA group name = ${CRM_DAEMON_GROUP}]) AC_MSG_RESULT([ HA user name = ${CRM_DAEMON_USER}]) AC_MSG_RESULT([]) AC_MSG_RESULT([$PACKAGE build info:]) AC_MSG_RESULT([ Library SONAME = ${SONAME}]) LIB_MSG_RESULT(m4_shift(local_soname_list))dnl AC_MSG_RESULT([ Default optimization = ${OPT_CFLAGS}]) AC_MSG_RESULT([ Default debug options = ${GDB_CFLAGS}]) AC_MSG_RESULT([ Extra compiler warnings = ${EXTRA_WARNING}]) AC_MSG_RESULT([ Env. defined CFLAG = ${ENV_CFLAGS}]) AC_MSG_RESULT([ Env. defined CPPFLAGS = ${ENV_CPPFLAGS}]) AC_MSG_RESULT([ Env. defined LDFLAGS = ${ENV_LDFLAGS}]) AC_MSG_RESULT([ OS defined CFLAGS = ${OS_CFLAGS}]) AC_MSG_RESULT([ OS defined CPPFLAGS = ${OS_CPPFLAGS}]) AC_MSG_RESULT([ OS defined LDFLAGS = ${OS_LDFLAGS}]) AC_MSG_RESULT([ OS defined LDL = ${OS_LDL}]) AC_MSG_RESULT([ OS defined DYFLAGS = ${OS_DYFLAGS}]) AC_MSG_RESULT([ ANSI defined CPPFLAGS = ${ANSI_CPPFLAGS}]) AC_MSG_RESULT([ Coverage CFLAGS = ${COVERAGE_CFLAGS}]) AC_MSG_RESULT([ Coverage LDFLAGS = ${COVERAGE_LDFLAGS}]) AC_MSG_RESULT([ Fatal War. CFLAGS = ${WERROR_CFLAGS}]) AC_MSG_RESULT([ Final CFLAGS = ${CFLAGS}]) AC_MSG_RESULT([ Final CPPFLAGS = ${CPPFLAGS}]) AC_MSG_RESULT([ Final LDFLAGS = ${LDFLAGS}]) diff --git a/script/unit-test.py b/script/unit-test.py.in old mode 100755 new mode 100644 similarity index 99% rename from script/unit-test.py rename to script/unit-test.py.in index 399528e..4f3cf62 --- a/script/unit-test.py +++ b/script/unit-test.py.in @@ -1,637 +1,637 @@ -#!/usr/bin/python +#!@PYTHON_SHEBANG@ # vim: fileencoding=utf-8 # see http://stackoverflow.com/questions/728891/correct-way-to-define-python-source-code-encoding # NOTE: setting the encoding is needed as non-ASCII characters are contained # FIXME: apparently, pexpect.EOF is not being excepted properly import os, sys, time, signal, tempfile, socket, posix, time import re, shutil, pexpect, logging, pprint import random, copy, glob, traceback # Don't make that much sense - function/line is write(). # Would have to use traceback.extract_stack() manually. # %(funcName)10.10s:%(lineno)3d %(levelname)8s # The second ":" is to get correct syntax highlightning, # eg. messages with ERROR etc. are red in vim. default_log_format = '%(asctime)s: : %(message)s' default_log_datefmt = '%b %d %H:%M:%S' # Compatibility with dictionary methods not present in Python 3; # https://www.python.org/dev/peps/pep-0469/#migrating-to-the-common-subset-of-python-2-and-3 try: dict.iteritems except AttributeError: # Python 3 iter_items = lambda d: iter(d.items()) else: # Python 2 iter_items = lambda d: d.iteritems() # {{{ pexpect-logging glue # needed for use as pexpect.logfile, to relay into existing logfiles class expect_logging(): prefix = "" test = None def __init__(self, pre, inst): self.prefix = pre self.test = inst def flush(self, *arg): pass def write(self, stg): if self.test.dont_log_expect == 0: # TODO: split by input/output, give program if sys.version_info[0] >= 3: stg = str(stg, 'UTF-8') for line in re.split(r"[\r\n]+", stg): if line == self.test.prompt: continue if line == "": continue logging.debug(" " + self.prefix + " " + line) # }}} # {{{ dictionary plus second hash class dict_plus(dict): def __init__(self): self.aux = dict() # def aux(self): # return self.aux # }}} class UT(): # {{{ Members binary = None test_base = None lockfile = None defaults = None this_port = None this_site = "127.0.0.1" this_site_id = None running = False gdb = None booth = None prompt = "CUSTOM-GDB-PROMPT-%d-%d" % (os.getpid(), time.time()) dont_log_expect = 0 current_nr = None udp_sock = None # http://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) # }}} # {{{ setup functions @classmethod def _filename(cls, desc): return "/tmp/booth-unittest.%d.%s" % (os.getpid(), desc) def __init__(self, bin, dir): self.binary = os.path.realpath(bin) self.test_base = os.path.realpath(dir) + "/" self.defaults = self.read_test_input(self.test_base + "_defaults.txt", state="ticket") self.lockfile = UT._filename("lock") self.udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) def read_test_input(self, file, state=None, m = dict()): fo = open(file, "r") state = None line_nr = 0 for line in fo.readlines(): line_nr += 1 # comment? if re.match(r"^\s*#", line): continue # empty line if re.match(r"^\s*$", line): continue # message resp. ticket # We allow a comment to have something to write out to screen res = re.match(r"^\s*(\w+)\s*:(?:\s*(#.*?\S))?\s*$", line) if res: state = res.group(1) if state not in m: m[state] = dict_plus() if res.group(2): m[state].aux["comment"] = res.group(2) m[state].aux["line"] = line_nr continue assert(state) res = re.match(r"^\s*(\S+)\s*(.*)\s*$", line) if res: m[state][ res.group(1) ] = res.group(2) return m def setup_log(self, **args): global default_log_format global default_log_datefmt this_test_log = logging.FileHandler( mode = "w", **args ) this_test_log.setFormatter( logging.Formatter(fmt = default_log_format, datefmt = default_log_datefmt) ) this_test_log.emit( logging.makeLogRecord( { "msg": "## vim: set ft=messages : ##", "lineno": 0, "levelname": "None", "level": None,} ) ) # in the specific files we want ALL information this_test_log.setLevel(logging.DEBUG) logging.getLogger('').addHandler(this_test_log) return this_test_log def running_on_console(self): return sys.stdout.isatty() def colored_string(self, stg, color): if self.running_on_console(): return "\033[%dm%s\033[0m" % (30+color, stg) return stg # We want shorthand in descriptions, ie. "state" # instead of "booth_conf->ticket[0].state". def translate_shorthand(self, name, context): if context == 'ticket': return "booth_conf->ticket[0]." + name if context == 'message': return "msg->" + name if context == 'inject': return "ntohl(((struct boothc_ticket_msg *)buf)->" + name + ")" assert(False) def stop_processes(self): if os.access(self.lockfile, os.F_OK): os.unlink(self.lockfile) # In case the boothd process is already dead, isalive() would still return True # (because GDB still has it), but terminate() does fail. # So we just quit GDB, and that might take the boothd with it - # if not, we terminate it ourselves. if self.gdb: self.gdb.close( force=True ); self.drain_booth_log() if self.booth: self.booth.close( force=self.booth.isalive() ) def start_a_process(self, bin, env_add=[], **args): name = re.sub(r".*/", "", bin) # How to get stderr, too? expct = pexpect.spawn(bin, env=dict(os.environ, **dict({ 'PATH': ':'.join((self.test_base + "/bin/", os.getenv('PATH'))), 'UNIT_TEST_PATH': self.test_base, 'LC_ALL': 'C', 'LANG': 'C'}, **dict(env_add))), timeout=30, maxread=32768, **args) expct.setecho(False) expct.logfile_read = expect_logging("<- %s" % name, self) expct.logfile_send = expect_logging(" -> %s" % name, self) return expct def start_processes(self, test): self.booth = self.start_a_process(self.binary, args = [ "daemon", "-D", "-c", self.test_base + "/booth.conf", "-s", "127.0.0.1", "-l", self.lockfile, ], env_add=[ ('UNIT_TEST', test), ('UNIT_TEST_FILE', os.path.realpath(test)), # provide some space, so that strcpy(getenv()) works ('UNIT_TEST_AUX', "".zfill(1024)), ]); logging.info("started booth with PID %d, lockfile %s" % (self.booth.pid, self.lockfile)) self.booth.expect("BOOTH site \S+ \(build \S+\) daemon is starting", timeout=2) #print self.booth.before; exit self.gdb = self.start_a_process("gdb", args=["-quiet", "-p", str(self.booth.pid), # Don't use .gdbinit "-nx", "-nh", # Run until the defined point. # This is necessary so that ticket state setting doesn't # happen _before_ the call to pcmk_load_ticket() # (which would overwrite our data) "-ex", "break ticket_cron", "-ex", "continue", ]) logging.info("started GDB with PID %d" % self.gdb.pid) self.gdb.expect("(gdb)") self.gdb.sendline("set pagination off\n") self.gdb.sendline("set interactive-mode off\n") self.gdb.sendline("set verbose off\n") ## sadly to late for the initial "symbol not found" messages self.gdb.sendline("set prompt " + self.prompt + "\\n\n"); self.sync(2000) # Only stop for this recipient, so that broadcasts are not seen multiple times self.send_cmd("break booth_udp_send if to == &(booth_conf->site[1])") self.send_cmd("break recvfrom") # ticket_cron is still a breakpoint # Now we're set up. self.this_site_id = self.query_value("local->site_id") self.this_port = int(self.query_value("booth_conf->port")) # do a self-test assert(self.check_value("local->site_id", self.this_site_id)) self.running = False # }}} # {{{ GDB communication def sync(self, timeout=-1): self.gdb.expect(self.prompt, timeout) answer = self.gdb.before self.dont_log_expect += 1 # be careful not to use RE characters like +*.[] etc. r = str(random.randint(2**19, 2**20)) self.gdb.sendline("print " + r) self.gdb.expect(r, timeout) self.gdb.expect(self.prompt, timeout) self.dont_log_expect -= 1 return answer # send a command to GDB, returning the GDB answer as string. def drain_booth_log(self): try: self.booth.read_nonblocking(64*1024, 0) except pexpect.EOF: pass except pexpect.TIMEOUT: pass finally: pass def send_cmd(self, stg, timeout=-1): # give booth a chance to get its messages out self.drain_booth_log() self.gdb.sendline(stg) return self.sync(timeout=timeout) def _query_value(self, which): val = self.send_cmd("print " + which) cleaned = re.search(r"^\$\d+ = (.*\S)\s*$", val, re.MULTILINE) if not cleaned: self.user_debug("query failed") return cleaned.group(1) def query_value(self, which): res = self._query_value(which) logging.debug("query_value: «%s» evaluates to «%s»" % (which, res)) return res def check_value(self, which, value): val = self._query_value("(" + which + ") == (" + value + ")") logging.debug("check_value: «%s» is «%s»: %s" % (which, value, val)) if val == "1": return True # for easier (test) debugging we'll show the _real_ value, too. want = self._query_value(value) # Order is important, so that next query works!! has = self._query_value(which) # for informational purposes self._query_value('state_to_string($$)') logging.error("«%s»: got «%s», expected «%s». ERROR." % (which, has, want)) return False # Send data to GDB, to inject them into the binary. # Handles different data types def set_val(self, name, value, numeric_conv=None): logging.debug("setting value «%s» to «%s» (num_conv %s)" %(name, value, numeric_conv)) res = None # string value? if re.match(r'^"', value): res = self.send_cmd("print strcpy(" + name + ", " + value + ")") elif re.match(r"^'", value): # single-quoted; GDB only understands double quotes. v1 = re.sub(r"^'", '', value) v2 = re.sub(r"'$", '', v1) # TODO: replace \\\\" etc. v3 = re.sub(r'"', '\\"', v2) res = self.send_cmd("print strcpy(" + name + ', "' + v3 + '")') # numeric elif numeric_conv: res = self.send_cmd("set variable " + name + " = " + numeric_conv + "(" + value + ")") else: res = self.send_cmd("set variable " + name + " = " + value) for r in [r"There is no member named", r"Structure has no component named", r"No symbol .* in current context", ]: assert(not re.search(r, res, re.MULTILINE)) logging.debug("set_val %s done" % name) # }}} GDB communication # there has to be some event waiting, so that boothd stops again. def continue_debuggee(self, timeout=30): res = None if not self.running: res = self.send_cmd("continue", timeout) self.drain_booth_log() return res # {{{ High-level functions. # Generally, GDB is attached to BOOTHD, and has it stopped. def set_state(self, kv): if not kv: return self.current_nr = kv.aux.get("line") #os.system("strace -f -tt -s 2000 -e write -p" + str(self.gdb.pid) + " &") for n, v in iter_items(kv): self.set_val( self.translate_shorthand(n, "ticket"), v) logging.info("set state") def user_debug(self, txt): logging.error("Problem detected: %s", txt) logging.info(self.gdb.buffer) if not sys.stdin.isatty(): logging.error("Not a terminal, stopping.") else: print("\n\nEntering interactive mode.\n\n") self.gdb.sendline("set prompt GDB> \n") self.gdb.setecho(True) # can't use send_cmd, doesn't reply with expected prompt anymore. self.gdb.interact() #while True: # sys.stdout.write("GDB> ") # sys.stdout.flush() # x = sys.stdin.readline() # if not x: # break # self.send_cmd(x) self.stop_processes() sys.exit(1) def wait_for_function(self, fn, timeout=20): until = time.time() + timeout while True: stopped_at = self.continue_debuggee(timeout=3) if not stopped_at: self.user_debug("Not stopped at any breakpoint?") if re.search(r"^Program received signal SIGABRT,", stopped_at, re.MULTILINE): self.user_debug("assert() failed") if re.search(r"^Program received signal SIGSEGV,", stopped_at, re.MULTILINE): self.user_debug("Segfault") if re.search(r"^Breakpoint \d+, (0x\w+ in )?%s " % fn, stopped_at, re.MULTILINE): break if time.time() > until: self.user_debug("Didn't stop in function %s" % fn) logging.info("Now in %s" % fn) # We break, change the data, and return the correct size. def send_message(self, msg): self.udp_sock.sendto('a', (socket.gethostbyname(self.this_site), self.this_port)) self.wait_for_function("recvfrom") # drain input, but stop afterwards for changing data self.send_cmd("finish") # step over length assignment self.send_cmd("next") # push message. for (n, v) in iter_items(msg): self.set_val( self.translate_shorthand(n, "message"), v, "htonl") # set "received" length self.set_val("rv", "msg->header.length", "ntohl") # the next thing should run continue via wait_for_function def wait_outgoing(self, msg): self.wait_for_function("booth_udp_send") ok = True for (n, v) in iter_items(msg): if re.search(r"\.", n): ok = self.check_value( self.translate_shorthand(n, "inject"), v) and ok else: ok = self.check_value( self.translate_shorthand(n, "ticket"), v) and ok if not ok: sys.exit(1) logging.info("out gone") #stopped_at = self.sync() def merge_dicts(self, base, overlay): return dict(base, **overlay) def loop(self, fn, data): matches = (re.match(r"^(outgoing|message)(\d+)$", k) for k in data) loop_max = max(int(m.group(2)) for m in matches if m) for counter in range(0, loop_max+1): # incl. last message kmsg = 'message%d' % counter msg = data.get(kmsg) ktkt = 'ticket%d' % counter tkt = data.get(ktkt) kout = 'outgoing%d' % counter out = data.get(kout) kgdb = 'gdb%d' % counter gdb = data.get(kgdb) if not any([msg, out, tkt]): continue logging.info("Part %d" % counter) if tkt: self.current_nr = tkt.aux.get("line") comment = tkt.aux.get("comment", "") logging.info("ticket change %s (%s:%d) %s" % (ktkt, fn, self.current_nr, comment)) self.set_state(tkt) if gdb: for (k, v) in iter_items(gdb): self.send_cmd(k + " " + v.replace("§", "\n")) if msg: self.current_nr = msg.aux.get("line") comment = msg.aux.get("comment", "") logging.info("sending %s (%s:%d) %s" % (kmsg, fn, self.current_nr, comment)) self.send_message(self.merge_dicts(data["message"], msg)) if kgdb in data and len(gdb) == 0: self.user_debug("manual override") if out: self.current_nr = out.aux.get("line") comment = out.aux.get("comment", "") logging.info("waiting for %s (%s:%d) %s" % (kout, fn, self.current_nr, comment)) self.wait_outgoing(out) logging.info("loop ends") def let_booth_go_a_bit(self): self.drain_booth_log() logging.debug("running: %d" % self.running) if not self.running: self.gdb.sendline("continue") time.sleep(1) self.drain_booth_log() # stop it - via GDB! self.gdb.sendintr() # If we sent the signal to booth, the next # "print state_to_string()" or "continue" # might catch the signal - and fail to do # what we want/need. # # This additional signal seems to be unnecessary. #posix.kill(self.gdb.pid, signal.SIGINT) # In case it's really needed we should drain booth's signals queue, # eg. by sending "print getpid()" twice, before the sync() call. self.running = False self.sync(2000) def do_finally(self, data): if not data: return self.current_nr = data.aux.get("line") # Allow debuggee to reach a stable state self.let_booth_go_a_bit() ok = True for (n, v) in iter_items(data): ok = self.check_value( self.translate_shorthand(n, "ticket"), v) and ok if not ok: sys.exit(1) def run(self, start_from="000", end_with="999"): os.chdir(self.test_base) # TODO: sorted, random order tests = sorted(f for f in glob.glob("*") if re.match(r"^\d\d\d_.*\.txt$", f)) failed = 0 for f in tests: if f[0:3] < start_from: continue if f[0:3] > end_with: continue log = None logfn = UT._filename(f) if self.running_on_console(): sys.stdout.write("\n") self.current_nr = "setup" try: log = self.setup_log(filename = logfn) log.setLevel(logging.DEBUG) logging.error(self.colored_string("Starting test '%s'" % f, self.BLUE) + ", logfile " + logfn) self.start_processes(f) test = self.read_test_input(f, m=copy.deepcopy(self.defaults)) logging.debug("data: %s" % pprint.pformat(test, width = 200)) self.set_state(test.get("ticket")) self.loop(f, test) self.do_finally(test.get("finally")) self.current_nr = "teardown" logging.warn(self.colored_string("Finished test '%s' - OK" % f, self.GREEN)) except: failed += 1 logging.error(self.colored_string("Broke in %s:%s %s" % (f, self.current_nr, sys.exc_info()), self.RED)) for frame in traceback.format_tb(sys.exc_info()[2]): logging.info(" - %s " % frame.rstrip()) finally: self.stop_processes() if log: log.close() logging.getLogger("").removeHandler(log) if self.running_on_console(): sys.stdout.write("\n") return failed # }}} #def traceit(frame, event, arg): # if event == "line": # lineno = frame.f_lineno # print frame.f_code.co_filename, ":", "line", lineno # return traceit # {{{ main if __name__ == '__main__': if os.geteuid() == 0: sys.stderr.write("Must be run non-root; aborting.\n") sys.exit(1) ut = UT(sys.argv[1], sys.argv[2] + "/") # "master" log object needs max level logging.basicConfig(level = logging.DEBUG, filename = "/dev/null", filemode = "a", format = default_log_format, datefmt = default_log_datefmt) # make sure no old processes are active anymore os.system("killall boothd > /dev/null 2> /dev/null") overview_log = ut.setup_log( filename = UT._filename('seq') ) overview_log.setLevel(logging.WARN) # http://stackoverflow.com/questions/9321741/printing-to-screen-and-writing-to-a-file-at-the-same-time console = logging.StreamHandler() console.setFormatter(logging.Formatter(' # %(message)s')) console.setLevel(logging.WARN) logging.getLogger('').addHandler(console) logging.info("Starting boothd unit tests.") #sys.settrace(traceit) starting = "0" if len(sys.argv) > 3: starting = sys.argv[3] ending = "999" if len(sys.argv) > 4: ending = sys.argv[4] ret = ut.run(starting, ending) sys.exit(ret) # }}} diff --git a/test/runtests.py b/test/runtests.py.in old mode 100755 new mode 100644 similarity index 98% rename from test/runtests.py rename to test/runtests.py.in index 833b1a7..ec59159 --- a/test/runtests.py +++ b/test/runtests.py.in @@ -1,57 +1,57 @@ -#!/usr/bin/python +#!@PYTHON_SHEBANG@ import os import re import shutil import sys import tempfile import time import unittest from clienttests import ClientConfigTests from sitetests import SiteConfigTests from arbtests import ArbitratorConfigTests if __name__ == '__main__': if os.geteuid() == 0: sys.stderr.write("Must be run non-root; aborting.\n") sys.exit(1) tmp_path = '/tmp/booth-tests' if not os.path.exists(tmp_path): os.makedirs(tmp_path) test_run_path = tempfile.mkdtemp(prefix='%d.' % time.time(), dir=tmp_path) suite = unittest.TestSuite() testclasses = [ SiteConfigTests, #ArbitratorConfigTests, ClientConfigTests, ] for testclass in testclasses: testclass.test_run_path = test_run_path suite.addTests(unittest.TestLoader().loadTestsFromTestCase(testclass)) runner_args = { #'verbosity' : 2, } major, minor, micro, releaselevel, serial = sys.version_info if major > 2 or (major == 2 and minor >= 7): # New in 2.7 runner_args['buffer'] = True runner_args['failfast'] = True pass # not root anymore, so safe # needed because old instances might still use the UDP port. os.system("killall boothd") runner = unittest.TextTestRunner(**runner_args) result = runner.run(suite) if result.wasSuccessful(): shutil.rmtree(test_run_path) sys.exit(0) else: print("Left %s for debugging" % test_run_path) sys.exit(1)