#!/usr/bin/env bash

# arg variable init
output_dir=
run_stress=

# Avoids a race condition in test_object_interface
# since a PSO might be held alive a little too long in the disk thread.
# It's very flaky and best to just avoid it.
export VKD3D_SHADER_CACHE_PATH=0

args=()
# by default run one job per cpu thread
nr_cpus=$(grep -w processor /proc/cpuinfo|wc -l)
while [[ $# -gt 0 ]]; do
	case $1 in
	-o|--output-dir)
		# this outputs the logs for each test to a separate file
		output_dir="$2"
		shift
		shift
		;;
	-s|--run-stress-tests)
		# stress tests take a long time to run and are disabled by default
		run_stress=1
		shift
		;;
	-j|--jobs)
		# override the default job count
		nr_cpus=$2
		shift
		shift
		;;
	-h|--help)
		echo "./test-runner.sh [-o|--output-dir logfile_dir] [-s|--run-stress-tests] [-j|--jobs N] path/to/tests/d3d12"
		exit 1
		;;
	-*|--*)
		echo "unknown arg $1"
		;;
	*)
		args+=("$1")
		shift
		;;
	esac
done

set -- "${args[@]}"
d3d12_bin="$1"

if [[ -z $d3d12_bin || ! -f "$d3d12_bin" ]] ; then
	echo "Must specify path to valid d3d12 test exe!"
	exit 1
fi

mapfile -t tests < <("$d3d12_bin" --list-tests)

if [[ -z $run_stress ]] ; then
	compacted=()
	for t in "${tests[@]}" ; do
		if [[ "$t" != *stress* ]] ; then
			compacted+=($t)
		fi
	done
	tests=(${compacted[@]})
fi

# runtime variable init
nr_tests=${#tests[@]}
if [ $nr_tests -lt 1 ] ; then
	echo "No tests detected! Is $d3d12_bin a valid test exe?"
	exit 1
fi

pids=()
# the counter for the number of tests running
counter=0
# the index for the next test to run
test_idx=0

if [[ -n $output_dir ]] ; then
	mkdir -p "$output_dir"
fi

# start test processes until $nr_cpus test processes are running
run_tests() {
	while (($counter < $nr_cpus)) ; do
		# output to /dev/null by default
		if [[ -z "$output_dir" ]] ; then
			VKD3D_TEST_MATCH=${tests[$test_idx]} "$d3d12_bin" &>/dev/null &
		else
			VKD3D_TEST_MATCH=${tests[$test_idx]} "$d3d12_bin" &> "$output_dir/${tests[$test_idx]}.log" &
		fi
		# capture pid of subprocess
		pids[$test_idx]=${!}
		#increment running test counter and test index
		counter=$((counter+1))
		test_idx=$((test_idx+1))
	done
}


echo "Running $nr_tests vkd3d-proton D3D12 tests..."
r=0
fails=()
last_notify=$(date +%s)
while (($r<$nr_tests)) ; do
	# run tests if there are more tests to run
	if [ $test_idx -lt $nr_tests ] ; then
		run_tests
	fi
	# tests are active: wait for one to finish, store pid to $finished
	wait -n -p finished &>/dev/null
	retval=$?
	pid_count=${#pids[@]}
	# iterate $pids array to find the index matching $finished
	for ((c=0; c < $pid_count; c++)) ; do
		if [[ ${pids[$c]} == $finished ]] ; then
			# failed/crashed tests have $retval!=0
			if [[ $retval != 0 ]] ; then
				# print failure immediately and store for summary
				fails+=(${tests[$c]})
				echo "FAILED ${tests[$c]}"
			fi
			# increment the "done" counter
			r=$((r+1))
			# decrement the "running" counter
			counter=$((counter-1))
			break
		fi
	done
	cur_time=$(date +%s)
	# only notify every 5s
	if [ $((last_notify+5)) -lt $cur_time ] ; then
		echo "$r / $nr_tests complete (${#fails[@]} failures)..."
		last_notify=$cur_time
	fi
done


# nice summary output at the end
echo "***********************"
echo "Finished in ${SECONDS}s!"

if [[ "${#fails[@]}" != 0 ]] ; then
	echo "${#fails[@]} FAILURES: (run with 'VKD3D_TEST_MATCH=<name> $d3d12_bin')"
	for fail in "${fails[@]}" ; do
		echo "$fail"
	done
else
	echo "ALL PASSED!"
fi
