#!/bin/bash

function die() {
    echo "Error:  ${*}" 1>&2
    exit 1
}

error() { echo "$@" 1>&2; }
get_snap_version_info() {
    # get_snap_version_info(snap)
    # set SNAP_VERSION and SNAP_REVISION to the installed 'version' and 'rev'
    # of the provided 'snap'. The caller must export or scope (using local) if
    # desired.
    local name="${1:-git-ubuntu}" out=""
    command -v snap >/dev/null 2>&1 || {
        error "no 'snap' command available."
        return 1
    }
    out=$(snap list "$name") || {
        error "failed 'snap list $name'. Is snap '$name' installed?"
        return 1;
    }
    set -- $(echo "$out" |
        while read curname ver rev track pub notes other; do
            [ "$curname" = "$name" ] && echo "$rev" "$ver" && exit 0
        done
        exit 1)
    [ -n "$1" -a -n "$2" ] || {
        error "Failed reading output of 'snap list $name'"
        return 1
    }
    SNAP_REVISION="$1" SNAP_VERSION="$2"
}

snap_run() {
    # snap_run([--exec,] snap, cmd, args...)
    #  execute the provided command in the snap environment.
    #  if '--exec' is provided, then use 'exec' (do not return).
    #
    # executing 'snap run --shell <snap> cmd arg1 arg2...'
    # results in snap changing the environment and then executing:
    #  /bin/sh cmd arg1 arg2 ...
    # (with cmd, arg1, arg2 as separate argv)
    #
    # It was probably only ever intended to be used to invoke an interactive
    # shell.  But we use it here to invoke arbitrary commands while keeping
    # positional parameters correct and avoiding any shell interpretation
    # of cmd or args.  We invoke:
    #  /bin/sh -c 'exec "$0" "$@"' cmd arg1 arg2...
    #
    # The end result is that 'cmd' is correctly invoked with exactly the args
    # provided and no need to worry about shell interpretation.
    local exec="" snap=""
    [ "$1" = "--exec" ] && { exec="exec"; shift; }
    snap="$1"
    shift
    $exec snap run --shell "$snap" -c 'exec "$0" "$@"' "$@"
}

if [ -n "$TEST_SYSTEM_TREE" ]; then
    pkg_dir=$(dirname $(python3 -c 'import gitubuntu; print(gitubuntu.__path__[0])')) 
    snap_bin_glob=/usr/bin/git-ubuntu.*
    pylintrc_path=/usr/share/git-ubuntu/pylintrc
elif [ -n "$SNAP" -a -z "$TEST_TREE" ]; then
    pkg_dir=${SNAP}/usr/lib/python3/dist-packages
    snap_bin_glob="${SNAP}/usr/bin/git-ubuntu.*"
    pylintrc_path="${SNAP}/usr/share/git-ubuntu/pylintrc"
    [ -d "$pkg_dir" ] ||
        { error "$pkg_dir is not a directory"; exit 1; }
else
    if [ -z "$TEST_TREE" ]; then
        if [ -d "./gitubuntu" ]; then
            export TEST_TREE="."
        else
            error "ERROR: Neither SNAP nor TEST_TREE set in environment."
            error "And no 'gitubuntu' dir in $PWD" 1>&2
            exit 3
        fi
    fi
    if [ ! -d "$TEST_TREE/gitubuntu" ]; then
        error "ERROR: TEST_TREE=$TEST_TREE but no $TEST_TREE/gitubuntu"
        exit 3
    fi
    if [ -z "$SNAP" ]; then
        if ! get_snap_version_info git-ubuntu; then
            error "Must have git-ubuntu snap installed to test tree."
            exit 3
        fi
        export SNAP_REVISION SNAP_VERSION
        snap_run --exec git-ubuntu "$0" "$@"
    fi
    pkg_dir=$( cd "${TEST_TREE%/}" && pwd )
    snap_bin_glob="${pkg_dir}/bin/*.py"
    pylintrc_path="$TEST_TREE/.pylintrc"
fi

retval=0

# Create temporary directory for test artifacts
tmp_dir=$(mktemp -d -t "ci-$(date +%Y%m%d)-XXXXXXXXXX")
[ -d "${tmp_dir}" ] \
    || die "Could not create ${tmp_dir}"

cd "${pkg_dir}" \
    || die "Could not chdir to ${pkg_dir}"

ln -s "${pkg_dir}/gitubuntu" "${tmp_dir}" \
    || die "Could not create link to gitubuntu module in ${tmp_dir}"

if [ -n "$TEST_SYSTEM_TREE" ]; then
    error "Testing git-ubuntu system installation."
else
    error "Inside git-ubuntu snap $SNAP_REVISION/$SNAP_VERSION."
fi
error "Testing tree in $pkg_dir/gitubuntu"
error "Working dir $PWD"

function cleanup() {
    rm "${tmp_dir}/gitubuntu"
    rm -rf "${tmp_dir}"
}
trap cleanup EXIT

if [ -z "$TEST_SYSTEM_TREE" ]; then
    # Check dependencies are installed
    if python3 -m pip check
    then
        echo "pip3 found all required dependencies"
    else
        echo "pip check failed; self test will fail"
        retval=1
    fi
fi

# Style check python modules
# For some reason, pylint hangs indefinitely if used without a -j option (the
# default is two) in the multiprocessing module. As a workaround for now, we
# specify -j1, which seems to work.
if python3 -m pylint --rcfile "$pylintrc_path" -E gitubuntu/ -j1
then
    echo "pylint passed!"
else
    echo "pylint failed; self test will fail"
    retval=1
fi

# Check python scripts in ${SNAP}/bin
while IFS= read -r -d '' script_source; do
    # py_compile will be writing .pyc to a __pycache__ directory adjacent to
    # the script.  Since we may be testing the script at a read-only location
    # (as will happen when jenkins tests the snap), we need to move the script
    # to a read-write location temporarily.
    #
    # Note that starting in python 3.8, there is a -B flag which
    # suppresses the creation of .pyc files; so, once this version of python
    # can be assumed, this logic can be simplified by avoiding the copying.
    cp "${script_source}" "${tmp_dir}/"
    script=$(basename "${script_source}")

    # Python version 3 basic syntax check.  Also catches code that is
    # valid for python2 but not python3.
    if python3 -m py_compile "${tmp_dir}/${script}"
    then
        echo "PASS (syntax) ${script}"
    else
        echo "FAIL (syntax) ${script}"
        continue
    fi

    # Smoke test compilation by attempting to import the script as if it
    # were a module, but only if the script handles __main__.  This catches
    # errors due to attempted imports of python module dependencies, and may
    # catch other coding errors not detected by pycompile3.
    if grep "if __name__ == '__main__':" "${tmp_dir}/${script}" > /dev/null
    then
	cd "${tmp_dir}" \
	   || die "Could not chdir to ${tmp_dir}"
        if python3 -m "${script%.py}" --help > /dev/null 2>&1
	then
            echo "PASS (compilation) ${script}"
        else
            echo "FAIL (compilation) ${script}; self test will fail"
            retval=1
        fi
    else
        echo "SKIP (compilation) ${script}"
    fi
done < <(compgen -G "$snap_bin_glob")

# Run the unit testsuite
if COVERAGE_FILE="${tmp_dir}/coverage.txt" python3 -m pytest -p no:cacheprovider --cov=gitubuntu gitubuntu/*
then
    echo "pytest passed!"
else
    echo "pytest failed; self test will fail"
    retval=1
fi

exit ${retval}
