Makefile: Run kselftests

Add rules and host-script for running Linux kselftests. Tests and test
modules are shared via virtio-9p and the name of the test to run is
passed via command line. One Makefile target is added per unittest and
they can all be called with `make test-ksft`.

Bug: 193407077
Test: make test-ksft
Change-Id: Ic8d9d82b1649e5ca0f8a8510a879fbf0f419304b
diff --git a/Makefile b/Makefile
index db7c74a..a0535d7 100644
--- a/Makefile
+++ b/Makefile
@@ -116,6 +116,7 @@
 	@echo 'Test Targets:'
 	@echo '   test                 - runs all tests in qemu-aarch64 simulated environment'
 	@echo '   test-kut             - runs all kvm-unit-tests in qemu-aarch64 simulated environment'
+	@echo '   test-ksft            - runs all kselftests in qemu-aarch64 simulated environment'
 	@echo '   test-list            - lists all test targets'
 	@echo ''
 	@echo 'Test Parameters:'
@@ -387,7 +388,12 @@
 ##
 
 KVM_MODES := vhe nvhe pkvm
+
 hyphensuffix = $(lastword $(subst -, ,$(1)))
+space := $(subst ,, )
+# Like wordlist but takes negative upper bounds (-2 means remove last two words)
+revwordlist = $(wordlist $(1),$(shell echo '(($(2) - 1 + $(words $(3))) % $(words $(3))) + 1' | bc),$(3))
+hyphenlist = $(subst $(space),-,$(call revwordlist,$(1),$(2),$(subst -, ,$(3))))
 
 RUN_TEST_SH := $(SCRIPTS_DIR)/kvm-unit-tests/run_test.sh
 RUN_TEST = $(RUN_TEST_SH) \
@@ -491,6 +497,28 @@
 		-R $(CROSVM_EXT4)
 $(CROSVM_HELLOWORLD_TESTS): RUN_TEST_TIMEOUT := 300s
 
+#
+# Linux kselftests
+#
+
+KSFT_TEST_TARGETS := $(foreach t,$(KSFT_TESTS),$(foreach m,$(KVM_MODES),\
+	test-ksft-$(subst :,-,$(t))-$(m)))
+
+.PHONY: test-ksft
+test-ksft: $(KSFT_TEST_TARGETS)
+
+
+TEST_LISTS += test-list-ksft
+test-list-ksft: LISTED_TESTS = $(KSFT_TEST_TARGETS)
+
+TESTS += $(KSFT_TEST_TARGETS)
+$(KSFT_TEST_TARGETS): $(KSFT_STAMP)
+$(KSFT_TEST_TARGETS): RUN_TEST_LOG_DIR = $(DIST_DIR)/logs/kselftest
+$(KSFT_TEST_TARGETS): RUN_TEST_HOST_SH = $(SCRIPTS_DIR)/kselftest/host.sh
+$(KSFT_TEST_TARGETS): RUN_TEST_QEMU_ARGS = \
+	-D kselftest:$(KSFT_INSTALL_DIR) \
+	-a 'ksft=$(call hyphenlist,3,3,$@):$(call hyphenlist,4,-1,$@)'
+
 .PHONY test: $(TESTS)
 $(TESTS): $(KERNEL_IMAGE) $(KERNEL_MODULES_DEP)
 	@ $(RUN_TEST)
diff --git a/kselftest/host.sh b/kselftest/host.sh
new file mode 100644
index 0000000..5c28fc0
--- /dev/null
+++ b/kselftest/host.sh
@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+
+# Copyright 2021 The Android KVM Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -eufo pipefail
+
+KSFT_TEST="$(sed -E 's/.*ksft=([^ ]*).*/\1/' /proc/cmdline)"
+
+PASS() { echo -e "\e[32mPASS\e[0m ${KSFT_TEST} ${1-}"; }
+SKIP() { echo -e "\e[33mSKIP\e[0m ${KSFT_TEST} ${1-}"; }
+FAIL() { echo -e "\e[31mFAIL\e[0m ${KSFT_TEST} ${1-}"; }
+
+KSFT_LOG=/tmp/output.log
+KSFT_DIR=/mnt
+MODULES_DIR=/lib/modules
+RUN_KSFT=${KSFT_DIR}/run_kselftest.sh
+
+mount -t 9p -o trans=virtio,version=9p2000.L kselftest "${KSFT_DIR}"
+mount -t 9p -o trans=virtio,version=9p2000.L modules "${MODULES_DIR}"
+
+# Run kselftests.
+"${RUN_KSFT}" -t "${KSFT_TEST}" | tee "${KSFT_LOG}" || true
+
+# Count test results for summary.
+OK_TEST=$(egrep -c '^ok ' "${KSFT_LOG}" || test $? -eq 1)
+SKIPPED=$(egrep -c '^ok .* # SKIP$' "${KSFT_LOG}" || test $? -eq 1)
+PASSED=$(expr ${OK_TEST} - ${SKIPPED} || test $? -eq 1)
+FAILED=$(egrep -c '^not ok ' "${KSFT_LOG}" || test $? -eq 1)
+TOTAL=$(expr ${PASSED} + ${SKIPPED} + ${FAILED} || test $? -eq 1)
+
+MSG="${TOTAL} tests"
+if [ "${FAILED}" -ne 0 ]; then
+	MSG+=", ${FAILED} failed"
+fi
+if [ "${SKIPPED}" -ne 0 ]; then
+	MSG+=", ${SKIPPED} skipped"
+fi
+
+if [ "${TOTAL}" -eq 0 ]; then
+	FAIL "(no tests found)"
+	exit 1
+elif [ "${FAILED}" -ne 0 ]; then
+	FAIL "(${MSG})"
+	exit 1
+elif [ "${PASSED}" -eq 0 ]; then
+	SKIP "(${MSG})"
+	exit 0
+else
+	PASS "(${MSG})"
+	exit 0
+fi
diff --git a/kvm-unit-tests/run_test.sh b/kvm-unit-tests/run_test.sh
index 41e2e22..a696d20 100755
--- a/kvm-unit-tests/run_test.sh
+++ b/kvm-unit-tests/run_test.sh
@@ -30,7 +30,7 @@
 
 DEFAULT_TIMEOUT=180s
 
-RESULT_ALIGN=40
+RESULT_ALIGN=60
 
 function usage() {
 	cat <<EOF