#!/bin/bash

# uses bash's define

set -e

# possible hostnames, always implies first

# gmake isn't on debian?
gnumake="make -C $(realpath $(dirname $0))/testing/kvm"

print-kvm-variable() {
    ${gnumake} --no-print-directory print-kvm-variable VARIABLE=$1
}

kvm_platform=$(print-kvm-variable KVM_PLATFORM)
kvm_testingdir=$(print-kvm-variable KVM_TESTINGDIR)
kvm_baseline=$(print-kvm-variable KVM_BASELINE)
kvm_test_status=$(echo $(print-kvm-variable KVM_TEST_STATUS) | tr ' ' '|')
kvm_test_name=$(print-kvm-variable KVM_TEST_NAME)
kvm_test_flags=$(print-kvm-variable KVM_TEST_FLAGS)
kvm_prefix=($(print-kvm-variable KVM_PREFIX))
kvm_sourcedir=$(print-kvm-variable KVM_SOURCEDIR)
kvm_pidfile=$(print-kvm-variable KVM_PIDFILE)

# repo under test
rutdir=${kvm_testingdir:+$(realpath $(dirname "${kvm_testingdir}"))}

__baseline=${kvm_baseline:+--baseline ${kvm_baseline}}


buildhosts=${kvm_platform}
basehosts=
upgradehosts=
for p in ${kvm_platform} ; do
    basehosts="${basehosts} ${p}-base ${p}-upgrade"
    upgradehosts="${upgradehosts} ${p}-upgrade ${p}-upgrade"
done

# eat newlines
testhosts=$(echo $($(dirname $0)/testing/utils/kvmhosts.sh))

hosts="${testhosts} ${buildhosts} ${basehosts} ${upgradehosts}"

# Anything matching '^____[-a-z]' is considered a command ('_' denotes
# a space).

pass1help()
{
    cat <<EOF
Modifiers:

  Use the test directories that have been modified when running and
  comparing tests:

    modified
	apply operation to modified tests (default is all tests)

  When comparing results, specify the baseline directory to compare
  against (default is Makefile.inc.local:KVM_BASELINE):

    baseline
	compare test results against baseline
    baseline-passed
	compare test results against baseline tests that passed
    baseline-failed
	compare test results against baseline tests that failed
    --baseline <dir>
	specify baseline directory to compare against
    --baseline-passed <dir>
	specify baseline directory to compare against
    --baseline-failed
	specify baseline directory to compare against

EOF
}

pass1=$(pass1help | awk '/^    [-a-z]/ { printf " %s ", $1 }' ; echo ${host})

# Anything matching '^____[-a-z]' is considered a command (_ denotes a
# space).

pass2help()
{
    cat <<EOF
  Set up the test environment, run the testsuite:

    install[-PLATFORM]
	if needed, create the build domains
	install libreswan on the build domains
	clone the build domains to create the test domains
	control with Makefile.inc.local:KVM_{FEDORA,FREEBSD,NETBSD,OPENBSD}=true
    check [ <test-directory> ... ]
	run the testsuite

  Examine the test results:

    diffs [ <test-directory> ... ]
	show differences (for the specified tests)
	exit non-zero when differences are found
    results [ <test-directory> ... ]
	list test results (for the specified tests)
	exit non-zero when failures are found
    failed [ <test-directory> ... ]
	list failed test results (for the specified tests)
	(XX: broken: exit non-zero when failures are found)

  Re-run, or run a-new the testsuite:

    recheck [ <test-directory> ... ]
	re-run the testsuite
    uninstall
	deletes test domains
	deletes build domains
	leaves test results and keys alone
    clean
	deletes test domains
	deletes build domains
	deletes test results
	deletes test keys
    check-clean
	delete the test results (leave the build trees and domains alone)
    keys
	deletes test keys, and then rebuilds them

  Update the expected test results (and GIT repository):

    patch [ <test-directory> ... ]
	apply test differences (to the specified test directories)
    add [ <test-directory> ... ]
	<<git add>> (the specified test directories)

  Manipulate the kvmrunner process:

    status
	report the status of the running test
    kill
	kill the running testsuite
    nohup
	run in the background

  Step wize create/delete the domains (just use ./kvm install):

    base[-PLATFORM]
	create a domain containing the base OS
	creates: ${basehosts}
    upgrade[-PLATFORM]
	create the upgrade domain from the base domain
	installs missing packages
        updates existing packages
	creates: ${upgradehosts}
    transmogrify[-PLATFORM]
	create the build domain from the upgrade domain
	transmogrifies the domain ready for building and testing
	creates: ${kvm_platform}
    build[-PLATFORM]
	build/install libreswan on the build domains
	uses: ${kvm_platform}
    install[-PLATFORM]
	build/install libreswan
	clones build domains to create the test domains

  Stepwize delete the domains (just use ./kvm purge):

    shutdown[-PLATFORM]
	shutdown all domains
    uninstall[-PLATFORM]
	delete test-domains, build domain
	leaves base domains and upgrade domains alone
    downgrade[-PLATFORM]
	also deletes upgrade domains
	also deletes test keys
	also deletes test results
    purge[-PLATFORM]
	also deletes base domains
    demolish[-PLATFORM]
	also deletes shared gateway

  Libvirt breakage:

    restart
	restart libvirt to workaround catatonic daemon performance

  To log into a domain:

    sh <host> [ <command> ]
	start a shell on <host> which can be a:
	  test domain: ${testhosts}
	  build domain: ${kvm_platform}
	  base domain: ${basehosts}
	  upgrade domain: ${upgradehosts}

  Configuration:

    config
	show the configuration

  Namespaces - broken:

    nsinstall
	install namespaces
    nsreinstall
	re-install namespaces
    nsrun [ <test-directory> ... ]
	run testsuite using namespaces on the fedora domain

EOF
}

pass2=$(pass2help | awk -v "plat=${kvm_platform}" -- '
BEGIN {
	split(plat, platforms)
}
/^    [a-z]*.-PLATFORM.$/ {
	t=$1
	sub(/[^a-z]*$/,"",t)
	printf " %s ", t
	for (i in platforms) {
		printf " %s-%s ", t, platforms[i]
	}
	next
}
/^    [-a-z]*/ {
	printf " %s ", $1
}')


# Invoked by completer with:
#
#   $0 <command==$0> <word> <previous>?
#
# ${pass1}, ${pass2} and ${hosts} contain completion values.

if test "$1" = $0 -a $# -eq 3 ; then
    command=$1
    word=$2
    previous=$3
    # hack to detect first vs later argument
    if test "${previous}" = "${command}" ; then
	# first command
	compgen -W "${pass1} ${pass2}" "${word}" | sort
    elif test "${previous}" = sh ; then
	# sh hostname
	compgen -W "${hosts}" "${word}"
    elif [[ " ${pass2}" =~ " ${word}" ]] ; then
	# word looks to be matching a command (or is empty),
	# expand to either <command> or <directory>
	compgen -o plusdirs -W "${pass2}" "${word}"
    else
	# doesn't match a command, so throw in the testing directory
	# as a quick expansion
	compgen -o plusdirs -W "${pass2}" -G "$(realpath --relative-base $PWD ${kvm_testingdir}/pluto)/${word}*" "${word}"
    fi
    exit 0
fi

# Translate ../../../kvm [...] into ./kvm [...]
#
# No arguments implies "check $PWD", and operations but no directory
# implies $PWD as the test.  Do this before checking $#=0 as this will
# add the missing parameter.

if test $(realpath $(dirname $0)) != $(realpath ${PWD}) ; then
    if test $# -eq 0 ; then
	set -- check $PWD
    else
	# convert any paths to absolute
	declare -a args
	i=0
	found_dir=false
	need_dir=false
	for arg in "$@" ; do
	    case "${arg}" in
		check | recheck | diff | diffs | patch | add )
		    args[$i]="${arg}"
		    need_dir=true
		    ;;
		* )
		    if test -d "${arg}" ; then
			found_dir=true
			args[$i]=$(realpath ${arg})
		    else
			args[$i]="${arg}"
		    fi
		    ;;
	    esac
	    i=$((i + 1))
	done
	if ! $found_dir && $need_dir ; then
	    set -- "${args[@]}" $PWD
	else
	    set -- "${args[@]}"
	fi
    fi
    cd $(dirname $0)
fi

# Finally is there at least one parameter?

if test $# -eq 0; then
    cat <<EOF
Usage:

   <modifier> ... <operation> ... <test-directory> <test-directory> ...

EOF
    pass1help
    echo
    pass2help
cat <<EOF

To enable completion, add these lines to .bashrc:

  complete -o filenames -C        ./kvm        ./kvm
  complete -o filenames -C ../../../kvm ../../../kvm

(the first is for top-level, the second for within a directory)
EOF
    exit 1
fi

# Accumulate pass 2 commands; execute pass 1 commands immediately.
#
# XXX: should "sh" be delayed so that "downgrade install sh netbsd"
# DTRT?

declare -a ops

modified=
baseline=

i=0
while test $# -gt 0 ; do

    # pass 1; look for modifiers  commands

    case "$1" in

	nohup )
	    if test "$i" -ne 0 ; then
		echo "nohup must be first command" 1>&2
		exit 1
	    fi
	    if test "$#" -eq 1 ; then
		echo "expecting a command after nohup" 1>&2
		exit 1
	    fi
	    # avoid race tail can try to open nohup.out before nohup
	    # creates it.
	    touch nohup.out
	    shift
	    nohup ./kvm "$@" &
	    exec tail -f nohup.out
	    ;;

	--baseline* )
	    baseline=$(expr $1 : '--\(.*\)')
	    shift
	    __baseline="--baseline $1"
	    ;;

	# aliases for pass2 commands
	diff )   ops[$i]=diffs ;   i=$((i + 1)) ;;
	result ) ops[$i]=results ; i=$((i + 1)) ;;
	failed ) ops[$i]=failed ;  i=$((i + 1)) ;;
	test   ) ops[$i]=check ;   i=$((i + 1)) ;;
	retest ) ops[$i]=recheck ; i=$((i + 1)) ;;
	test-clean ) ops[$i]=check-clean ; i=$((i + 1)) ;;

	sh )
	    # remainder of line is host + optional commands
	    ops[$i]=sh ; i=$((i + 1)) ; shift
	    if test $# -lt 1 ; then
		echo "missing hostname" 1>&2
		exit 1
	    fi
	    if ! [[ " ${hosts} " =~ " $1 " ]] ; then
		echo "unrecognized hostname" 1>&2
		exit 1
	    fi
	    break
	    ;;

	# wrappers
	modified )
	    modified=$(git status testing/pluto/ \
			   | awk '/(modified|deleted|renamed|new file):/ { print $NF }' \
			   | grep '/.*/.*/' \
			   | cut -d/ -f1-3 \
			   | sort -u)
	    if test -z "${modified}" ; then
		echo "no modified tests" 1>&2
		exit 1
	    fi
	    ;;

	baseline | baseline-passed | baseline-failed )
	    if test -z "${__baseline}" ; then
		echo "no KVM_BASELINE" 1&2
		exit 1
	    fi
	    baseline=$1
	    ;;

	* ) # either: a directory; a hostname; or pass2 command

	    if [[ " ${pass2} " =~ " $1 " ]] ; then

	       # a pass2 command, accumulate
	       ops[$i]="$1" ; i=$((i + 1))

	    elif [[ " ${hosts} " =~ " $1 " ]] ; then

		# a hostname ..., run: sh hostname (leave it in input)
		ops[$i]="sh" ; i=$((i + 1))
		break

	    else

		# If the argument is a directory then bail; it's
		# assumed that "$@" is a list of test directories.
		if test ! -d "$1" -a ! -d "${rutdir}/$1" ; then
		    if test -z "${ops[*]}" ; then
			echo "unknown command: $1" 1>&2
		    else
			echo "not a directory: $1" 1>&2
		    fi
		    exit 1
		fi
		break

	    fi

	    ;;
    esac
    shift
done

if test -n "${modified}" ; then
    if test -z "${ops[*]}" ; then
	echo "${modified}"
	exit 0
    elif test $# -ne 0 ; then
	echo "both modified and tests specified" 1>&2
	exit 1
    fi
    set -- ${modified}
elif test $# -eq 0 ; then
    set -- ${kvm_testingdir}
fi

if test -z "${ops[*]}" ; then
    echo "nothing to do!" 1>&2
    exit 1
fi

kvmresults()
{
    ./testing/utils/kvmresults.py \
	${kvm_testingdir:+--testing-directory ${kvm_testingdir}} \
	${kvm_test_status:+--test-status "${kvm_test_status}"} \
	${kvm_test_name:+--test-name "${kvm_test_name}"} \
	${kvm_test_flags} \
	"$@"
}

results_command()
{
    if test -n "${baseline}" ; then
	kvmresults ${__baseline} "$@" \
	    | grep --line-buffered -v -e baseline:untested \
	    | grep --line-buffered -e ${baseline}
    else
	kvmresults "$@"
	status=$?
    fi
}

diffs_command()
{
    if test -n "${baseline}" ; then
	kvmresults ${__baseline} --stats none "$@" \
	    | grep --line-buffered -v -e baseline:untested \
	    | grep --line-buffered -e "${baseline}" \
	    | while read test eol ; do
	    kvmresults --stats none --print diffs ${test}
	done
    else
	kvmresults --stats none --print diffs "$@"
	status=$?
    fi
}

kill_command()
{
    if test ! -r ${kvm_pidfile} ; then
	echo "no ${kvm_pidfile} file" 1>&2
	exit 1
    fi
    local pid=$(cat ${kvm_pidfile})
    echo "killing ${pid}" 1>&2
    kill ${pid}
}

status_command()
{
    if test ! -r ${kvm_pidfile} ; then
	echo "no ${kvm_pidfile} file" 1>&2
	exit 1
    fi
    local pid=$(cat ${kvm_pidfile})
    ps="ps www --no-headers ${pid}"
    echo "${ps}"
    ${ps}
}

# second pass
status=0
for op in "${ops[@]}" ; do
    case ${op} in

	add ) git add "$@" ;;

	kill ) kill_command ;;
	status ) status_command ;;

	config | base* | upgrade* | transmogrify* | build* | install* | \
        shutdown* | uninstall* | downgrade* | demolish* | \
        check-clean | clean | purge* )
	    echo ${gnumake} kvm-${op}
	    ${gnumake} kvm-${op} || exit $?
	    ;;
	check | recheck )
	    echo ${op}: "$@"
	    ${gnumake} kvm-${op} KVM_TESTS="$*" || exit $?
	    ;;
	diffs )
	    diffs_command "$@"
	    ;;
	results )
	    results_command "$@"
	    ;;
	failed )
	    results_command "$@" | if grep -e ' failed' -e ' unresolved' ; then false ; else true ; fi
	    ;;
	patch )
	    diffs_command "$@" | patch -p1
	    ;;
	keys )
	    ${gnumake} kvm-keys-clean kvm-keys
	    ;;
	restart )
	    sudo systemctl restart libvirtd
	    ;;
	sh )
	    host=${kvm_prefix}$1 ; shift
	    echo "Connecting to ${host}" 1>&2
	    exec testing/utils/kvmsh.py ${host} "$@"
	    ;;
	nsinstall )
	    testing/utils/kvmsh.py --chdir /source ${prefix}fedora -- gmake nsinstall
	    ;;
	nsreinstall )
	    testing/utils/kvmsh.py --chdir /source ${prefix}fedora -- gmake nsreinstall
	    ;;
	nsrun )
	    testing/utils/kvmsh.py --chdir /source ${prefix}fedora -- gmake nsrun
	    ;;
    esac
done

exit ${status}
