#!/bin/bash -efu

# Called from standard-test-basic role to run basic tests
# This script is copied to test-environment.

debug() {
    if [ -n "$DEBUG" ]; then
        echo "$*" >&2
    fi
}

msg_usage() {
    cat << EOF

Run basic test.

Usage:
$PROG <options>

Options:
-h, --help              display this help and exit
-v, --verbose           turn on debug
-w, --workdir           test environment work dir. Directory for test execution.
-a, --artifactsdir      test environment dir to store artifacts
-t, --testname          name of the test
    --timeout           test timeout
-c, --cmd               shell command to run the test
    --save-files        list of files to be saved to artifacts, files are separated by comma. Path is relative to workdir
EOF
}

# Entry

PROG="${PROG:-${0##*/}}"
DEBUG="${DEBUG:-}"
STR_CMD="${STR_CMD:-}"
STR_DEBUG="${STR_DEBUG:-}"
STR_VERBOSE="${STR_VERBOSE:-}"
STR_WORKDIR="${STR_WORKDIR:-}"
STR_TEST_NAME="${STR_TEST_NAME:-}"
STR_ARTIFACTS_DIR="${STR_ARTIFACTS_DIR:-/tmp}"
STR_TIMEOUT="${STR_TIMEOUT:-0}"
STR_SAVE_FILES="${STR_SAVE_FILES:-}"

# http://wiki.bash-hackers.org/howto/getopts_tutorial
opt=$(getopt -n "$0" --options "hvt:w:a:c:" --longoptions "help,verbose,cmd:,testname:,workdir:,artifactsdir:,timeout:,save-files:" -- "$@")
eval set -- "$opt"
while [[ $# -gt 0 ]]; do
    case "$1" in
        -t|--testname)
            STR_TEST_NAME="$2"
            shift 2
            ;;
        -c|--cmd)
            STR_CMD="$2"
            shift 2
            ;;
        -w|--workdir)
            STR_WORKDIR="$2"
            shift 2
            ;;
        -a|--artifactsdir)
            STR_ARTIFACTS_DIR="$2"
            shift 2
            ;;
        --timeout)
            STR_TIMEOUT="$2"
            shift 2
            ;;
        --save-files)
            STR_SAVE_FILES=$(echo "$2" | tr "," " ")
            shift 2
            ;;
        -v|--verbose)
            DEBUG="-v"
            shift
            ;;
        -h|--help)
            msg_usage
            exit 0
            ;;
        --)
            shift
            ;;
        *)
            msg_usage
            exit 1
    esac
done

# Entry

if [ -z "$STR_TEST_NAME" ] || [ -z "$STR_WORKDIR" ] || [ -z "$STR_CMD" ]; then
    echo "Use: $PROG -h for help."
    exit 0
fi

mkdir -p "$STR_WORKDIR"
mkdir -p "$STR_ARTIFACTS_DIR"
STR_WORKDIR="$(realpath "$STR_WORKDIR")"
STR_ARTIFACTS_DIR="$(realpath "$STR_ARTIFACTS_DIR")"
debug "Test: $STR_TEST_NAME"
debug "Command: $STR_CMD"
debug "Work dir: $STR_WORKDIR"
debug "Artifacts dir: $STR_ARTIFACTS_DIR"
debug "Timeout: $STR_TIMEOUT"

# Up to this point any fail is considered as ci-sytem fail. Exit code != 0.
# Starting from this point and bellow any fail is considered as a test fail. Exit code == 0.

clean_exit() {
    rc=$?
    # WARNING! At this place ansible closes all FD for STDIN STDERR.
    # echo "something" > ANY will not work, and will fail
    # With the above do next relax:
    set +efu
    # Also any output to old tee-STDERR/STDOUT will terminate clean_exit()
    trap - SIGINT SIGTERM SIGABRT EXIT # clear the trap
    echo "Run test '$STR_TEST_NAME': done. Test's exit code: $rc" >&4
    if [[ $terminated_outside -eq 1 ]]; then
        echo "The test was terminated outside." >&4
        echo "Mark current test as ERROR." >&4
        rc=77
    fi
    # Exit code == 0, no matter of the test result.
    # Close tee pipes
    for pid in $(ps -o pid --no-headers --ppid $$ 2>/dev/null); do
        if [ -n "$(ps -p $pid -o pid= 2>/dev/null)" ]; then
            kill -s HUP $pid > /dev/null 2>&1
        fi
    done
    # At this place STDIN/STDOUT(tee) are closed.
    # Can work original STDOUT/STDERR &3 and &4.
    # Depends how this command was invoked.
    local status="FAIL"
    # Return non-zero when test command not found
    if [[ $rc -eq 127 ]]; then
        echo "$STR_TEST_NAME (problem with test execution)" >&4
        status="ERROR"
    elif [[ $rc -eq 77 ]]; then
        # Test was terminated outside.
        # For example: ansible-playbook was terminated
        echo "$STR_TEST_NAME (test was terminated outside)" >&4
        status="ERROR"
    elif [[ $rc -eq 124 ]]; then
        # test case timed out
        echo "$STR_TEST_NAME (test aborted due to timeout)" >&4
        status="ERROR"
    elif [[ $rc -eq 0 ]]; then
        status="PASS"
    fi
    local run_journal="$STR_ARTIFACTS_DIR/test.log"
    echo "${status} $STR_TEST_NAME" >> "$run_journal"
    # Handle results.yml file, rename logs
    local results="$STR_ARTIFACTS_DIR/results.yml"
    local result=$(echo $status | tr '[:upper:]' '[:lower:]')
    test -f "$results" || echo 'results:' > "$results"
    printf '%s\n' '' \
        "- test: $STR_TEST_NAME" \
        "  result: $result"      \
        "  runtime: $runtime_seconds" \
        "  logs:"  \
        >> "$results"
    for log in "$logfile"; do
        if [ -f "$log" ]; then
            local prefixed_log="$STR_ARTIFACTS_DIR/${status}-$(basename $log)"
            mv -f "$log" "$prefixed_log"
            echo "  - $(basename $prefixed_log)" >> "$results"
        fi
    done
    for file in $STR_SAVE_FILES; do
        if [[ ! -d $STR_ARTIFACTS_DIR/$STR_TEST_NAME ]]; then
            mkdir -p $STR_ARTIFACTS_DIR/$STR_TEST_NAME
        fi
        set +f # allow expand wildcards on file name, like output*.log
        # if for some reason file doesn't exist, just ignore it
        cp -f $STR_WORKDIR/$file $STR_ARTIFACTS_DIR/$STR_TEST_NAME || true
        set -f
    done
    # If killed outside, return code will not be 0, no matter what.
    exit 0
}

# next vars are used in clean_exit()
runtime_start="$(date '+%s')"
# runtime == 0 if test is not started or terminated outside
runtime_finish="$runtime_start"
runtime_seconds=0

trap clean_exit SIGINT SIGTERM SIGABRT EXIT
terminated_outside=1
rc=0

export PATH="$PATH:$STR_WORKDIR"
mkdir -p "$STR_ARTIFACTS_DIR"
# add str_ prefix to test logs
logfile="$STR_ARTIFACTS_DIR/$(echo "str_$STR_TEST_NAME" | sed -e 's/\//-/g').log"
logfile="$(realpath "$logfile")"
# Save real STDIN/STDERR to 3 and 4
exec 3>&1 4>&2 > >(tee -a "$logfile") 2>&1
cd "$STR_WORKDIR"
# Purpose to spawn new bash is to ignore -efu setting for current shell
# Test command: run-basic-test -w wodir -c 'false; echo 123; echo 333 >&2; touch "123 123"; exit 43' -t my_test -a logs -v
if [ -f "$STR_CMD" ]; then
    chmod 0775 "$STR_CMD"
fi
timeout --foreground "$STR_TIMEOUT" bash -c "$STR_CMD" || rc=$?
runtime_finish="$(date '+%s')"
runtime_seconds=$((runtime_finish - runtime_start))
# `terminated_outside` epxlanation:
# This is for a case when STR is driven by STR-pipeline.
# The STR-pipeline has code: `timeout 4h ansible-playbook`.
# `ansible-playbook` is terminated by `timeout` command.
# Active ansible-task (this scipt) is terminated with HUP signal.
# In this case `clean_exit()` is called with:
# Signal: HUP
# Rerurn code: 0 (the `timeout` command will be terminated itself ==> no return code from test command)
# If terminated_outside==1 mark unfinished script as failed.
terminated_outside=0
# Explicit return code
exit $rc
