#!/usr/bin/env bash # Test the current package under a different kernel. # Requires virtme and qemu to be installed. # Examples: # Run all tests on a 5.4 kernel # $ ./run-tests.sh 5.4 # Run a subset of tests: # $ ./run-tests.sh 5.4 ./link # Run using a local kernel image # $ ./run-tests.sh /path/to/bzImage set -euo pipefail script="$(realpath "$0")" readonly script # This script is a bit like a Matryoshka doll since it keeps re-executing itself # in various different contexts: # # 1. invoked by the user like run-tests.sh 5.4 # 2. invoked by go test like run-tests.sh --exec-vm # 3. invoked by init in the vm like run-tests.sh --exec-test # # This allows us to use all available CPU on the host machine to compile our # code, and then only use the VM to execute the test. This is because the VM # is usually slower at compiling than the host. if [[ "${1:-}" = "--exec-vm" ]]; then shift input="$1" shift # Use sudo if /dev/kvm isn't accessible by the current user. sudo="" if [[ ! -r /dev/kvm || ! -w /dev/kvm ]]; then sudo="sudo" fi readonly sudo testdir="$(dirname "$1")" output="$(mktemp -d)" printf -v cmd "%q " "$@" if [[ "$(stat -c '%t:%T' -L /proc/$$/fd/0)" == "1:3" ]]; then # stdin is /dev/null, which doesn't play well with qemu. Use a fifo as a # blocking substitute. mkfifo "${output}/fake-stdin" # Open for reading and writing to avoid blocking. exec 0<> "${output}/fake-stdin" rm "${output}/fake-stdin" fi for ((i = 0; i < 3; i++)); do if ! $sudo virtme-run --kimg "${input}/bzImage" --memory 768M --pwd \ --rwdir="${testdir}=${testdir}" \ --rodir=/run/input="${input}" \ --rwdir=/run/output="${output}" \ --script-sh "PATH=\"$PATH\" CI_MAX_KERNEL_VERSION="${CI_MAX_KERNEL_VERSION:-}" \"$script\" --exec-test $cmd" \ --kopt possible_cpus=2; then # need at least two CPUs for some tests exit 23 fi if [[ -e "${output}/status" ]]; then break fi if [[ -v CI ]]; then echo "Retrying test run due to qemu crash" continue fi exit 42 done rc=$(<"${output}/status") $sudo rm -r "$output" exit $rc elif [[ "${1:-}" = "--exec-test" ]]; then shift mount -t bpf bpf /sys/fs/bpf mount -t tracefs tracefs /sys/kernel/debug/tracing if [[ -d "/run/input/bpf" ]]; then export KERNEL_SELFTESTS="/run/input/bpf" fi if [[ -f "/run/input/bpf/bpf_testmod/bpf_testmod.ko" ]]; then insmod "/run/input/bpf/bpf_testmod/bpf_testmod.ko" fi dmesg --clear rc=0 "$@" || rc=$? dmesg echo $rc > "/run/output/status" exit $rc # this return code is "swallowed" by qemu fi if [[ -z "${1:-}" ]]; then echo "Expecting kernel version or path as first argument" exit 1 fi readonly input="$(mktemp -d)" readonly tmp_dir="${TMPDIR:-/tmp}" fetch() { echo Fetching "${1}" pushd "${tmp_dir}" > /dev/null curl --no-progress-meter -L -O --fail --etag-compare "${1}.etag" --etag-save "${1}.etag" "https://github.com/cilium/ci-kernels/raw/${BRANCH:-master}/${1}" local ret=$? popd > /dev/null return $ret } if [[ -f "${1}" ]]; then readonly kernel="${1}" cp "${1}" "${input}/bzImage" else # LINUX_VERSION_CODE test compares this to discovered value. export KERNEL_VERSION="${1}" readonly kernel="linux-${1}.bz" readonly selftests="linux-${1}-selftests-bpf.tgz" fetch "${kernel}" cp "${tmp_dir}/${kernel}" "${input}/bzImage" if fetch "${selftests}"; then echo "Decompressing selftests" mkdir "${input}/bpf" tar --strip-components=4 -xf "${tmp_dir}/${selftests}" -C "${input}/bpf" else echo "No selftests found, disabling" fi fi shift args=(-short -coverpkg=./... -coverprofile=coverage.out -count 1 ./...) if (( $# > 0 )); then args=("$@") fi export GOFLAGS=-mod=readonly export CGO_ENABLED=0 echo Testing on "${kernel}" go test -exec "$script --exec-vm $input" "${args[@]}" echo "Test successful on ${kernel}" rm -r "${input}"