Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3#
4# Copyright (c) 2025 Red Hat
5# Copyright (c) 2025 Meta Platforms, Inc. and affiliates
6#
7# Dependencies:
8# * virtme-ng
9# * busybox-static (used by virtme-ng)
10# * qemu (used by virtme-ng)
11
12readonly SCRIPT_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
13readonly KERNEL_CHECKOUT=$(realpath "${SCRIPT_DIR}"/../../../../)
14
15source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh
16
17readonly HID_BPF_TEST="${SCRIPT_DIR}"/hid_bpf
18readonly HIDRAW_TEST="${SCRIPT_DIR}"/hidraw
19readonly HID_BPF_PROGS="${KERNEL_CHECKOUT}/drivers/hid/bpf/progs"
20readonly SSH_GUEST_PORT=22
21readonly WAIT_PERIOD=3
22readonly WAIT_PERIOD_MAX=60
23readonly WAIT_TOTAL=$(( WAIT_PERIOD * WAIT_PERIOD_MAX ))
24readonly QEMU_PIDFILE=$(mktemp /tmp/qemu_hid_vmtest_XXXX.pid)
25
26readonly QEMU_OPTS="\
27 --pidfile ${QEMU_PIDFILE} \
28"
29readonly KERNEL_CMDLINE=""
30readonly LOG=$(mktemp /tmp/hid_vmtest_XXXX.log)
31readonly TEST_NAMES=(vm_hid_bpf vm_hidraw vm_pytest)
32readonly TEST_DESCS=(
33 "Run hid_bpf tests in the VM."
34 "Run hidraw tests in the VM."
35 "Run the hid-tools test-suite in the VM."
36)
37
38VERBOSE=0
39SHELL_MODE=0
40BUILD_HOST=""
41BUILD_HOST_PODMAN_CONTAINER_NAME=""
42
43usage() {
44 local name
45 local desc
46 local i
47
48 echo
49 echo "$0 [OPTIONS] [TEST]... [-- tests-args]"
50 echo "If no TEST argument is given, all tests will be run."
51 echo
52 echo "Options"
53 echo " -b: build the kernel from the current source tree and use it for guest VMs"
54 echo " -H: hostname for remote build host (used with -b)"
55 echo " -p: podman container name for remote build host (used with -b)"
56 echo " Example: -H beefyserver -p vng"
57 echo " -q: set the path to or name of qemu binary"
58 echo " -s: start a shell in the VM instead of running tests"
59 echo " -v: more verbose output (can be repeated multiple times)"
60 echo
61 echo "Available tests"
62
63 for ((i = 0; i < ${#TEST_NAMES[@]}; i++)); do
64 name=${TEST_NAMES[${i}]}
65 desc=${TEST_DESCS[${i}]}
66 printf "\t%-35s%-35s\n" "${name}" "${desc}"
67 done
68 echo
69
70 exit 1
71}
72
73die() {
74 echo "$*" >&2
75 exit "${KSFT_FAIL}"
76}
77
78vm_ssh() {
79 # vng --ssh-client keeps shouting "Warning: Permanently added 'virtme-ng%22'
80 # (ED25519) to the list of known hosts.",
81 # So replace the command with what's actually called and add the "-q" option
82 stdbuf -oL ssh -q \
83 -F ${HOME}/.cache/virtme-ng/.ssh/virtme-ng-ssh.conf \
84 -l root virtme-ng%${SSH_GUEST_PORT} \
85 "$@"
86 return $?
87}
88
89cleanup() {
90 if [[ -s "${QEMU_PIDFILE}" ]]; then
91 pkill -SIGTERM -F "${QEMU_PIDFILE}" > /dev/null 2>&1
92 fi
93
94 # If failure occurred during or before qemu start up, then we need
95 # to clean this up ourselves.
96 if [[ -e "${QEMU_PIDFILE}" ]]; then
97 rm "${QEMU_PIDFILE}"
98 fi
99}
100
101check_args() {
102 local found
103
104 for arg in "$@"; do
105 found=0
106 for name in "${TEST_NAMES[@]}"; do
107 if [[ "${name}" = "${arg}" ]]; then
108 found=1
109 break
110 fi
111 done
112
113 if [[ "${found}" -eq 0 ]]; then
114 echo "${arg} is not an available test" >&2
115 usage
116 fi
117 done
118
119 for arg in "$@"; do
120 if ! command -v > /dev/null "test_${arg}"; then
121 echo "Test ${arg} not found" >&2
122 usage
123 fi
124 done
125}
126
127check_deps() {
128 for dep in vng ${QEMU} busybox pkill ssh pytest; do
129 if [[ ! -x $(command -v "${dep}") ]]; then
130 echo -e "skip: dependency ${dep} not found!\n"
131 exit "${KSFT_SKIP}"
132 fi
133 done
134
135 if [[ ! -x $(command -v "${HID_BPF_TEST}") ]]; then
136 printf "skip: %s not found!" "${HID_BPF_TEST}"
137 printf " Please build the kselftest hid_bpf target.\n"
138 exit "${KSFT_SKIP}"
139 fi
140
141 if [[ ! -x $(command -v "${HIDRAW_TEST}") ]]; then
142 printf "skip: %s not found!" "${HIDRAW_TEST}"
143 printf " Please build the kselftest hidraw target.\n"
144 exit "${KSFT_SKIP}"
145 fi
146}
147
148check_vng() {
149 local tested_versions
150 local version
151 local ok
152
153 tested_versions=("1.36" "1.37")
154 version="$(vng --version)"
155
156 ok=0
157 for tv in "${tested_versions[@]}"; do
158 if [[ "${version}" == *"${tv}"* ]]; then
159 ok=1
160 break
161 fi
162 done
163
164 if [[ ! "${ok}" -eq 1 ]]; then
165 printf "warning: vng version '%s' has not been tested and may " "${version}" >&2
166 printf "not function properly.\n\tThe following versions have been tested: " >&2
167 echo "${tested_versions[@]}" >&2
168 fi
169}
170
171handle_build() {
172 if [[ ! "${BUILD}" -eq 1 ]]; then
173 return
174 fi
175
176 if [[ ! -d "${KERNEL_CHECKOUT}" ]]; then
177 echo "-b requires vmtest.sh called from the kernel source tree" >&2
178 exit 1
179 fi
180
181 pushd "${KERNEL_CHECKOUT}" &>/dev/null
182
183 if ! vng --kconfig --config "${SCRIPT_DIR}"/config; then
184 die "failed to generate .config for kernel source tree (${KERNEL_CHECKOUT})"
185 fi
186
187 local vng_args=("-v" "--config" "${SCRIPT_DIR}/config" "--build")
188
189 if [[ -n "${BUILD_HOST}" ]]; then
190 vng_args+=("--build-host" "${BUILD_HOST}")
191 fi
192
193 if [[ -n "${BUILD_HOST_PODMAN_CONTAINER_NAME}" ]]; then
194 vng_args+=("--build-host-exec-prefix" \
195 "podman exec -ti ${BUILD_HOST_PODMAN_CONTAINER_NAME}")
196 fi
197
198 if ! vng "${vng_args[@]}"; then
199 die "failed to build kernel from source tree (${KERNEL_CHECKOUT})"
200 fi
201
202 if ! make -j$(nproc) -C "${HID_BPF_PROGS}"; then
203 die "failed to build HID bpf objects from source tree (${HID_BPF_PROGS})"
204 fi
205
206 if ! make -j$(nproc) -C "${SCRIPT_DIR}"; then
207 die "failed to build HID selftests from source tree (${SCRIPT_DIR})"
208 fi
209
210 popd &>/dev/null
211}
212
213vm_start() {
214 local logfile=/dev/null
215 local verbose_opt=""
216 local kernel_opt=""
217 local qemu
218
219 qemu=$(command -v "${QEMU}")
220
221 if [[ "${VERBOSE}" -eq 2 ]]; then
222 verbose_opt="--verbose"
223 logfile=/dev/stdout
224 fi
225
226 # If we are running from within the kernel source tree, use the kernel source tree
227 # as the kernel to boot, otherwise use the currently running kernel.
228 if [[ "$(realpath "$(pwd)")" == "${KERNEL_CHECKOUT}"* ]]; then
229 kernel_opt="${KERNEL_CHECKOUT}"
230 fi
231
232 vng \
233 --run \
234 ${kernel_opt} \
235 ${verbose_opt} \
236 --qemu-opts="${QEMU_OPTS}" \
237 --qemu="${qemu}" \
238 --user root \
239 --append "${KERNEL_CMDLINE}" \
240 --ssh "${SSH_GUEST_PORT}" \
241 --rw &> ${logfile} &
242
243 local vng_pid=$!
244 local elapsed=0
245
246 while [[ ! -s "${QEMU_PIDFILE}" ]]; do
247 if ! kill -0 "${vng_pid}" 2>/dev/null; then
248 echo "vng process (PID ${vng_pid}) exited early, check logs for details" >&2
249 die "failed to boot VM"
250 fi
251
252 if [[ ${elapsed} -ge ${WAIT_TOTAL} ]]; then
253 echo "Timed out after ${WAIT_TOTAL} seconds waiting for VM to boot" >&2
254 die "failed to boot VM"
255 fi
256
257 sleep 1
258 elapsed=$((elapsed + 1))
259 done
260}
261
262vm_wait_for_ssh() {
263 local i
264
265 i=0
266 while true; do
267 if [[ ${i} -gt ${WAIT_PERIOD_MAX} ]]; then
268 die "Timed out waiting for guest ssh"
269 fi
270 if vm_ssh -- true; then
271 break
272 fi
273 i=$(( i + 1 ))
274 sleep ${WAIT_PERIOD}
275 done
276}
277
278vm_mount_bpffs() {
279 vm_ssh -- mount bpffs -t bpf /sys/fs/bpf
280}
281
282__log_stdin() {
283 stdbuf -oL awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0; fflush() }'
284}
285
286__log_args() {
287 echo "$*" | awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0 }'
288}
289
290log() {
291 local verbose="$1"
292 shift
293
294 local prefix="$1"
295
296 shift
297 local redirect=
298 if [[ ${verbose} -le 0 ]]; then
299 redirect=/dev/null
300 else
301 redirect=/dev/stdout
302 fi
303
304 if [[ "$#" -eq 0 ]]; then
305 __log_stdin | tee -a "${LOG}" > ${redirect}
306 else
307 __log_args "$@" | tee -a "${LOG}" > ${redirect}
308 fi
309}
310
311log_setup() {
312 log $((VERBOSE-1)) "setup" "$@"
313}
314
315log_host() {
316 local testname=$1
317
318 shift
319 log $((VERBOSE-1)) "test:${testname}:host" "$@"
320}
321
322log_guest() {
323 local testname=$1
324
325 shift
326 log ${VERBOSE} "# test:${testname}" "$@"
327}
328
329test_vm_hid_bpf() {
330 local testname="${FUNCNAME[0]#test_}"
331
332 vm_ssh -- "${HID_BPF_TEST}" \
333 2>&1 | log_guest "${testname}"
334
335 return ${PIPESTATUS[0]}
336}
337
338test_vm_hidraw() {
339 local testname="${FUNCNAME[0]#test_}"
340
341 vm_ssh -- "${HIDRAW_TEST}" \
342 2>&1 | log_guest "${testname}"
343
344 return ${PIPESTATUS[0]}
345}
346
347test_vm_pytest() {
348 local testname="${FUNCNAME[0]#test_}"
349
350 shift
351
352 vm_ssh -- pytest ${SCRIPT_DIR}/tests --color=yes "$@" \
353 2>&1 | log_guest "${testname}"
354
355 return ${PIPESTATUS[0]}
356}
357
358run_test() {
359 local vm_oops_cnt_before
360 local vm_warn_cnt_before
361 local vm_oops_cnt_after
362 local vm_warn_cnt_after
363 local name
364 local rc
365
366 vm_oops_cnt_before=$(vm_ssh -- dmesg | grep -c -i 'Oops')
367 vm_error_cnt_before=$(vm_ssh -- dmesg --level=err | wc -l)
368
369 name=$(echo "${1}" | awk '{ print $1 }')
370 eval test_"${name}" "$@"
371 rc=$?
372
373 vm_oops_cnt_after=$(vm_ssh -- dmesg | grep -i 'Oops' | wc -l)
374 if [[ ${vm_oops_cnt_after} -gt ${vm_oops_cnt_before} ]]; then
375 echo "FAIL: kernel oops detected on vm" | log_host "${name}"
376 rc=$KSFT_FAIL
377 fi
378
379 vm_error_cnt_after=$(vm_ssh -- dmesg --level=err | wc -l)
380 if [[ ${vm_error_cnt_after} -gt ${vm_error_cnt_before} ]]; then
381 echo "FAIL: kernel error detected on vm" | log_host "${name}"
382 vm_ssh -- dmesg --level=err | log_host "${name}"
383 rc=$KSFT_FAIL
384 fi
385
386 return "${rc}"
387}
388
389QEMU="qemu-system-$(uname -m)"
390
391while getopts :hvsbq:H:p: o
392do
393 case $o in
394 v) VERBOSE=$((VERBOSE+1));;
395 s) SHELL_MODE=1;;
396 b) BUILD=1;;
397 q) QEMU=$OPTARG;;
398 H) BUILD_HOST=$OPTARG;;
399 p) BUILD_HOST_PODMAN_CONTAINER_NAME=$OPTARG;;
400 h|*) usage;;
401 esac
402done
403shift $((OPTIND-1))
404
405trap cleanup EXIT
406
407PARAMS=""
408
409if [[ ${#} -eq 0 ]]; then
410 ARGS=("${TEST_NAMES[@]}")
411else
412 ARGS=()
413 COUNT=0
414 for arg in $@; do
415 COUNT=$((COUNT+1))
416 if [[ x"$arg" == x"--" ]]; then
417 break
418 fi
419 ARGS+=($arg)
420 done
421 shift $COUNT
422 PARAMS="$@"
423fi
424
425if [[ "${SHELL_MODE}" -eq 0 ]]; then
426 check_args "${ARGS[@]}"
427 echo "1..${#ARGS[@]}"
428fi
429check_deps
430check_vng
431handle_build
432
433log_setup "Booting up VM"
434vm_start
435vm_wait_for_ssh
436vm_mount_bpffs
437log_setup "VM booted up"
438
439if [[ "${SHELL_MODE}" -eq 1 ]]; then
440 log_setup "Starting interactive shell in VM"
441 echo "Starting shell in VM. Use 'exit' to quit and shutdown the VM."
442 CURRENT_DIR="$(pwd)"
443 vm_ssh -t -- "cd '${CURRENT_DIR}' && exec bash -l"
444 exit "$KSFT_PASS"
445fi
446
447cnt_pass=0
448cnt_fail=0
449cnt_skip=0
450cnt_total=0
451for arg in "${ARGS[@]}"; do
452 run_test "${arg}" "${PARAMS}"
453 rc=$?
454 if [[ ${rc} -eq $KSFT_PASS ]]; then
455 cnt_pass=$(( cnt_pass + 1 ))
456 echo "ok ${cnt_total} ${arg}"
457 elif [[ ${rc} -eq $KSFT_SKIP ]]; then
458 cnt_skip=$(( cnt_skip + 1 ))
459 echo "ok ${cnt_total} ${arg} # SKIP"
460 elif [[ ${rc} -eq $KSFT_FAIL ]]; then
461 cnt_fail=$(( cnt_fail + 1 ))
462 echo "not ok ${cnt_total} ${arg} # exit=$rc"
463 fi
464 cnt_total=$(( cnt_total + 1 ))
465done
466
467echo "SUMMARY: PASS=${cnt_pass} SKIP=${cnt_skip} FAIL=${cnt_fail}"
468echo "Log: ${LOG}"
469
470if [ $((cnt_pass + cnt_skip)) -eq ${cnt_total} ]; then
471 exit "$KSFT_PASS"
472else
473 exit "$KSFT_FAIL"
474fi