#!/bin/bash
# ------------------------------------------------------------------------------
# (C) British Crown Copyright 2006-17 Met Office.
#
# This file is part of FCM, tools for managing and building source code.
#
# FCM is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# FCM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with FCM. If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------------------------------------------
# NAME
#     test_header
#
# SYNOPSIS
#     . $FCM_HOME/t/lib/bash/test_header
#
# DESCRIPTION
#     Provide bash shell functions for writing tests for "fcm" commands to
#     output in Perl's TAP format. Add "set -eu". Create a temporary working
#     directory $TEST_DIR and change to it. Automatically increment test number.
#     If $FCM_HOME is not specified, set it to point to the "fcm" source tree
#     containing this script. Add $FCM_HOME/bin to the front of $PATH.
#
# FUNCTIONS
#     tests N
#         echo "1..$N".
#     skip N REASON
#         echo "ok $((++T)) # skip REASON" N times, where T is the test number.
#     skip_all REASON
#         echo "1..0 # SKIP $REASON" and exit.
#     pass TEST_KEY
#         echo "ok $T - $TEST_KEY" where T is the current test number.
#     fail TEST_KEY
#         echo "not ok $T - $TEST_KEY" where T is the current test number.
#     run_pass TEST_KEY COMMAND ...
#         Run $COMMAND. pass/fail $TEST_KEY if $COMMAND returns true/false.
#         Write STDOUT and STDERR in $TEST_KEY.out and $TEST_KEY.err.
#     run_fail TEST_KEY COMMAND ...
#         Run $COMMAND. pass/fail $TEST_KEY if $COMMAND returns false/true.
#         Write STDOUT and STDERR in $TEST_KEY.out and $TEST_KEY.err.
#     file_cmp TEST_KEY FILE_ACTUAL [$FILE_EXPECT]
#         Compare contents in $FILE_ACTUAL and $FILE_EXPECT. pass/fail
#         $TEST_KEY if contents are identical/different. If $FILE_EXPECT is "-"
#         or not defined, compare $FILE_ACTUAL with STDIN to this function.
#     file_test TEST_KEY FILE [OPTION]
#         pass/fail $TEST_KEY if "test $OPTION $FILE" returns 0/1. $OPTION is
#         -e if not specified.
#     file_grep TEST_KEY PATTERN FILE
#         Run "grep -q PATTERN FILE". pass/fail $TEST_KEY accordingly.
#     branch_tidy INPUT_FILE
#         Standardise branch-create output between Subversion 1.8 and 1.9.
#     commit_sort INPUT_FILE OUTPUT_FILE
#         Sort status and transmitting info within the commit output.
#     diff_sort INPUT_FILE OUTPUT_FILE
#         Sort Subversion diff output by filename.
#     diff_svn_version_filter
#         Preprocess stdin to stdout based on relevant '#IF SVN1.X' prefixes.
#     status_sort INPUT_FILE OUTPUT_FILE
#         Sort Subversion status lines.
#     merge_sort INPUT_FILE OUTPUT_FILE
#         Sort Subversion merge status lines.
#     check_svn_version
#         Check Subversion version and skip tests if not compatible.
#     FINALLY
#         This is run on EXIT or INT to remove the temporary working directory
#         for the test. Call FINALLY_MORE if it is declared.
#
# VARIABLES
#     FCM_HOME
#         Root of FCM's installation. (Exported.)
#     SIGNALS
#         List of signals trapped by FINALLY, currently EXIT and INT.
#     TEST_DIR
#         Temporary directory that is also the working directory for this test.
#     TEST_KEY_BASE
#         Base root name of current test file.
#     TEST_NUMBER
#         Test number of latest test.
#     TEST_SOURCE_DIR
#         Directory containing the current test file.
#-------------------------------------------------------------------------------
set -eu

SIGNALS="EXIT INT"
TEST_DIR=
function FINALLY() {
    for S in $SIGNALS; do
        trap '' $S
    done
    if [[ -n $TEST_DIR ]]; then
        cd ~
        rm -rf $TEST_DIR
    fi
    if declare -F FINALLY_MORE >/dev/null; then
        FINALLY_MORE
    fi

}
for S in $SIGNALS; do
    trap "FINALLY $S" $S
done

TEST_NUMBER=0

function tests() {
    echo "1..$1"
}

function skip() {
    local N_SKIPS=$1
    shift 1
    local I=0
    while ((I++ < N_SKIPS)); do
        echo "ok $((++TEST_NUMBER)) # skip $@"
    done
}

function skip_all() {
    echo "1..0 # SKIP $@"
    exit
}

function pass() {
    echo "ok $((++TEST_NUMBER)) - $@"
}

function fail() {
    echo "not ok $((++TEST_NUMBER)) - $@"
}

function run_pass() {
    local TEST_KEY=$1
    shift 1
    if ! "$@" 1>$TEST_KEY.out 2>$TEST_KEY.err; then
        fail $TEST_KEY
        return
    fi
    pass $TEST_KEY
}

function run_fail() {
    local TEST_KEY=$1
    shift 1
    if "$@" 1>$TEST_KEY.out 2>$TEST_KEY.err; then
        fail $TEST_KEY
        return
    fi
    pass $TEST_KEY
}

function file_cmp() {
    local TEST_KEY=$1
    local FILE_ACTUAL=$2
    local FILE_EXPECT=${3:--}
    if diff -u $FILE_EXPECT $FILE_ACTUAL >&2; then
        pass $TEST_KEY
        return
    fi
    fail $TEST_KEY
}

function file_test() {
    local TEST_KEY=$1
    local FILE=$2
    local OPTION=${3:--e}
    if test $OPTION $FILE; then
        pass $TEST_KEY
    else
        fail $TEST_KEY
    fi
}

function file_grep() {
    local TEST_KEY=$1
    local PATTERN=$2
    local FILE=$3
    if grep -q -e "$PATTERN" $FILE; then
        pass $TEST_KEY
        return
    fi
    fail $TEST_KEY
}

function branch_tidy() {
    local INPUT_FILE=$TEST_DIR/$1
    sed -i "/^Committing transaction/d; /^$/d" "$INPUT_FILE"
}

function commit_sort() {
    local INPUT_FILE=$1
    local OUTPUT_FILE=$2
    local TMP_OUTPUT_FILE=$(mktemp)
    # Sort the svn status part of the message
    status_sort $INPUT_FILE $TMP_OUTPUT_FILE
    # Sort the 'Adding/Deleting', etc part of the message
    python -c 'import re, sys
text = sys.stdin.read()
sending_lines = re.findall("^\w+ing  +.*$", text, re.M)
prefix = text[:text.index(sending_lines[0])]
suffix = text[(text.index(sending_lines[-1]) + len(sending_lines[-1])):]
sending_lines.sort()
print prefix + "\n".join(sending_lines) + suffix.rstrip()
' <"$TMP_OUTPUT_FILE" >"$OUTPUT_FILE"
    rm "$TMP_OUTPUT_FILE"
    # Remove 1.8 to 1.9 specific changes (transmitting, transaction lines).
    sed -i "/^Transmitting file data/d; /^Committing transaction/d; /^$/d" \
        "$OUTPUT_FILE"
}

function diff_sort() {
    local INPUT_FILE=$1
    local OUTPUT_FILE=$2
    # Sort the diff file order.
    python -c 'import re, sys
text = sys.stdin.read()
print "\nIndex: ".join(
    [l.strip() for l in sorted(re.compile("^Index: ", re.M).split(text))])
' <"$INPUT_FILE" >"$OUTPUT_FILE"
    # In 1.9, new files are (nonexistent) rather than (working copy).
    sed -i "s/(nonexistent)/(working copy)/" "$OUTPUT_FILE"
}

function diff_svn_version_filter() {
    if "$SVN_VERSION_IS_19"; then
        sed "s/^#IF SVN1.9 //g; /^#IF SVN1.8 /d"
    else
        sed "s/^#IF SVN1.8 //g; /^#IF SVN1.9 /d"
    fi
}

function status_sort() {
    local INPUT_FILE=$1
    local OUTPUT_FILE=$2
    python -c 'import re, sys
text = sys.stdin.read()
status_lines = re.findall("^.{7} [\w./].*$", text, re.M)
prefix = text[:text.index(status_lines[0])]
suffix = text[(text.index(status_lines[-1]) + len(status_lines[-1])):]
status_lines.sort()
print prefix + "\n".join(status_lines) + suffix.rstrip()
' <"$INPUT_FILE" >"$OUTPUT_FILE"
}

function merge_sort() {
    local INPUT_FILE=$1
    local OUTPUT_FILE=$2
    python -c 'import re, sys
text = sys.stdin.read()
status_lines = []
for line in text.splitlines():
    if line.startswith("Enter \"y\"") and ": " in line:
        head, tail = line.split(": ", 1)
        if status_lines:
            print "\n".join(sorted(status_lines))
            status_lines = []
        print head + ": "
        if tail:
            line = tail
    if re.search("^.{4} [\w./].*$", line):
        status_lines.append(line)
    elif status_lines:
        print "\n".join(sorted(status_lines))
        print line
        status_lines = []
    else:
        print line
if status_lines:
    print "\n".join(sorted(status_lines))
' <"$INPUT_FILE" >"$OUTPUT_FILE"
}

function check_svn_version() {
    if ! svn --version | head -1 | grep -q "^svn, version 1\.\(8\|9\)"; then
        skip_all "Tests require Subversion 1.8 or 1.9"
        exit 0
    fi
}

function fcm_make_build_hello_tests() {
    local TEST_KEY=$1
    local HELLO_EXT=${2:-}
    shift 2
    rm -fr \
        .fcm-make \
        build \
        fcm-make-as-parsed.cfg \
        fcm-make-on-success.cfg \
        fcm-make.log
    run_pass "$TEST_KEY" fcm make "$@"
    file_test "$TEST_KEY.hello$HELLO_EXT" "$PWD/build/bin/hello$HELLO_EXT"
    "$PWD/build/bin/hello$HELLO_EXT" >"$TEST_KEY.hello$HELLO_EXT.out"
    file_cmp "$TEST_KEY.hello$HELLO_EXT.out" \
        "$TEST_KEY.hello$HELLO_EXT.out" <<'__OUT__'
Hello World!
__OUT__
}

FCM_HOME=${FCM_HOME:-$(cd $(dirname $(readlink -f $BASH_SOURCE))/../../.. && pwd)}
export FCM_HOME
PATH=$FCM_HOME/bin:$PATH

SVN_VERSION_IS_19=false
if svn --version 1>/dev/null 2>&1; then
    SVN_VERSION=$(svn --version | \
                  sed -n "s/^svn, version \([0-9.]\+\) .*/\1/p")
    if [[ $SVN_VERSION =~ "1.9." ]]; then
        SVN_VERSION_IS_19=true
    fi
fi

TEST_KEY_BASE=$(basename $0 .t)
TEST_SOURCE_DIR=$(cd $(dirname $0) && pwd)
TEST_DIR=$(mktemp -d)
export LC_ALL=C
export LANG=C
cd $TEST_DIR

set +e
