#!/bin/bash
PYTHON=${PYTHON:-python3}
ERR=()
RESULTS_DIR=$($PYTHON -m avocado config | grep datadir.paths.logs_dir | awk '{print $2}')
# Very basic version of expanduser
RESULTS_DIR="${RESULTS_DIR/#\~/$HOME}"

run_rc() {
    CHECK=$1
    shift
    echo -e "\n\e[32mRunning '$1'\e[0m"
    eval $*
    if [ $? != 0 ]; then
        echo -e "\e[31m$CHECK FAILED\e[0m"
        ERR+=("$CHECK")
        exit 1
    else
        echo -e "\e[32m$CHECK PASSED\e[0m\n"
    fi
}


parallel_selftests() {
    local START=$(date +%s)
    local ERR=0
    local FIND_UNITTESTS=$(readlink -f ./selftests/list)
    local NO_WORKERS=$(($(cat /proc/cpuinfo | grep -c processor) * 2))

    # The directories that may contain files with tests, from the Avocado core
    # and from all optional plugins
    declare -A TESTS
    TESTS[selftests]=$(${PYTHON} ${FIND_UNITTESTS} | sort -R)
    for PLUGIN in $(find optional_plugins -mindepth 1 -maxdepth 1 -type d); do
        PLUGIN_BASENAME=$(basename $PLUGIN)
        THIS_DIR_TESTS=$(${PYTHON} ${FIND_UNITTESTS} --no-base-selftests --plugin-dirs $PLUGIN_BASENAME | sort -R)
        if [ -n "$THIS_DIR_TESTS" ]; then
            TESTS[$PLUGIN]=${THIS_DIR_TESTS};
        fi
    done

    for TEST_DIR in "${!TESTS[@]}"; do
        if [ "x$TEST_DIR" != "xselftests" ]; then
            OLD_PWD=$PWD
            cd $TEST_DIR/tests
        fi

        declare -a ALL
        ALL=(${TESTS[$TEST_DIR]})

        local PER_SLICE=$((${#ALL[@]} / $NO_WORKERS))
        [ $PER_SLICE -eq 0 ] && PER_SLICE=1
        local PIDS=()
        local TMPS=()

        for I in $(seq 0 $PER_SLICE $((${#ALL[@]} - 1))); do
            TMP=$(mktemp /tmp/avocado_parallel_unittest_output_XXXXXX)
            TMPS+=("$TMP")
            ( $PYTHON -m unittest ${ALL[@]:$I:$PER_SLICE} &> $TMP ) &
            PIDS+=("$!")
            sleep 0.1
        done

        FAILED_ONCE=()
        for I in $(seq 0 $((${#PIDS[@]} - 1))); do
            wait ${PIDS[$I]}
            RET=$?
            if [ $RET -ne 0 ]; then
                for FAILURE in $(cat "${TMPS[$I]}" | sed -n 's/\(ERROR\|FAIL\): \([^ ]*\) (\([^)]*\)).*/\3.\2/p'); do
                    FAILED_ONCE+=("$FAILURE")
                done
                # On Python 3.4, load errors are not treated as test failures, and we can
                # not easily tell which test failed to be loaded.  Let's return immediately.
                grep -q "AttributeError: 'module' object has no attribute" "${TMPS[$I]}"
                if [ $? == 0 ]; then
                    echo
                    echo ----------------------------------------------------------------------
                    echo "ERROR: failed to load at least 1 test"
                    echo "Check the following log output for more information:"
                    cat "${TMPS[$I]}"
                    echo ----------------------------------------------------------------------
                    return 1;
                fi
            else
                rm ${TMPS[$I]}
            fi
        done
        if [ ${#FAILED_ONCE[@]} -gt 0 ]; then
            if [ ${#FAILED_ONCE[@]} -le 24 ]; then
                echo ${#FAILED_ONCE[@]} failed during parallel execution, trying them in series
                echo "$PYTHON -m unittest --failfast ${FAILED_ONCE[@]}"
                if $PYTHON -m unittest --failfast ${FAILED_ONCE[@]}; then
                    echo "All failed tests passed when executed in series"
                    echo
                    for I in $(seq 0 $((${#PIDS[@]} - 1))); do
                        [ -e "${TMPS[$I]}" ] && rm "${TMPS[$I]}"
                    done
                else
                    echo
                    echo "Some test(s) failed in series as well, showing failures from parallel run:"
                    ERR=1
                fi
            else
                echo "${#FAILED_ONCE[@]} tests failed during execution, not trying to re-run them."
                ERR=1
            fi
        fi

        # Remove all tmp files
        for I in $(seq 0 $((${#PIDS[@]} - 1))); do
            if [ -e "${TMPS[$I]}" ]; then
                echo
                echo $PYTHON -m unittest ${ALL[@]:$(($I * $PER_SLICE)):$PER_SLICE}
                cat "${TMPS[$I]}"
                rm "${TMPS[$I]}"
            fi
        done
        echo ----------------------------------------------------------------------
        echo Ran ${#ALL[@]} tests for $TEST_DIR in $(($(date +%s) - START))s
        if [ -n $TEST_DIR ]; then
            cd $OLD_PWD
        fi
    done;
    return $ERR
}


results_dir_content() {
    NOW="$(ls $RESULTS_DIR)"
    if [ "$(echo $* | xargs)" != "$(echo $NOW | xargs)" ]; then
        echo "The output of '$RESULTS_DIR' is not the same as before running the checks"
        echo "ORIGINAL:"
        echo "$*"
        echo "NOW:"
        echo "$NOW"
        return 1
    else:
        echo "No extra files were created in '$RESULTS_DIR'"
        return 0
    fi
}

[ "$SKIP_RESULTSDIR_CHECK" ] || RESULTS_DIR_CONTENT="$(ls $RESULTS_DIR 2> /dev/null)"

run_rc lint "selftests/inspekt-lint.sh"
run_rc indent "selftests/inspekt-indent.sh"
run_rc style "selftests/inspekt-style.sh"
run_rc boundaries "selftests/modules-boundaries.sh"
run_rc signed-off-by 'selftests/signedoff-check.sh'
if [ "$AVOCADO_PARALLEL_CHECK" ]; then
    run_rc selftests parallel_selftests
else
    run_rc selftests "$PYTHON selftests/run"
fi
[ "$SKIP_RESULTSDIR_CHECK" ] || run_rc job-results results_dir_content "$RESULTS_DIR_CONTENT"

if [ "$ERR" ]; then
    echo -e "\e[31m"
    echo "Checks:"
    for CHECK in "${ERR[@]}"; do
        echo -e " * $CHECK FAILED"
    done
    echo -ne "\e[0m"
else
    echo -e "\e[32mAll checks PASSED\e[0m"
fi
if [ "$ERR" ]; then
    exit 1
fi
exit 0
