#!/bin/bash die() { echo "$@" exit 5 } _is_true() { case "$1" in y|Y|yes|YES|1|true|TRUE) return 0 ;; n|N|no|NO|0|false|FALSE) return 1 ;; *) if test -n "$2"; then _is_true "$2" return $? fi return 2 ;; esac } usage() { echo "$0 [\$OPTIONS] [--] \$TEST [\$TEST_OPTIONS]" echo "" echo " Runs the unit test with setting up dbus-session (as necessary)," echo " optionally build the test first, and run valgrind" echo "" echo " --help|-h: help" echo " --launch-dbus: the test runner by default automatically launches a D-Bus session" echo " depending on a hard-coded list of tests that require it. This flag overwrites" echo " the automatism to always launch a D-Bus session" echo " --no-launch-dbus|-D: prevent launching a D-Bus session" echo " --no-libtool: when running with valgrind, the script tries automatically to" echo " use libtool as necessary. This disables libtool usage" echo " --make-first|-m: before running the test, make it (only works with autotools build)" echo " --valgrind|-v: run under valgrind" echo " --no-valgrind|-V: disable running under valgrind (overrides NMTST_USE_VALGRIND=1)" echo " -d: set NMTST_DEBUG=d" echo " --test|-t \$TEST: set the test that should be run" echo "" echo " With \"--test\" and \"--\" you can select the test and which arguments are" echo " passed to the test. You can omit these, in which case the first unknown parameter" echo " is the test and all other unknown parameters are passed to the test. For example" echo " $0 -m --test src/core/tests/test-core -- -p /general/match-spec/device" echo " can also be called as" echo " $0 src/core/tests/test-core -p /general/match-spec/device -m" echo "" echo " The following environment variables are honored:" echo " NMTST_USE_VALGRIND=0|1: enable/disable valgrind" echo " NMTST_LIBTOOL=: libtool path (or disable)" echo " NMTST_LAUNCH_DBUS=0|1: whether to lounch a D-Bus session" echo " NMTST_SET_DEBUG=0|1: saet NMTST_DEBUG=d" echo "" echo " This script is also called by the build system as test wrapper. In that case" echo " different, internal command line syntax is used. In that case, environment variables" echo " are still honored, so \`NMTST_USE_VALGRIND=1 make check\` works as expected" } SCRIPT_PATH="${SCRIPT_PATH:-$(readlink -f "$(dirname "$0")")}" VALGRIND_ERROR=37 if [ "$1" == "--called-from-make" ]; then shift CALLED_FROM_MAKE=1 else CALLED_FROM_MAKE=0 fi BUILDDIR= if [ "$CALLED_FROM_MAKE" == 1 ]; then BUILDDIR="$1" shift if [ -n "$1" ]; then NMTST_LIBTOOL=($1 --mode=execute); else NMTST_LIBTOOL=() fi shift NMTST_VALGRIND_ARG="$1"; shift if [[ "$NMTST_VALGRIND_ARG" == no ]]; then NMTST_VALGRIND_ARG= fi if [[ -z "${NMTST_VALGRIND}" ]]; then # the valgrind path can be specified via $NMTST_VALGRIND. # Otherwise, it can be determined by the configure scripts. # Otherwise, it is found in the current $PATH (below). if [[ "$NMTST_VALGRIND_ARG" != "" ]]; then NMTST_VALGRIND="${NMTST_VALGRIND_ARG}" fi fi if [[ -z "${NMTST_USE_VALGRIND+x}" ]]; then # whether to use valgrind can be specified via $NMTST_USE_VALGRIND. # Otherwise, it depends on the configure option. if [ "$NMTST_VALGRIND_ARG" == "" ]; then NMTST_USE_VALGRIND=0 else NMTST_USE_VALGRIND=1 fi fi NMTST_SUPPRESSIONS_ARGS="$1"; shift if [[ -z "${NMTST_SUPPRESSIONS+x}" ]]; then if [[ "$NMTST_SUPPRESSIONS_ARGS" == "" ]]; then NMTST_SUPPRESSIONS="$SCRIPT_PATH/../valgrind.suppressions" else NMTST_SUPPRESSIONS="${NMTST_SUPPRESSIONS_ARGS}" fi fi if [ "$1" = "--launch-dbus" ]; then NMTST_LAUNCH_DBUS=1 shift elif [ "$1" = "--launch-dbus=auto" ]; then NMTST_LAUNCH_DBUS= shift else NMTST_LAUNCH_DBUS=0 fi TEST="$1"; shift NMTST_MAKE_FIRST=0 TEST_ARGV=("$@") else if [[ -z "${NMTST_USE_VALGRIND+x}" ]]; then # by default, disable valgrind checks. NMTST_USE_VALGRIND=0 fi if [ -z "${NMTST_LIBTOOL+x}" ]; then NMTST_LIBTOOL=(sh "$SCRIPT_PATH/../libtool" "--mode=execute") elif [ -z "$NMTST_LIBTOOL" ]; then NMTST_LIBTOOL=() else NMTST_LIBTOOL=("$NMTST_LIBTOOL" "--mode=execute") fi TEST_ARGV=() unset TEST while test $# -gt 0; do case "$1" in --help|-h) usage exit 0 ;; "--launch-dbus") NMTST_LAUNCH_DBUS=1 shift ;; "--no-launch-dbus"|"-D") NMTST_LAUNCH_DBUS=0 shift ;; "--no-libtool") NMTST_LIBTOOL=() shift ;; --make-first|-m) NMTST_MAKE_FIRST=1 shift ;; "--valgrind"|-v) NMTST_USE_VALGRIND=1 shift; ;; "--no-valgrind"|-V) NMTST_USE_VALGRIND=0 shift; ;; "-d") NMTST_SET_DEBUG=1 shift; ;; "--test"|-t) shift TEST="$1" shift ;; "--") shift if test -z "${TEST+x}"; then TEST="$1"; shift fi TEST_ARGV+=("$@") break ;; *) if test -z "${TEST+x}"; then TEST="$1"; else TEST_ARGV+=("$1") fi shift ;; esac done # we support calling the script directly. In this case, # only pass the path to the test to run. if [[ -z "${NMTST_SUPPRESSIONS+x}" ]]; then NMTST_SUPPRESSIONS="$SCRIPT_PATH/../valgrind.suppressions" fi if [[ -z "$NMTST_BUILDDIR" ]]; then if [[ "${NMTST_BUILDDIR-x}" == x ]]; then # autodetect BUILDDIR="$(readlink -f "$TEST")" while [[ -n "$BUILDDIR" ]]; do BUILDDIR="$(dirname "$BUILDDIR")" [[ "$BUILDDIR" == / ]] && BUILDDIR= [[ -z "$BUILDDIR" ]] && break [[ -e "$BUILDDIR/src/libnm-client-impl/.libs/libnm.so" ]] && break [[ -e "$BUILDDIR/src/libnm-client-impl/libnm.so" ]] && break done fi fi fi if [ "$NMTST_SET_DEBUG" == 1 -a -z "${NMTST_DEBUG+x}" ]; then export NMTST_DEBUG=d fi [ -n "$TEST" ] || die "Missing test name. Specify it on the command line." if _is_true "$NMTST_MAKE_FIRST" 0; then git_dir="$(readlink -f "$(git rev-parse --show-toplevel)")" rel_path="$(realpath --relative-to="$git_dir" -m "$TEST" 2>/dev/null)" || die "cannot resolve test-name \"$TEST\". Did you call the script properly?" cd "$git_dir" make -j5 "$rel_path" || die "make of $TEST failed ($git_dir / $rel_path)" cd - 1>/dev/null fi [ -x "$TEST" ] || die "Test \"$TEST\" does not exist" TEST_PATH="$(readlink -f "$(dirname "$TEST")")" TEST_NAME="${TEST##*/}" if [ -z "${NMTST_LAUNCH_DBUS}" ]; then # autodetect whether to launch D-Bus based on the test path. if [[ $TEST_PATH == */src/libnm-client-impl/tests ]]; then NMTST_LAUNCH_DBUS=1 else NMTST_LAUNCH_DBUS=0 fi fi # if the user wishes, change first into the directory of the test if _is_true "$NMTST_CHANGE_DIRECTORY" 0; then cd "$TEST_PATH" TEST="./$TEST_NAME" fi NMTST_DBUS_RUN_SESSION=() if _is_true "$NMTST_LAUNCH_DBUS"; then if ! command -v dbus-run-session &>/dev/null ; then eval `dbus-launch --sh-syntax` trap "kill $DBUS_SESSION_BUS_PID" EXIT else NMTST_DBUS_RUN_SESSION=(dbus-run-session --) fi fi [ -x "$TEST" ] || die "Cannot execute test \"$TEST\"" if [[ -n "$BUILDDIR" ]]; then if [[ -d "$BUILDDIR/src/libnm-client-impl" ]]; then export GI_TYPELIB_PATH="$BUILDDIR/src/libnm-client-impl/${GI_TYPELIB_PATH:+:$GI_TYPELIB_PATH}" if [[ -d "$BUILDDIR/src/libnm-client-impl/.libs" ]]; then export LD_LIBRARY_PATH="$BUILDDIR/src/libnm-client-impl/.libs${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" else export LD_LIBRARY_PATH="$BUILDDIR/src/libnm-client-impl${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" fi fi fi export ASAN_OPTIONS="$NM_TEST_ASAN_OPTIONS" export LSAN_OPTIONS="$NM_TEST_LSAN_OPTIONS" export UBSAN_OPTIONS="$NM_TEST_UBSAN_OPTIONS" if [ -z "${NM_TEST_ASAN_OPTIONS+x}" ]; then ASAN_OPTIONS="fast_unwind_on_malloc=false detect_leaks=1" fi if [ -z "${NM_TEST_LSAN_OPTIONS+x}" ]; then LSAN_OPTIONS="suppressions=$SCRIPT_PATH/../lsan.suppressions" fi if [ -z "${NM_TEST_UBSAN_OPTIONS+x}" ]; then UBSAN_OPTIONS="print_stacktrace=1:halt_on_error=1" fi if ! _is_true "$NMTST_USE_VALGRIND" 0; then export NM_TEST_UNDER_VALGRIND=0 exec "${NMTST_DBUS_RUN_SESSION[@]}" \ "$TEST" "${TEST_ARGV[@]}" die "exec \"$TEST\" failed" fi if [[ -z "${NMTST_VALGRIND}" ]]; then NMTST_VALGRIND="$(command -v valgrind)" || die "cannot find valgrind binary. Set \$NMTST_VALGRIND" else test -e "${NMTST_VALGRIND}" || die "cannot find valgrind binary from NMTST_VALGRIND=\"${NMTST_VALGRIND}\"" fi if [[ "${NMTST_SUPPRESSIONS}" != "" ]]; then NMTST_SUPPRESSIONS=("--suppressions=$NMTST_SUPPRESSIONS") else NMTST_SUPPRESSIONS=() fi LOGFILE="${TEST}.valgrind-log" export G_SLICE=always-malloc export G_DEBUG=gc-friendly export NM_TEST_UNDER_VALGRIND=1 "${NMTST_DBUS_RUN_SESSION[@]}" \ "${NMTST_LIBTOOL[@]}" \ "$NMTST_VALGRIND" \ --quiet \ --error-exitcode=$VALGRIND_ERROR \ --leak-check=full \ --gen-suppressions=all \ "${NMTST_SUPPRESSIONS[@]}" \ --num-callers=100 \ --log-file="$LOGFILE" \ "$TEST" \ "${TEST_ARGV[@]}" RESULT=$? test -s "$LOGFILE" HAS_ERRORS=$? if [ $RESULT -ne 0 -a $RESULT -ne 77 ]; then if [ $HAS_ERRORS -ne 0 ]; then rm -f "$LOGFILE" elif [ $RESULT -ne $VALGRIND_ERROR ]; then # the test (probably) didn't fail due to valgrind. echo "The test failed. Also check the valgrind log at '`realpath "$LOGFILE"`'" >&2 else echo "valgrind failed! Check the log at '`realpath "$LOGFILE"`'" >&2 UNRESOLVED=$(awk -F: '/obj:\// {print $NF}' "$LOGFILE" | sort | uniq) if [ -n "$UNRESOLVED" ]; then echo Some addresses could not be resolved into symbols. >&2 echo The errors might get suppressed when you install the debugging symbols. >&2 if [ -x /usr/bin/dnf ]; then echo Hint: dnf debuginfo-install $UNRESOLVED >&2 elif [ -x /usr/bin/debuginfo-install ]; then echo Hint: debuginfo-install $UNRESOLVED >&2 else echo Files without debugging symbols: $UNRESOLVED >&2 fi fi fi exit $RESULT fi if [ $HAS_ERRORS -eq 0 ]; then # valgrind doesn't support setns syscall and spams the logfile. # hack around it... case "$TEST_NAME" in 'test-acd' | \ 'test-address-linux' | \ 'test-cleanup-linux' | \ 'test-config' | \ 'test-l3cfg' | \ 'test-link-linux' | \ 'test-lldp' | \ 'test-nm-client' | \ 'test-platform-general' | \ 'test-remote-settings-client' | \ 'test-route-linux' | \ 'test-secret-agent' | \ 'test-service-providers' | \ 'test-tc-linux' ) if [ -z "$(sed -e '/^--[0-9]\+-- WARNING: unhandled .* syscall: /,/^--[0-9]\+-- it at http.*\.$/d' "$LOGFILE")" ]; then HAS_ERRORS=1 fi ;; esac fi if [ $HAS_ERRORS -eq 0 ]; then # shouldn't actually happen... echo "valgrind succeeded, but log is not empty: '`realpath "$LOGFILE"`'" >&2 exit 1 fi rm -f "$LOGFILE" exit $RESULT