#!/bin/bash -ex

# Test for APLOGNO() macro errors (duplicates, empty args) etc.  For
# trunk, run the updater script to see if it fails.  If it succeeds
# and changes any files (because there was a missing argument), the
# git diff will be non-empty, so fail for that case too.  For
# non-trunk use a grep and only catch the empty argument case.
if test -v TEST_LOGNO; then
    if test -f docs/log-message-tags/update-log-msg-tags; then
        find server modules os -name \*.c | \
            xargs perl docs/log-message-tags/update-log-msg-tags
        git diff --exit-code .
        : PASSED
        exit 0
    else
        set -o pipefail
        if find server modules os -name \*.c | \
                xargs grep -C1 --color=always 'APLOGNO()'; then
            : FAILED
            exit 1
        else
            : PASSED
            exit 0
        fi
    fi
fi

### Installed apr/apr-util don't include the *.m4 files but the
### Debian packages helpfully install them, so use the system APR to buildconf
./buildconf --with-apr=/usr/bin/apr-1-config ${BUILDCONFIG}

PREFIX=${PREFIX:-$HOME/build/httpd-root}

# If perl-framework testing is required it is checked out here by
# _before_linux.sh:
if test -d test/perl-framework; then
    CONFIG="$CONFIG --enable-load-all-modules"
    if grep -q ^check: Makefile.in; then
        CONFIG="--with-test-suite=test/perl-framework $CONFIG"
        WITH_TEST_SUITE=1
    fi

    # Use the CPAN environment.
    eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib)
fi
if test -v APR_VERSION; then
    CONFIG="$CONFIG --with-apr=$HOME/root/apr-${APR_VERSION}"
else
    CONFIG="$CONFIG --with-apr=/usr"
fi
if test -v APU_VERSION; then
    CONFIG="$CONFIG --with-apr-util=$HOME/root/apr-util-${APU_VERSION}"
else
    CONFIG="$CONFIG --with-apr-util=/usr"
fi

# Pick up the rustls install built previously.
if test -v TEST_MOD_TLS -a RUSTLS_VERSION; then
  CONFIG="$CONFIG --with-tls --with-rustls=$HOME/root/rustls"
fi

if test -v TEST_OPENSSL3; then
    CONFIG="$CONFIG --with-ssl=$HOME/root/openssl3"
    # Temporarily set LD_RUN_PATH so that httpd/mod_ssl binaries pick
    # up the custom OpenSSL build
    export LD_RUN_PATH=$HOME/root/openssl3/lib:$HOME/root/openssl3/lib64
    export PATH=$HOME/root/openssl3/bin:$PATH
    openssl version
fi

srcdir=$PWD

if test -v TEST_VPATH; then
    mkdir ../vpath
    cd ../vpath
fi

builddir=$PWD

$srcdir/configure --prefix=$PREFIX $CONFIG
make $MFLAGS

if test -v TEST_OPENSSL3; then
   # Clear the library/run paths so that anything else run during
   # testing is not forced to use the custom OpenSSL build; e.g. perl,
   # php-fpm, ...
   unset LD_LIBRARY_PATH
   unset LD_RUN_PATH
fi

if test -v TEST_INSTALL; then
   make install
   pushd $PREFIX
     # Basic sanity tests of the installed server.
     ./bin/apachectl -V
     test `./bin/apxs -q PREFIX` = $PREFIX
     test `$PWD/bin/apxs -q PREFIX` = $PREFIX
     ./bin/apxs -g -n foobar
     cd foobar; make
   popd
fi

if test -v SKIP_TESTING; then
    # Check that httpd was built successfully, nothing more.
    ./httpd -V
    exit 0
fi

###############################################################
### Everything below is only run if SKIP_TESTING was not set ##
###############################################################

: Running tests...

set +e
RV=0

if test -v TEST_MALLOC; then
    # Enable enhanced glibc malloc debugging, see mallopt(3)
    export MALLOC_PERTURB_=65 MALLOC_CHECK_=3
    export LIBC_FATAL_STDERR_=1
fi

if test -v TEST_UBSAN; then
    export UBSAN_OPTIONS="log_path=$PWD/ubsan.log"
fi

if test -v TEST_ASAN; then
    export ASAN_OPTIONS="log_path=$PWD/asan.log:detect_leaks=0"
fi

if test -v PHP_FPM; then
    # Sanity test the executable exists.
    $PHP_FPM --version
fi

# Try to keep all potential coredumps from all processes
sudo sysctl -w kernel.core_uses_pid=1 2>/dev/null || true
# Systemd based systems might process core dumps via systemd-coredump.
# But we want to have local unprocessed files.
sudo sysctl -w kernel.core_pattern=core || true
ulimit -c unlimited 2>/dev/null || true

if ! test -v NO_TEST_FRAMEWORK; then
    if test -v WITH_TEST_SUITE; then
        make check TESTS="${TESTS}" TEST_CONFIG="${TEST_ARGS}" | tee test.log
        RV=${PIPESTATUS[0]}
        # re-run failing tests with -v, avoiding set -e
        if [ $RV -ne 0 ]; then
            # mv test/perl-framework/t/logs/error_log test/perl-framework/t/logs/error_log_save
            FAILERS=""
            while read FAILER; do
                FAILERS="$FAILERS $FAILER"
            done < <(awk '/Failed:/{print $1}' test.log)
            if [ -n "$FAILERS" ]; then
                make check TESTS="-v $FAILERS" || true
            fi
            # set -e would have killed us after the original t/TEST
            rm -f test.log
            # mv test/perl-framework/t/logs/error_log_save test/perl-framework/t/logs/error_log
            false
        fi
    else
        test -v TEST_INSTALL || make install
        pushd test/perl-framework
            perl Makefile.PL -apxs $PREFIX/bin/apxs
            make test APACHE_TEST_EXTRA_ARGS="${TEST_ARGS} ${TESTS}" | tee test.log
            RV=${PIPESTATUS[0]}
            # re-run failing tests with -v, avoiding set -e
            if [ $RV -ne 0 ]; then
                # mv t/logs/error_log t/logs/error_log_save
                FAILERS=""
                while read FAILER; do
                    FAILERS="$FAILERS $FAILER"
                done < <(awk '/Failed:/{print $1}' test.log)
                if [ -n "$FAILERS" ]; then
                    t/TEST -v $FAILERS || true
                fi
                # set -e would have killed us after the original t/TEST
                rm -f test.log
                # mv t/logs/error_log_save t/logs/error_log
                false
            fi
        popd
    fi

    # Skip further testing if a core dump was created during the test
    # suite run above.
    if test $RV -eq 0 && test -n "`ls test/perl-framework/t/core{,.*} 2>/dev/null`"; then
        RV=4
    fi
fi

if test \( -v TEST_SSL -o -v TEST_OPENSSL3 \) \
        -a -f test/perl-framework/t/logs/error_log; then
    : -- Check OpenSSL version used by mod_ssl at compile- and run-time --
    grep 'mod_ssl.*compiled against' test/perl-framework/t/logs/error_log | tail -n1 | grep --color=always 'OpenSSL/[^ ]*'
    grep 'resuming normal operations' test/perl-framework/t/logs/error_log | tail -n1 | grep --color=always 'OpenSSL/[^ ]*'
fi

if test -v TEST_SSL -a $RV -eq 0; then
    pushd test/perl-framework
        # Test loading encrypted private keys
        ./t/TEST -defines "TEST_SSL_DES3_KEY TEST_SSL_PASSPHRASE_EXEC" t/ssl
        RV=$?

        # Test various session cache backends
        for cache in shmcb redis:localhost:6379 memcache:localhost:11211; do
            test $RV -eq 0 || break

            SSL_SESSCACHE=$cache ./t/TEST -sslproto TLSv1.2 -defines TEST_SSL_SESSCACHE -start
            ./t/TEST t/ssl
            RV=$?
            if test $RV -eq 0; then
                # TODO: only really useful in e.g. triggering
                # server segfaults which are caught later, doesn't
                # directly catch non-200 responses etc.
                $builddir/support/ab -qd -n 4000 -c 20 -f TLS1.2 https://localhost:8532/
                RV=$?
            fi
            ./t/TEST -stop
            SRV=$?
            if test $RV -eq 0 -a $SRV -ne 0; then
                RV=$SRV
            fi
        done
    popd
fi

if test -v LITMUS -a $RV -eq 0; then
    pushd test/perl-framework
       mkdir -p t/htdocs/modules/dav
       ./t/TEST -start
       # litmus uses $TESTS, so unset it.
       unset TESTS
       litmus http://localhost:8529/modules/dav/
       RV=$?
       ./t/TEST -stop
    popd
fi

if test -v TEST_CORE -a $RV -eq 0; then
    # Run HTTP/2 tests.
    MPM=event py.test-3 test/modules/core
    RV=$?
fi

if test -v TEST_PROXY -a $RV -eq 0; then
    # Run proxy tests.
    py.test-3 test/modules/proxy
    RV=$?
fi

if test -v TEST_H2 -a $RV -eq 0; then
    # Build the test clients
    (cd test/clients && make)
    # Run HTTP/2 tests.
    MPM=event py.test-3 test/modules/http2
    RV=$?
    if test $RV -eq 0; then
      MPM=worker py.test-3 test/modules/http2
      RV=$?
    fi
fi

if test -v TEST_MD -a $RV -eq 0; then
    # Run ACME tests.
    # need the go based pebble as ACME test server
    # which is a package on debian sid, but not on focal
    # FAILS on TRAVIS with
    # package github.com/letsencrypt/pebble/cmd/pebble
    #         imports crypto/ed25519: unrecognized import path "crypto/ed25519" (import path does not begin with hostname)
    #
    # but works on a docker ubuntu-focal image. ???
    export GOPATH=${PREFIX}/gocode
    mkdir -p "${GOPATH}"
    export PATH="${GOROOT}/bin:${GOPATH}/bin:${PATH}"
    go get -u github.com/letsencrypt/pebble/...
    (cd $GOPATH/src/github.com/letsencrypt/pebble && go install ./...)

    py.test-3 test/modules/md
    RV=$?
fi

# Catch cases where abort()s get logged to stderr by libraries but
# only cause child processes to terminate e.g. during shutdown,
# which may not otherwise trigger test failures.

# "glibc detected": printed with LIBC_FATAL_STDERR_/MALLOC_CHECK_
# glibc will abort when malloc errors are detected.  This will get
# caught by the segfault grep as well.

# "pool concurrency check": printed by APR built with
# --enable-thread-debug when an APR pool concurrency check aborts

for phrase in 'Segmentation fault' 'glibc detected' 'pool concurrency check:' 'Assertion.*failed'; do
    # Ignore IO/debug logs
    if grep -v ':\(debug\|trace[12345678]\)\]' test/perl-framework/t/logs/error_log | grep -q "$phrase"; then
        grep --color=always -C5 "$phrase" test/perl-framework/t/logs/error_log
        RV=2
    fi
done

if test -v TEST_UBSAN && test -n "`ls ubsan.log.* 2>/dev/null`"; then
    cat ubsan.log.*
    RV=3
fi

if test -v TEST_ASAN && test -n "`ls asan.log.* 2>/dev/null`"; then
    cat asan.log.*

    # ASan can report memory leaks, fail on errors only
    if grep -q "ERROR: AddressSanitizer:" `ls asan.log.*`; then
        RV=4
    fi
fi

for core in `ls test/perl-framework/t/core{,.*} test/gen/apache/core{,.*} 2>/dev/null`; do
    gdb -ex 'thread apply all backtrace full' -batch ./httpd "$core"
    RV=5
done

exit $RV
