Commit be66a27b authored by Benjamin Tissoires's avatar Benjamin Tissoires Committed by Jiri Kosina
Browse files

selftests/hid: update vmtest.sh for virtme-ng



This commit is a rewrite almost from scratch of vmtest.sh.

By relying on virtme-ng, we get rid of boot2container, reducing the
total bootup time (and network requirements). That means that we are
relying on the programs being installed on the host, but that shouldn't
be an issue. The generation of the kconfig is also now handled by
virtme-ng, so that's one less thing to worry.

I used tools/testing/selftests/vsock/vmtest.sh as a base and modified it
to look mostly like my previous script:
- removed the custom ssh handling
- make use of vng for compiling, which allows to bring remote
  compilation (and potentially remote compilation on a remote container)
- change the verbosity logic by having 2 levels:
  - first one shows the tests outputs
  - second level also shows the VM logs
- instead of only running the compiled kernel when it is built, if we
  are in the kernel tree, use the kernel artifacts there (and complain
  if they are not built)
- adapted the tests list to match the HID subsystem tests

Signed-off-by: default avatarBenjamin Tissoires <bentiss@kernel.org>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.com>
parent 02d6eeed
Loading
Loading
Loading
Loading
+423 −245
Original line number Diff line number Diff line
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2025 Red Hat
# Copyright (c) 2025 Meta Platforms, Inc. and affiliates
#
# Dependencies:
#		* virtme-ng
#		* busybox-static (used by virtme-ng)
#		* qemu	(used by virtme-ng)

readonly SCRIPT_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
readonly KERNEL_CHECKOUT=$(realpath "${SCRIPT_DIR}"/../../../../)

source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh

readonly HID_BPF_TEST="${SCRIPT_DIR}"/hid_bpf
readonly HIDRAW_TEST="${SCRIPT_DIR}"/hidraw
readonly HID_BPF_PROGS="${KERNEL_CHECKOUT}/drivers/hid/bpf/progs"
readonly SSH_GUEST_PORT=22
readonly WAIT_PERIOD=3
readonly WAIT_PERIOD_MAX=60
readonly WAIT_TOTAL=$(( WAIT_PERIOD * WAIT_PERIOD_MAX ))
readonly QEMU_PIDFILE=$(mktemp /tmp/qemu_hid_vmtest_XXXX.pid)

readonly QEMU_OPTS="\
	 --pidfile ${QEMU_PIDFILE} \
"
readonly KERNEL_CMDLINE=""
readonly LOG=$(mktemp /tmp/hid_vmtest_XXXX.log)
readonly TEST_NAMES=(vm_hid_bpf vm_hidraw vm_pytest)
readonly TEST_DESCS=(
	"Run hid_bpf tests in the VM."
	"Run hidraw tests in the VM."
	"Run the hid-tools test-suite in the VM."
)

VERBOSE=0
SHELL_MODE=0
BUILD_HOST=""
BUILD_HOST_PODMAN_CONTAINER_NAME=""

usage() {
	local name
	local desc
	local i

	echo
	echo "$0 [OPTIONS] [TEST]... [-- tests-args]"
	echo "If no TEST argument is given, all tests will be run."
	echo
	echo "Options"
	echo "  -b: build the kernel from the current source tree and use it for guest VMs"
	echo "  -H: hostname for remote build host (used with -b)"
	echo "  -p: podman container name for remote build host (used with -b)"
	echo "      Example: -H beefyserver -p vng"
	echo "  -q: set the path to or name of qemu binary"
	echo "  -s: start a shell in the VM instead of running tests"
	echo "  -v: more verbose output (can be repeated multiple times)"
	echo
	echo "Available tests"

	for ((i = 0; i < ${#TEST_NAMES[@]}; i++)); do
		name=${TEST_NAMES[${i}]}
		desc=${TEST_DESCS[${i}]}
		printf "\t%-35s%-35s\n" "${name}" "${desc}"
	done
	echo

set -u
set -e

# This script currently only works for x86_64
ARCH="$(uname -m)"
case "${ARCH}" in
x86_64)
	QEMU_BINARY=qemu-system-x86_64
	BZIMAGE="arch/x86/boot/bzImage"
	;;
*)
	echo "Unsupported architecture"
	exit 1
	;;
esac
SCRIPT_DIR="$(dirname $(realpath $0))"
OUTPUT_DIR="$SCRIPT_DIR/results"
KCONFIG_REL_PATHS=("${SCRIPT_DIR}/config" "${SCRIPT_DIR}/config.common" "${SCRIPT_DIR}/config.${ARCH}")
B2C_URL="https://gitlab.freedesktop.org/gfx-ci/boot2container/-/raw/main/vm2c.py"
NUM_COMPILE_JOBS="$(nproc)"
LOG_FILE_BASE="$(date +"hid_selftests.%Y-%m-%d_%H-%M-%S")"
LOG_FILE="${LOG_FILE_BASE}.log"
EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status"
CONTAINER_IMAGE="registry.freedesktop.org/bentiss/hid/fedora/39:2023-11-22.1"
}

TARGETS="${TARGETS:=$(basename ${SCRIPT_DIR})}"
DEFAULT_COMMAND="pip3 install hid-tools; make -C tools/testing/selftests TARGETS=${TARGETS} run_tests"
die() {
	echo "$*" >&2
	exit "${KSFT_FAIL}"
}

usage()
{
	cat <<EOF
Usage: $0 [-j N] [-s] [-b] [-d <output_dir>] -- [<command>]
vm_ssh() {
	# vng --ssh-client keeps shouting "Warning: Permanently added 'virtme-ng%22'
	# (ED25519) to the list of known hosts.",
	# So replace the command with what's actually called and add the "-q" option
	stdbuf -oL ssh -q \
		       -F ${HOME}/.cache/virtme-ng/.ssh/virtme-ng-ssh.conf \
		       -l root virtme-ng%${SSH_GUEST_PORT} \
		       "$@"
	return $?
}

<command> is the command you would normally run when you are in
the source kernel direcory. e.g:
cleanup() {
	if [[ -s "${QEMU_PIDFILE}" ]]; then
		pkill -SIGTERM -F "${QEMU_PIDFILE}" > /dev/null 2>&1
	fi

	$0 -- ./tools/testing/selftests/hid/hid_bpf
	# If failure occurred during or before qemu start up, then we need
	# to clean this up ourselves.
	if [[ -e "${QEMU_PIDFILE}" ]]; then
		rm "${QEMU_PIDFILE}"
	fi
}

If no command is specified and a debug shell (-s) is not requested,
"${DEFAULT_COMMAND}" will be run by default.
check_args() {
	local found

If you build your kernel using KBUILD_OUTPUT= or O= options, these
can be passed as environment variables to the script:
	for arg in "$@"; do
		found=0
		for name in "${TEST_NAMES[@]}"; do
			if [[ "${name}" = "${arg}" ]]; then
				found=1
				break
			fi
		done

  O=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf
		if [[ "${found}" -eq 0 ]]; then
			echo "${arg} is not an available test" >&2
			usage
		fi
	done

or
	for arg in "$@"; do
		if ! command -v > /dev/null "test_${arg}"; then
			echo "Test ${arg} not found" >&2
			usage
		fi
	done
}

  KBUILD_OUTPUT=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf
check_deps() {
	for dep in vng ${QEMU} busybox pkill ssh pytest; do
		if [[ ! -x $(command -v "${dep}") ]]; then
			echo -e "skip:    dependency ${dep} not found!\n"
			exit "${KSFT_SKIP}"
		fi
	done

Options:
	if [[ ! -x $(command -v "${HID_BPF_TEST}") ]]; then
		printf "skip:    %s not found!" "${HID_BPF_TEST}"
		printf " Please build the kselftest hid_bpf target.\n"
		exit "${KSFT_SKIP}"
	fi

	-u)		Update the boot2container script to a newer version.
	-d)		Update the output directory (default: ${OUTPUT_DIR})
	-b)		Run only the build steps for the kernel and the selftests
	-j)		Number of jobs for compilation, similar to -j in make
			(default: ${NUM_COMPILE_JOBS})
	-s)		Instead of powering off the VM, start an interactive
			shell. If <command> is specified, the shell runs after
			the command finishes executing
EOF
	if [[ ! -x $(command -v "${HIDRAW_TEST}") ]]; then
		printf "skip:    %s not found!" "${HIDRAW_TEST}"
		printf " Please build the kselftest hidraw target.\n"
		exit "${KSFT_SKIP}"
	fi
}

download()
{
	local file="$1"

	echo "Downloading $file..." >&2
	curl -Lsf "$file" -o "${@:2}"
}
check_vng() {
	local tested_versions
	local version
	local ok

recompile_kernel()
{
	local kernel_checkout="$1"
	local make_command="$2"
	tested_versions=("1.36" "1.37")
	version="$(vng --version)"

	cd "${kernel_checkout}"
	ok=0
	for tv in "${tested_versions[@]}"; do
		if [[ "${version}" == *"${tv}"* ]]; then
			ok=1
			break
		fi
	done

	${make_command} olddefconfig
	${make_command} headers
	${make_command}
	if [[ ! "${ok}" -eq 1 ]]; then
		printf "warning: vng version '%s' has not been tested and may " "${version}" >&2
		printf "not function properly.\n\tThe following versions have been tested: " >&2
		echo "${tested_versions[@]}" >&2
	fi
}

update_selftests()
{
	local kernel_checkout="$1"
	local selftests_dir="${kernel_checkout}/tools/testing/selftests/hid"

	cd "${selftests_dir}"
	${make_command}
}
handle_build() {
	if [[ ! "${BUILD}" -eq 1 ]]; then
		return
	fi

run_vm()
{
	local run_dir="$1"
	local b2c="$2"
	local kernel_bzimage="$3"
	local command="$4"
	local post_command=""

	cd "${run_dir}"

	if ! which "${QEMU_BINARY}" &> /dev/null; then
		cat <<EOF
Could not find ${QEMU_BINARY}
Please install qemu or set the QEMU_BINARY environment variable.
EOF
	if [[ ! -d "${KERNEL_CHECKOUT}" ]]; then
		echo "-b requires vmtest.sh called from the kernel source tree" >&2
		exit 1
	fi

	# alpine (used in post-container requires the PATH to have /bin
	export PATH=$PATH:/bin
	pushd "${KERNEL_CHECKOUT}" &>/dev/null

	if [[ "${debug_shell}" != "yes" ]]
	then
		touch ${OUTPUT_DIR}/${LOG_FILE}
		command="mount bpffs -t bpf /sys/fs/bpf/; set -o pipefail ; ${command} 2>&1 | tee ${OUTPUT_DIR}/${LOG_FILE}"
		post_command="cat ${OUTPUT_DIR}/${LOG_FILE}"
	else
		command="mount bpffs -t bpf /sys/fs/bpf/; ${command}"
	if ! vng --kconfig --config "${SCRIPT_DIR}"/config; then
		die "failed to generate .config for kernel source tree (${KERNEL_CHECKOUT})"
	fi

	set +e
	$b2c --command "${command}" \
	     --kernel ${kernel_bzimage} \
	     --workdir ${OUTPUT_DIR} \
	     --image ${CONTAINER_IMAGE}
	local vng_args=("-v" "--config" "${SCRIPT_DIR}/config" "--build")

	if [[ -n "${BUILD_HOST}" ]]; then
		vng_args+=("--build-host" "${BUILD_HOST}")
	fi

	echo $? > ${OUTPUT_DIR}/${EXIT_STATUS_FILE}
	if [[ -n "${BUILD_HOST_PODMAN_CONTAINER_NAME}" ]]; then
		vng_args+=("--build-host-exec-prefix" \
			   "podman exec -ti ${BUILD_HOST_PODMAN_CONTAINER_NAME}")
	fi

	set -e
	if ! vng "${vng_args[@]}"; then
		die "failed to build kernel from source tree (${KERNEL_CHECKOUT})"
	fi

	${post_command}
}
	if ! make -j$(nproc) -C "${HID_BPF_PROGS}"; then
		die "failed to build HID bpf objects from source tree (${HID_BPF_PROGS})"
	fi

is_rel_path()
{
	local path="$1"
	if ! make -j$(nproc) -C "${SCRIPT_DIR}"; then
		die "failed to build HID selftests from source tree (${SCRIPT_DIR})"
	fi

	[[ ${path:0:1} != "/" ]]
	popd &>/dev/null
}

do_update_kconfig()
{
	local kernel_checkout="$1"
	local kconfig_file="$2"
vm_start() {
	local logfile=/dev/null
	local verbose_opt=""
	local kernel_opt=""
	local qemu

	qemu=$(command -v "${QEMU}")

	rm -f "$kconfig_file" 2> /dev/null
	if [[ "${VERBOSE}" -eq 2 ]]; then
		verbose_opt="--verbose"
		logfile=/dev/stdout
	fi

	for config in "${KCONFIG_REL_PATHS[@]}"; do
		local kconfig_src="${config}"
		cat "$kconfig_src" >> "$kconfig_file"
	# If we are running from within the kernel source tree, use the kernel source tree
	# as the kernel to boot, otherwise use the currently running kernel.
	if [[ "$(realpath "$(pwd)")" == "${KERNEL_CHECKOUT}"* ]]; then
		kernel_opt="${KERNEL_CHECKOUT}"
	fi

	vng \
		--run \
		${kernel_opt} \
		${verbose_opt} \
		--qemu-opts="${QEMU_OPTS}" \
		--qemu="${qemu}" \
		--user root \
		--append "${KERNEL_CMDLINE}" \
		--ssh "${SSH_GUEST_PORT}" \
		--rw  &> ${logfile} &

	local vng_pid=$!
	local elapsed=0

	while [[ ! -s "${QEMU_PIDFILE}" ]]; do
		if ! kill -0 "${vng_pid}" 2>/dev/null; then
			echo "vng process (PID ${vng_pid}) exited early, check logs for details" >&2
			die "failed to boot VM"
		fi

		if [[ ${elapsed} -ge ${WAIT_TOTAL} ]]; then
			echo "Timed out after ${WAIT_TOTAL} seconds waiting for VM to boot" >&2
			die "failed to boot VM"
		fi

		sleep 1
		elapsed=$((elapsed + 1))
	done
}

update_kconfig()
{
	local kernel_checkout="$1"
	local kconfig_file="$2"

	if [[ -f "${kconfig_file}" ]]; then
		local local_modified="$(stat -c %Y "${kconfig_file}")"

		for config in "${KCONFIG_REL_PATHS[@]}"; do
			local kconfig_src="${config}"
			local src_modified="$(stat -c %Y "${kconfig_src}")"
			# Only update the config if it has been updated after the
			# previously cached config was created. This avoids
			# unnecessarily compiling the kernel and selftests.
			if [[ "${src_modified}" -gt "${local_modified}" ]]; then
				do_update_kconfig "$kernel_checkout" "$kconfig_file"
				# Once we have found one outdated configuration
				# there is no need to check other ones.
vm_wait_for_ssh() {
	local i

	i=0
	while true; do
		if [[ ${i} -gt ${WAIT_PERIOD_MAX} ]]; then
			die "Timed out waiting for guest ssh"
		fi
		if vm_ssh -- true; then
			break
		fi
		i=$(( i + 1 ))
		sleep ${WAIT_PERIOD}
	done
	else
		do_update_kconfig "$kernel_checkout" "$kconfig_file"
	fi
}

main()
{
	local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
	local kernel_checkout=$(realpath "${script_dir}"/../../../../)
	# By default the script searches for the kernel in the checkout directory but
	# it also obeys environment variables O= and KBUILD_OUTPUT=
	local kernel_bzimage="${kernel_checkout}/${BZIMAGE}"
	local command="${DEFAULT_COMMAND}"
	local update_b2c="no"
	local debug_shell="no"
	local build_only="no"

	while getopts ':hsud:j:b' opt; do
		case ${opt} in
		u)
			update_b2c="yes"
			;;
		d)
			OUTPUT_DIR="$OPTARG"
			;;
		j)
			NUM_COMPILE_JOBS="$OPTARG"
			;;
		s)
			command="/bin/sh"
			debug_shell="yes"
			;;
		b)
			build_only="yes"
			;;
		h)
			usage
			exit 0
			;;
		\? )
			echo "Invalid Option: -$OPTARG"
			usage
			exit 1
			;;
		: )
			echo "Invalid Option: -$OPTARG requires an argument"
			usage
			exit 1
			;;
		esac
	done
	shift $((OPTIND -1))
vm_mount_bpffs() {
	vm_ssh -- mount bpffs -t bpf /sys/fs/bpf
}

	# trap 'catch "$?"' EXIT
	if [[ "${build_only}" == "no" && "${debug_shell}" == "no" ]]; then
		if [[ $# -eq 0 ]]; then
			echo "No command specified, will run ${DEFAULT_COMMAND} in the vm"
		else
			command="$@"
__log_stdin() {
	stdbuf -oL awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0; fflush() }'
}

			if [[ "${command}" == "/bin/bash" || "${command}" == "bash" ]]
			then
				debug_shell="yes"
			fi
__log_args() {
	echo "$*" | awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0 }'
}

log() {
	local verbose="$1"
	shift

	local prefix="$1"

	shift
	local redirect=
	if [[ ${verbose} -le 0 ]]; then
		redirect=/dev/null
	else
		redirect=/dev/stdout
	fi

	if [[ "$#" -eq 0 ]]; then
		__log_stdin | tee -a "${LOG}" > ${redirect}
	else
		__log_args "$@" | tee -a "${LOG}" > ${redirect}
	fi
}

	local kconfig_file="${OUTPUT_DIR}/latest.config"
	local make_command="make -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}"
log_setup() {
	log $((VERBOSE-1)) "setup" "$@"
}

	# Figure out where the kernel is being built.
	# O takes precedence over KBUILD_OUTPUT.
	if [[ "${O:=""}" != "" ]]; then
		if is_rel_path "${O}"; then
			O="$(realpath "${PWD}/${O}")"
		fi
		kernel_bzimage="${O}/${BZIMAGE}"
		make_command="${make_command} O=${O}"
	elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then
		if is_rel_path "${KBUILD_OUTPUT}"; then
			KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")"
log_host() {
	local testname=$1

	shift
	log $((VERBOSE-1)) "test:${testname}:host" "$@"
}

log_guest() {
	local testname=$1

	shift
	log ${VERBOSE} "# test:${testname}" "$@"
}

test_vm_hid_bpf() {
	local testname="${FUNCNAME[0]#test_}"

	vm_ssh -- "${HID_BPF_TEST}" \
		2>&1 | log_guest "${testname}"

	return ${PIPESTATUS[0]}
}

test_vm_hidraw() {
	local testname="${FUNCNAME[0]#test_}"

	vm_ssh -- "${HIDRAW_TEST}" \
		2>&1 | log_guest "${testname}"

	return ${PIPESTATUS[0]}
}

test_vm_pytest() {
	local testname="${FUNCNAME[0]#test_}"

	shift

	vm_ssh -- pytest ${SCRIPT_DIR}/tests --color=yes "$@" \
		2>&1 | log_guest "${testname}"

	return ${PIPESTATUS[0]}
}

run_test() {
	local vm_oops_cnt_before
	local vm_warn_cnt_before
	local vm_oops_cnt_after
	local vm_warn_cnt_after
	local name
	local rc

	vm_oops_cnt_before=$(vm_ssh -- dmesg | grep -c -i 'Oops')
	vm_error_cnt_before=$(vm_ssh -- dmesg --level=err | wc -l)

	name=$(echo "${1}" | awk '{ print $1 }')
	eval test_"${name}" "$@"
	rc=$?

	vm_oops_cnt_after=$(vm_ssh -- dmesg | grep -i 'Oops' | wc -l)
	if [[ ${vm_oops_cnt_after} -gt ${vm_oops_cnt_before} ]]; then
		echo "FAIL: kernel oops detected on vm" | log_host "${name}"
		rc=$KSFT_FAIL
	fi
		kernel_bzimage="${KBUILD_OUTPUT}/${BZIMAGE}"
		make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}"

	vm_error_cnt_after=$(vm_ssh -- dmesg --level=err | wc -l)
	if [[ ${vm_error_cnt_after} -gt ${vm_error_cnt_before} ]]; then
		echo "FAIL: kernel error detected on vm" | log_host "${name}"
		vm_ssh -- dmesg --level=err | log_host "${name}"
		rc=$KSFT_FAIL
	fi

	local b2c="${OUTPUT_DIR}/vm2c.py"
	return "${rc}"
}

	echo "Output directory: ${OUTPUT_DIR}"
QEMU="qemu-system-$(uname -m)"

while getopts :hvsbq:H:p: o
do
	case $o in
	v) VERBOSE=$((VERBOSE+1));;
	s) SHELL_MODE=1;;
	b) BUILD=1;;
	q) QEMU=$OPTARG;;
	H) BUILD_HOST=$OPTARG;;
	p) BUILD_HOST_PODMAN_CONTAINER_NAME=$OPTARG;;
	h|*) usage;;
	esac
done
shift $((OPTIND-1))

	mkdir -p "${OUTPUT_DIR}"
	update_kconfig "${kernel_checkout}" "${kconfig_file}"
trap cleanup EXIT

	recompile_kernel "${kernel_checkout}" "${make_command}"
	update_selftests "${kernel_checkout}" "${make_command}"
PARAMS=""

	if [[ "${build_only}" == "no" ]]; then
		if [[ "${update_b2c}" == "no" && ! -f "${b2c}" ]]; then
			echo "vm2c script not found in ${b2c}"
			update_b2c="yes"
if [[ ${#} -eq 0 ]]; then
	ARGS=("${TEST_NAMES[@]}")
else
	ARGS=()
	COUNT=0
	for arg in $@; do
		COUNT=$((COUNT+1))
		if [[ x"$arg" == x"--" ]]; then
			break
		fi

		if [[ "${update_b2c}" == "yes" ]]; then
			download $B2C_URL $b2c
			chmod +x $b2c
		ARGS+=($arg)
	done
	shift $COUNT
	PARAMS="$@"
fi

		run_vm "${kernel_checkout}" $b2c "${kernel_bzimage}" "${command}"
		if [[ "${debug_shell}" != "yes" ]]; then
			echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"
if [[ "${SHELL_MODE}" -eq 0 ]]; then
	check_args "${ARGS[@]}"
	echo "1..${#ARGS[@]}"
fi
check_deps
check_vng
handle_build

log_setup "Booting up VM"
vm_start
vm_wait_for_ssh
vm_mount_bpffs
log_setup "VM booted up"

if [[ "${SHELL_MODE}" -eq 1 ]]; then
	log_setup "Starting interactive shell in VM"
	echo "Starting shell in VM. Use 'exit' to quit and shutdown the VM."
	CURRENT_DIR="$(pwd)"
	vm_ssh -t -- "cd '${CURRENT_DIR}' && exec bash -l"
	exit "$KSFT_PASS"
fi

		exit $(cat ${OUTPUT_DIR}/${EXIT_STATUS_FILE})
cnt_pass=0
cnt_fail=0
cnt_skip=0
cnt_total=0
for arg in "${ARGS[@]}"; do
	run_test "${arg}" "${PARAMS}"
	rc=$?
	if [[ ${rc} -eq $KSFT_PASS ]]; then
		cnt_pass=$(( cnt_pass + 1 ))
		echo "ok ${cnt_total} ${arg}"
	elif [[ ${rc} -eq $KSFT_SKIP ]]; then
		cnt_skip=$(( cnt_skip + 1 ))
		echo "ok ${cnt_total} ${arg} # SKIP"
	elif [[ ${rc} -eq $KSFT_FAIL ]]; then
		cnt_fail=$(( cnt_fail + 1 ))
		echo "not ok ${cnt_total} ${arg} # exit=$rc"
	fi
}
	cnt_total=$(( cnt_total + 1 ))
done

main "$@"
echo "SUMMARY: PASS=${cnt_pass} SKIP=${cnt_skip} FAIL=${cnt_fail}"
echo "Log: ${LOG}"

if [ $((cnt_pass + cnt_skip)) -eq ${cnt_total} ]; then
	exit "$KSFT_PASS"
else
	exit "$KSFT_FAIL"
fi