#!/bin/sh

set -e

ME=$(basename $0)

usage () {
	echo "usage: ${ME} [--serial] [--concurrency N] [tests dir]"
	exit 1
}

# Modeled after iterate_on_hosts() of OCI's oci-cluster-upgrade-funcs:
# tests are grouped per folder, the folders are started in the
# background, and we wait for a bounded number of them at a time (one
# per available CPU by default, as reported by nproc(1), overridable
# through -c/--concurrency or the RUN_TESTS_MAX_PARALLEL variable).
# -s/--serial restores the fully sequential behavior. Tests which are
# in the same folder share the same working directory, so they run one
# after the other within their folder, while distinct folders can
# safely run in parallel, as each has its own working directory.
#
# As the test folders are started as background jobs, the exit code of
# a test cannot be waited upon: instead, each test records it in a
# result file, and its output in an output file, so that failures can
# be reported correctly, with their full output, once the batch of
# folders they belong to is finished.
if [ "${1}" = "-s" ] || [ "${1}" = "--serial" ] ; then
	USE_SERIAL=yes
	shift
fi
if [ "${1}" = "-c" ] || [ "${1}" = "--concurrency" ] ; then
	RUN_TESTS_CONCURRENCY=${2}
	shift
	shift
fi
if [ "${1}" = "-h" ] || [ "${1}" = "--help" ] ; then
	usage
fi
if [ -n "${1}" ] ; then
	TESTS_DIR=${1}
fi
RUN_TESTS_CONCURRENCY=${RUN_TESTS_CONCURRENCY:-${RUN_TESTS_MAX_PARALLEL:-$(nproc 2>/dev/null || echo 8)}}
if [ -z "${TESTS_DIR}" ] ; then
	TESTS_DIR=tests
fi
if ! [ -d "${TESTS_DIR}" ] ; then
	echo "${ME}: no ${TESTS_DIR} folder holding test_* scripts: nothing to do."
	exit 1
fi

CURDIR=$(pwd)
WORKDIR=$(mktemp -d /tmp/pkgos-run-tests.XXXXXX)
trap 'rm -rf ${WORKDIR}' EXIT

# Records the result of one test: $1 = test path, $2 = result file,
# $3 = output file. A test which failed gets its full output printed,
# so that failures are easy to debug.
record_result () {
	echo -n "${1} ... "
	if [ "$(cat ${2})" = "0" ] ; then
		echo "OK"
	else
		echo "FAILED"
		SUCCESS=no
		echo "--- Output of ${1}: ---"
		cat "${3}"
		echo "--- End of the output of ${1}. ---"
	fi
}

# Reports the results of the folders of the last batch: each running
# folder appends one line per test to its own manifest file.
flush_results () {
	while read DIRBATCH ; do
		while read TEST RCFILE OUTFILE ; do
			record_result "${TEST}" "${RCFILE}" "${OUTFILE}"
		done <"${DIRBATCH}"
	done <"${BATCH_LIST}"
	: >"${BATCH_LIST}"
}

N_TESTS=$(find ${TESTS_DIR} -type f -iname 'test_*' | wc -l)
N_FOLDERS=$(find ${TESTS_DIR} -type f -iname 'test_*' | xargs -n1 dirname | uniq | wc -l)
if [ "${USE_SERIAL}" = "yes" ] ; then
	echo "Running ${N_TESTS} shell script unit tests from ${N_FOLDERS} folders (one after the other)..."
else
	echo "Running ${N_TESTS} shell script unit tests from ${N_FOLDERS} folders (up to ${RUN_TESTS_CONCURRENCY} in parallel)..."
fi

SUCCESS=yes
CNT=0
JOB_CNT=1
BATCH_N=0
BATCH_LIST=${WORKDIR}/batch-list
: >${BATCH_LIST}

if [ "${USE_SERIAL}" = "yes" ] ; then
	for TEST in $(find ${TESTS_DIR} -type f -iname 'test_*' | sort) ; do
		CNT=$(( ${CNT} + 1 ))
		DIRNAME=$(dirname ${TEST})
		BASENAME=$(basename ${TEST})
		echo "=== Running ${TEST} ==="
		cd ${DIRNAME}
		if ./${BASENAME} ; then
			echo "=== ${TEST}: OK ==="
		else
			SUCCESS=no
			echo "=== ${TEST}: FAILED ==="
		fi
		cd ${CURDIR}
	done
else
	for DIR in $(find ${TESTS_DIR} -type f -iname 'test_*' | sort | xargs -n1 dirname | uniq) ; do
		BATCH_N=$(( ${BATCH_N} + 1 ))
		DIRBATCH=${WORKDIR}/batch.${BATCH_N}
		: >"${DIRBATCH}"
		for TEST in $(find ${DIR} -maxdepth 1 -type f -iname 'test_*' | sort) ; do
			CNT=$(( ${CNT} + 1 ))
			echo "${TEST} ${WORKDIR}/rc.$CNT ${WORKDIR}/out.$CNT" >>"${DIRBATCH}"
		done
		echo "${DIRBATCH}" >>"${BATCH_LIST}"
		(
			cd ${CURDIR}
			while read TEST RCFILE OUTFILE ; do
				echo "=== Running ${TEST} ==="
				cd $(dirname ${TEST})
				BASENAME=$(basename ${TEST})
				if ./${BASENAME} >"${OUTFILE}" 2>&1 ; then
					echo 0 >"${RCFILE}"
				else
					echo $? >"${RCFILE}"
				fi
				cd ${CURDIR}
			done <"${DIRBATCH}"
		) &
		JOB_CNT=$(( ${JOB_CNT} + 1 ))
		if [ "${JOB_CNT}" -gt "${RUN_TESTS_CONCURRENCY}" ] ; then
			wait
			flush_results
			JOB_CNT=1
		fi
	done
	wait
	flush_results
fi

if [ "${SUCCESS}" = "yes" ] ; then
	echo "Successfully ran ${CNT} tests :)"
	exit 0
else
	echo "Failure while running unit tests :("
	exit 1
fi
