#!/bin/sh
#
# Copyright (C) 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/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.

#
# Run a system test.
#

top_builddir=@top_builddir@
builddir=@abs_builddir@
srcdir=@abs_srcdir@

# shellcheck source=conf.sh
. ${builddir}/conf.sh

if [ "$(id -u)" -eq "0" ] && [ "@DEVELOPER_MODE@" != "yes" ]; then
    echofail "Refusing to run test as root. Build with --enable-developer to override." >&2
    exit 1
fi

export builddir
export srcdir

date_with_args() (
    date "+%Y-%m-%dT%T%z"
)

stopservers=true
# baseport == 0 means random
baseport=0

if [ "${SYSTEMTEST_NO_CLEAN:-0}" -eq 1 ]; then
    clean=false
else
    clean=true
fi

do_run=false
log_flags="-r"
while getopts "sknp:r-:" OPT; do
    log_flags="$log_flags -$OPT$OPTARG"
    if [ "$OPT" = "-" ] && [ -n "$OPTARG" ]; then
        OPT="${OPTARG%%=*}"
        OPTARG="${OPTARG#$OPT}"
        OPTARG="${OPTARG#=}"
    fi

    # shellcheck disable=SC2214
    case "$OPT" in
        k | keep) stopservers=false ;;
        n | noclean) clean=false ;;
        p | port) baseport=$OPTARG ;;
        r | run) do_run=true ;;
        s | skip) exit 77 ;;
        -) break ;;
        *) echo "invalid option" >&2; exit 1 ;;
    esac
done

shift $((OPTIND-1))

if ! $do_run; then
    if [ "$baseport" -eq 0 ]; then
        log_flags="$log_flags -p 5300"
    fi
    env - SLOT="$SLOT" SOFTHSM2_CONF="$SOFTHSM2_CONF" PATH="$PATH" ${LD_LIBRARY_PATH:+"LD_LIBRARY_PATH=${LD_LIBRARY_PATH}"} TESTS="$*" TEST_SUITE_LOG=run.log LOG_DRIVER_FLAGS="--verbose yes --color-tests yes" LOG_FLAGS="$log_flags" make -e check
    exit $?
fi

if [ $# -eq 0 ]; then
    echofail "Usage: $0 [-k] [-n] [-p <PORT>] test-directory [test-options]" >&2;
    exit 1
fi

systest=$(basename "${1%%/}")
shift

if [ ! -d "${srcdir}/$systest" ]; then
    echofail "$0: $systest: no such test" >&2
    exit 1
fi

if [ "${srcdir}" != "${builddir}" ]; then
    if [ ! -d common ] || [ ! -r common/.prepared ]; then
        cp -a "${srcdir}/common" "${builddir}"
    fi
    # Some tests require additional files to work for out-of-tree test runs.
    for file in ckdnsrps.sh digcomp.pl ditch.pl fromhex.pl kasp.sh packet.pl start.pl stop.pl testcrypto.sh; do
        if [ ! -r "${file}" ]; then
            cp -a "${srcdir}/${file}" "${builddir}"
        fi
    done
    if [ ! -d "$systest" ] || [ ! -r "$systest/.prepared" ]; then
        mkdir -p "${builddir}/$systest"
        cp -a "${srcdir}/$systest" "${builddir}/"
        touch "$systest/.prepared"
    fi
fi

if [ ! -d "${systest}" ]; then
    echofail "$0: $systest: no such test" >&2
    exit 1
fi


# Determine which ports to use for this system test.
eval "$(cd "${srcdir}" && ./get_ports.sh -p "$baseport" -t "$systest")"

restart=false

start_servers_failed() {
    echoinfo "I:$systest:starting servers failed"
    echofail "R:$systest:FAIL"
    echoend  "E:$systest:$(date_with_args)"
    exit 1
}

start_servers() {
    echoinfo "I:$systest:starting servers"
    if $restart; then
        $PERL start.pl --restart --port "$PORT" "$systest" || start_servers_failed
    else
        restart=true
        $PERL start.pl --port "$PORT" "$systest" || start_servers_failed
    fi
}

stop_servers() {
    if $stopservers; then
        echoinfo "I:$systest:stopping servers"
        if ! $PERL stop.pl "$systest"; then
            echoinfo "I:$systest:stopping servers failed"
            return 1
        fi
    fi
}

echostart "S:$systest:$(date_with_args)"
echoinfo  "T:$systest:1:A"
echoinfo  "A:$systest:System test $systest"
echoinfo  "I:$systest:PORTS:${PORT},${TLSPORT},${HTTPPORT},${HTTPSPORT},${EXTRAPORT1},${EXTRAPORT2},${EXTRAPORT3},${EXTRAPORT4},${EXTRAPORT5},${EXTRAPORT6},${EXTRAPORT7},${EXTRAPORT8},${CONTROLPORT}"

$PERL ${srcdir}/testsock.pl -p "$PORT"  || {
    echowarn "I:$systest:Network interface aliases not set up.  Skipping test."
    echowarn "R:$systest:FAIL"
    echoend  "E:$systest:$(date_with_args)"
    exit 1;
}

# Check for test-specific prerequisites.
test ! -f "$systest/prereq.sh" || ( cd "${systest}" && $SHELL prereq.sh "$@" )
result=$?

if [ $result -eq 0 ]; then
    : prereqs ok
else
    echowarn "I:$systest:Prerequisites missing, skipping test."
    echowarn "R:$systest:SKIPPED";
    echoend "E:$systest:$(date_with_args)"
    exit 77
fi

# Check for PKCS#11 support
if
    test ! -f "$systest/usepkcs11" || $SHELL cleanpkcs11.sh
then
    : pkcs11 ok
else
    echowarn "I:$systest:Need PKCS#11, skipping test."
    echowarn "R:$systest:PKCS11ONLY"
    echoend  "E:$systest:$(date_with_args)"
    exit 0
fi

# Clean up files left from any potential previous runs
if test -f "$systest/clean.sh"
then
    if ! ( cd "${systest}" && $SHELL clean.sh "$@" ); then
        echowarn "I:$systest:clean.sh script failed"
        echofail "R:$systest:FAIL"
        echoend  "E:$systest:$(date_with_args)"
        exit 1
    fi
fi

# Set up any dynamically generated test data
if test -f "$systest/setup.sh"
then
    if ! ( cd "${systest}" && $SHELL setup.sh "$@" ); then
        echowarn "I:$systest:setup.sh script failed"
        echofail "R:$systest:FAIL"
        echoend  "E:$systest:$(date_with_args)"
        exit 1
    fi
fi

status=0
run=0
# Run the tests
if [ -r "$systest/tests.sh" ]; then
    start_servers
    ( cd "$systest" && $SHELL tests.sh "$@" )
    status=$?
    run=$((run+1))
    stop_servers || status=1
fi

if [ -n "$PYTEST" ]; then
    run=$((run+1))
    for test in $(cd "${systest}" && find . -name "tests*.py"); do
        start_servers
        rm -f "$systest/$test.status"
        test_status=0
        (cd "$systest" && "$PYTEST" -v "$test" "$@" || echo "$?" > "$test.status") | SYSTESTDIR="$systest" cat_d
        if [ -f "$systest/$test.status" ]; then
            echo_i "FAILED"
            test_status=$(cat "$systest/$test.status")
        fi
        status=$((status+test_status))
        stop_servers || status=1
    done
else
    echoinfo "I:$systest:pytest not installed, skipping python tests"
fi

if [ "$run" -eq "0" ]; then
    echoinfo "I:$systest:No tests were found and run"
    status=255
fi

if $stopservers
then
    :
else
    exit $status
fi

get_core_dumps() {
    find "$systest/" \( -name 'core' -or -name 'core.*' -or -name '*.core' \) ! -name '*.gz' ! -name '*.txt' | sort
}

core_dumps=$(get_core_dumps | tr '\n' ' ')
assertion_failures=$(find "$systest/" -name named.run -print0 | xargs -0 grep "assertion failure" | wc -l)
sanitizer_summaries=$(find "$systest/" -name 'tsan.*' | wc -l)
if [ -n "$core_dumps" ]; then
    status=1
    echoinfo "I:$systest:Core dump(s) found: $core_dumps"
    get_core_dumps | while read -r coredump; do
        export SYSTESTDIR="$systest"
        echoinfo "D:$systest:backtrace from $coredump:"
        echoinfo "D:$systest:--------------------------------------------------------------------------------"
        binary=$(gdb --batch --core="$coredump" 2>/dev/null | sed -ne "s|Core was generated by \`\([^' ]*\)[' ].*|\1|p")
        if [ ! -f "${binary}" ]; then
            binary=$(find "${top_builddir}" -path "*/.libs/${binary}" -type f)
        fi
        "${top_builddir}/libtool" --mode=execute gdb \
                                  -batch \
                                  -ex bt \
                                  -core="$coredump" \
                                  -- \
                                  "$binary" 2>/dev/null | sed -n '/^Core was generated by/,$p' | cat_d
        echoinfo "D:$systest:--------------------------------------------------------------------------------"
        coredump_backtrace="${coredump}-backtrace.txt"
        echoinfo "D:$systest:full backtrace from $coredump saved in $coredump_backtrace"
        "${top_builddir}/libtool" --mode=execute gdb \
                                  -batch \
                                  -command=run.gdb \
                                  -core="$coredump" \
                                  -- \
                                  "$binary" > "$coredump_backtrace" 2>&1
        echoinfo "D:$systest:core dump $coredump archived as $coredump.gz"
        gzip -1 "${coredump}"
    done
elif [ "$assertion_failures" -ne 0 ]; then
    status=1
    SYSTESTDIR="$systest"
    echoinfo "I:$systest:$assertion_failures assertion failure(s) found"
    find "$systest/" -name 'tsan.*' -print0 | xargs -0 grep "SUMMARY: " | sort -u | cat_d
elif [ "$sanitizer_summaries" -ne 0 ]; then
    status=1
    echoinfo "I:$systest:$sanitizer_summaries sanitizer report(s) found"
fi

print_outstanding_files() {
    if test -d ${srcdir}/../../../.git; then
        git status -su --ignored "${systest}/" 2>/dev/null | \
        sed -n -e 's|^?? \(.*\)|I:'${systest}':file \1 not removed|p' \
            -e 's|^!! \(.*/named.run\)$|I:'${systest}':file \1 not removed|p' \
            -e 's|^!! \(.*/named.memstats\)$|I:'${systest}':file \1 not removed|p'
    fi
}

print_outstanding_files_oot() {
    if test -d ${srcdir}/../../../.git; then
        git -C "${srcdir}/${systest}" ls-files | sed "s|^|${systest}/|" > gitfiles.txt
        find "${systest}/" -type f ! -name .prepared ! -name Makefile > testfiles.txt
        grep -F -x -v -f gitfiles.txt testfiles.txt
        rm -f gitfiles.txt testfiles.txt
    fi
}

if [ $status -ne 0 ]; then
    echofail "R:$systest:FAIL"
else
    echopass "R:$systest:PASS"
    if $clean; then
        ( cd "${systest}" && $SHELL clean.sh "$@" )
        if [ "${srcdir}" = "${builddir}" ]; then
            print_outstanding_files
        else
            print_outstanding_files_oot | xargs rm -f
            find "${systest}/" \( -type d -empty \) -delete 2>/dev/null
        fi
    fi
fi

NAMED_RUN_LINES_THRESHOLD=200000
find "${systest}" -type f -name "named.run" -exec wc -l {} \; | awk "\$1 > ${NAMED_RUN_LINES_THRESHOLD} { print \$2 }" | sort | while read -r LOG_FILE; do
    echowarn "I:${systest}:${LOG_FILE} contains more than ${NAMED_RUN_LINES_THRESHOLD} lines, consider tweaking the test to limit disk I/O"
done

echoend "E:$systest:$(date_with_args)"

exit $status
