#!/bin/bash
#
#  Copyright (c) 2018, The OpenThread Authors.
#  All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are met:
#  1. Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#  2. Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#  3. Neither the name of the copyright holder nor the
#     names of its contributors may be used to endorse or promote products
#     derived from this software without specific prior written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
#  POSSIBILITY OF SUCH DAMAGE.
#
#    Description:
#      This file runs various tests of OpenThread.
#

set -euo pipefail

readonly OT_BUILD_JOBS=$(getconf _NPROCESSORS_ONLN)
readonly OT_BUILDDIR="$(pwd)/build"
readonly OT_SRCDIR="$(pwd)"
readonly OT_TEST_COMMON_OPTIONS=(
    "-DOT_CHILD_SUPERVISION=ON"
    "-DOT_COVERAGE=ON"
    "-DOT_MAC_FILTER=ON"
    "-DOT_REFERENCE_DEVICE=ON"
)

readonly COLOR_PASS='\033[0;32m'
readonly COLOR_FAIL='\033[0;31m'
readonly COLOR_NONE='\033[0m'

readonly NODE_MODE="${NODE_MODE:-standalone}"
readonly NODE_TYPE="${NODE_TYPE:-sim}"
readonly THREAD_VERSION="${THREAD_VERSION:-1.1}"
readonly VERBOSE="${VERBOSE:-0}"
readonly VIRTUAL_TIME="${VIRTUAL_TIME:-1}"

build_simulation()
{
    local version="$1"
    local options=("${OT_TEST_COMMON_OPTIONS[@]}")

    options+=(
        "-DOT_PLATFORM=simulation"
        "-DOT_THREAD_VERSION=${version}"
    )

    if [[ "${VIRTUAL_TIME}" == 1 ]]; then
        options+=("-DOT_SIMULATION_VIRTUAL_TIME=ON")

        if [[ "${NODE_MODE}" == "rcp" ]]; then
            options+=("-DOT_SIMULATION_VIRTUAL_TIME_UART=ON")
        fi

    fi

    local builddir="${OT_BUILDDIR}/cmake/openthread-simulation-${version}"
    (mkdir -p "${builddir}" \
        && cd "${builddir}" \
        && cmake -GNinja "${OT_SRCDIR}" "${options[@]}" \
        && ninja)

}

build_posix()
{
    local version="$1"
    local options=("${OT_TEST_COMMON_OPTIONS[@]}")

    options+=(
        "-DOT_PLATFORM=posix"
        "-DOT_THREAD_VERSION=${version}"
    )

    if [[ "${VIRTUAL_TIME}" == 1 ]]; then
        options+=("-DOT_POSIX_VIRTUAL_TIME=ON")
    fi

    local builddir="${OT_BUILDDIR}/cmake/openthread-posix-${version}"
    (mkdir -p "${builddir}" \
        && cd "${builddir}" \
        && cmake -GNinja "${OT_SRCDIR}" "${options[@]}" \
        && ninja)
}

do_build() {
    build_simulation "${THREAD_VERSION}"

    if [[ "${NODE_MODE}" == "rcp" ]]; then
        build_posix "${THREAD_VERSION}"
    fi

    # Extra 1.1 build for 1.2 tests
    if [[ "${THREAD_VERSION}" == "1.2" ]]; then
        build_simulation 1.1

        if [[ "${NODE_MODE}" == "rcp" ]]; then
            build_posix 1.1
        fi
    fi
}

do_clean() {
    rm -rfv "${OT_BUILDDIR}"
}

do_cert() {
    export top_builddir="${OT_BUILDDIR}/cmake/openthread-simulation-${THREAD_VERSION}"

    if [[ "${THREAD_VERSION}" == "1.2" ]]; then
        export top_builddir_1_1="${OT_BUILDDIR}/cmake/openthread-simulation-1.1"
    fi

    [[ ! -d tmp ]] || rm -rvf tmp
    PYTHONUNBUFFERED=1 "$1"
}

do_cert_suite() {
    export top_builddir="${OT_BUILDDIR}/cmake/openthread-simulation-${THREAD_VERSION}"

    if [[ "${THREAD_VERSION}" == "1.2" ]]; then
        export top_builddir_1_1="${OT_BUILDDIR}/cmake/openthread-simulation-1.1"
    fi

    local pass_count=0
    local fail_count=0

    [[ ! -f fail.log ]] || rm fail.log

    for test_case in "$@"; do
        [[ ! -d tmp ]] || rm -rf tmp
        if "${test_case}" &> test.log; then
            echo -e "${COLOR_PASS}PASS${COLOR_NONE} ${test_case}"
            pass_count=$(( pass_count + 1 ))
        else
            echo -e "${COLOR_FAIL}FAIL${COLOR_NONE} ${test_case}"
            fail_count=$(( fail_count + 1 ))
            {
                echo "=============================="
                echo "!!! FAIL:${test_case}"
                echo "=============================="
                cat test.log
            } >> fail.log
        fi
    done

    echo "=================================="
    echo "         Test Summary"
    echo "=================================="
    echo "# TOTAL: $((pass_count + fail_count))"
    echo "# PASS: ${pass_count}"
    echo "# FAIL: ${fail_count}"

    if [[ "${fail_count}" -gt 0 ]]; then
        tr -dc '[:print:]\r\n\t' < fail.log
        exit 1
    else
        exit 0
    fi
}

print_usage() {
    echo "USAGE: [ENVIRONMENTS] $0 COMMANDS

ENVIRONMENTS:
    NODE_TYPE       'sim' for CLI, 'ncp-sim' for NCP. The default is 'sim'.
    NODE_MODE       'rcp' for RCP mode, otherwise for standalone mode. The default is standalone mode.
    VERBOSE         1 to build or test verbosely. The default is 0.
    VIRTUAL_TIME    1 for virtual time, otherwise real time. The default is 1.
    THREAD_VERSION  1.1 for Thread 1.1 stack, 1.2 for Thread 1.2 stack. The default is 1.1.

COMMANDS:
    clean           Clean built files to prepare for new build.
    build           Build project for running tests. This can be used to rebuild the project for changes.
    cert            Run a single thread-cert test. ENVIRONMENTS should be the same as those given to build or update.
    cert_suite      Run a batch of thread-cert tests and summarize the test results. Only echo logs for failing tests.
    help            Print this help.

EXAMPLES:
    # Test CLI with default settings
    $0 clean build cert tests/scripts/thread-cert/Cert_5_1_01_RouterAttach.py
    $0 cert tests/scripts/thread-cert/Cert_5_1_02_ChildAddressTimeout.py

    # Test NCP with default settings
    NODE_TYPE=ncp-sim $0 clean build cert tests/scripts/thread-cert/Cert_5_1_01_RouterAttach.py
    NODE_TYPE=ncp-sim $0 cert tests/scripts/thread-cert/Cert_5_1_02_ChildAddressTimeout.py

    # Test CLI with radio only
    NODE_MODE=rcp $0 clean build cert tests/scripts/thread-cert/Cert_5_1_01_RouterAttach.py
    NODE_MODE=rcp $0 cert tests/scripts/thread-cert/Cert_5_1_02_ChildAddressTimeout.py

    # Test CLI with real time
    VIRTUAL_TIME=0 $0 clean build cert tests/scripts/thread-cert/Cert_5_1_01_RouterAttach.py
    VIRTUAL_TIME=0 $0 cert tests/scripts/thread-cert/Cert_5_1_02_ChildAddressTimeout.py

    # Test Thread 1.2 with real time
    THREAD_VERSION=1.2 VIRTUAL_TIME=0 $0 clean build cert tests/scripts/thread-cert/v1_2_router_5_1_1.py
    THREAD_VERSION=1.2 VIRTUAL_TIME=0 $0 clean build cert_suite tests/scripts/thread-cert/v1_2_*
    "

    exit "$1"
}

do_package() {
    local builddir="${OT_BUILDDIR}/cmake/openthread-sim"
    (mkdir -p "${builddir}" \
        && cd "${builddir}" \
        && cmake "${OT_SRCDIR}" -DOT_PLATFORM="simulation" \
        && make -j"${OT_BUILD_JOBS}" package \
        && ls "${builddir}"/openthread-simulation-*.deb)

    builddir="${OT_BUILDDIR}/cmake/openthread-host"
    (mkdir -p "${builddir}" \
        && cd "${builddir}" \
        && cmake "${OT_SRCDIR}" -DOT_PLATFORM="posix" \
        && make -j"${OT_BUILD_JOBS}" package \
        && ls "${builddir}"/openthread-standalone-*.deb)

    builddir="${OT_BUILDDIR}/cmake/openthread-daemon"
    (mkdir -p "${builddir}" \
        && cd "${builddir}" \
        && cmake "${OT_SRCDIR}" -DOT_PLATFORM="posix" -DOT_DAEMON=1 -DOT_PLATFORM_NETIF=1 -DOT_PLATFORM_UDP=1 \
        && make -j"${OT_BUILD_JOBS}" package \
        && ls "${builddir}"/openthread-daemon-*.deb)
}

envsetup()
{
    if [[ "${NODE_MODE}" == 'rcp' ]] ; then
        export RADIO_DEVICE="${OT_BUILDDIR}/cmake/openthread-simulation-${THREAD_VERSION}/examples/apps/ncp/ot-rcp"
        export OT_CLI_PATH="${OT_BUILDDIR}/cmake/openthread-posix-${THREAD_VERSION}/src/posix/ot-cli"
        export OT_NCP_PATH="${OT_BUILDDIR}/cmake/openthread-posix-${THREAD_VERSION}/src/posix/ot-ncp"

        if [[ "${THREAD_VERSION}" == "1.2" ]]; then
            export RADIO_DEVICE_1_1="${OT_BUILDDIR}/cmake/openthread-simulation-1.1/examples/apps/ncp/ot-rcp"
            export OT_CLI_PATH_1_1="${OT_BUILDDIR}/cmake/openthread-posix-1.1/src/posix/ot-cli"
            export OT_NCP_PATH_1_1="${OT_BUILDDIR}/cmake/openthread-posix-1.1/src/posix/ot-ncp"
        fi
    fi

    export NODE_TYPE VIRTUAL_TIME

    # CMake always works in verbose mode if VERBOSE exists in environments.
    if [[ "${VERBOSE}" == 1 ]]; then
        export VERBOSE
    else
        export -n VERBOSE
    fi
}

main()
{
    envsetup

    if [[ -z "$1" ]]; then
        print_usage 1
    fi

    [[ "${NODE_MODE}" == "rcp" ]] && echo "Using rcp mode" || echo "Using standalone mode"
    [[ "${NODE_TYPE}" == "ncp-sim" ]] && echo "Using NCP node" || echo "Using CLI node"
    [[ "${VIRTUAL_TIME}" == 1 ]] && echo "Using virtual time" || echo "Using real time"
    [[ "${THREAD_VERSION}" == "1.2" ]] && echo "Using Thread 1.2 stack" || echo "Using Thread 1.1 stack"

    while [[ $# != 0 ]]; do
        case "$1" in
            clean)
                do_clean
                ;;
            build)
                do_build
                ;;
            cert)
                shift
                do_cert "$1"
                ;;
            cert_suite)
                shift
                do_cert_suite "$@"
                ;;
            help)
                print_usage
                ;;
            package)
                do_package
                ;;
        esac
        shift
    done
}

main "$@"
