blob: 6e8593d5a2c549bcde7900b0e0748d4547bb6181 [file] [edit]
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026, Google LLC.
#
# Generic Boot Tracing Test Harness
# Builds a lightweight initramfs, applies bootconfigs and kernel cmdline parameters,
# boots QEMU, and verifies tracefs configuration using test scripts.
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -d "$SCRIPT_DIR/boottime" ]; then
SCRIPT_DIR="$SCRIPT_DIR/boottime"
fi
KERNEL_SRC="$(cd "$SCRIPT_DIR/../../../../.." && pwd)"
# Source KTAP helpers
if [ -f "$SCRIPT_DIR/../../kselftest/ktap_helpers.sh" ]; then
KSELFTEST_DIR="$(cd "$SCRIPT_DIR/../../kselftest" && pwd)"
elif [ -f "$SCRIPT_DIR/../kselftest/ktap_helpers.sh" ]; then
KSELFTEST_DIR="$(cd "$SCRIPT_DIR/../kselftest" && pwd)"
else
echo "Error: ktap_helpers.sh not found relative to $SCRIPT_DIR" >&2
exit 1
fi
. "$KSELFTEST_DIR/ktap_helpers.sh"
BOOTCONFIG="${BOOTCONFIG:-"$KERNEL_SRC/tools/bootconfig/bootconfig"}"
KERNEL="${KERNEL:-"$KERNEL_SRC/arch/x86/boot/bzImage"}"
QEMU="${QEMU:-"qemu-system-x86_64"}"
BUSYBOX="${BUSYBOX:-"$(which busybox 2>/dev/null || echo "/usr/bin/busybox")"}"
TIMEOUT="${TIMEOUT:-60s}"
LOGDIR="${LOGDIR:-""}"
TARGET_TEST="all"
BUSYBOX_APPLETS=(
sh
cat
mount
umount
echo
sync
reboot
poweroff
cpio
grep
sleep
sed
)
usage() {
echo "Usage: $0 [options]"
echo "Options:"
echo " -b, --bootconfig PATH Path to bootconfig tool (default: $BOOTCONFIG)"
echo " -k, --kernel PATH Path to kernel image (default: $KERNEL)"
echo " -q, --qemu PATH Path to QEMU executable (default: $QEMU)"
echo " -B, --busybox PATH Path to busybox binary (default: $BUSYBOX)"
echo " -T, --timeout DURATION QEMU execution timeout (default: $TIMEOUT)"
echo " -l, --logdir DIR Directory to save QEMU log files"
echo " -t, --test NAME Run specific test (e.g. 01-kprobe) or 'all'"
echo " -h, --help Show this help message"
exit 1
}
while [ $# -gt 0 ]; do
case "$1" in
-b|--bootconfig)
BOOTCONFIG="$2"
shift 2
;;
-k|--kernel)
KERNEL="$2"
shift 2
;;
-q|--qemu)
QEMU="$2"
shift 2
;;
-B|--busybox)
BUSYBOX="$2"
shift 2
;;
-T|--timeout)
TIMEOUT="$2"
shift 2
;;
-l|--logdir)
LOGDIR="$2"
shift 2
;;
-t|--test)
TARGET_TEST="$2"
shift 2
;;
-h|--help)
usage
;;
*)
echo "Unknown option: $1"
usage
;;
esac
done
# Check host architecture
HOST_ARCH="$(uname -m)"
case "$HOST_ARCH" in
x86_64|i686|i386|x86)
;;
*)
if [ "$KERNEL" = "$KERNEL_SRC/arch/x86/boot/bzImage" ]; then
ktap_print_header
ktap_skip_all \
"Default boot tracing test is x86 only ($HOST_ARCH)"
exit 0
fi
;;
esac
# Ensure bootconfig tool is built if Makefile exists
if [ ! -x "$BOOTCONFIG" ]; then
if [ -f "$KERNEL_SRC/tools/bootconfig/Makefile" ]; then
make -C "$KERNEL_SRC/tools/bootconfig" > /dev/null 2>&1 || true
fi
if [ ! -x "$BOOTCONFIG" ]; then
BOOTCONFIG="$(which bootconfig 2>/dev/null || true)"
fi
fi
if [ ! -x "$BOOTCONFIG" ]; then
ktap_print_header
ktap_skip_all "bootconfig tool not found at $BOOTCONFIG"
exit 0
fi
if [ ! -f "$KERNEL" ]; then
ktap_print_header
ktap_skip_all "Kernel image not found at $KERNEL"
exit 0
fi
if ! command -v "$QEMU" >/dev/null 2>&1; then
ktap_print_header
ktap_skip_all "QEMU binary not found: $QEMU"
exit 0
fi
if [ ! -x "$BUSYBOX" ]; then
ktap_print_header
ktap_skip_all "busybox binary not found at $BUSYBOX"
exit 0
fi
# Verify file command existence and architecture compatibility
if ! command -v file >/dev/null 2>&1; then
ktap_print_header
ktap_skip_all "file tool not found"
exit 0
fi
KERNEL_INFO="$(file -bL "$KERNEL" 2>/dev/null || true)"
case "$KERNEL_INFO" in
*x86*)
;;
*)
ktap_print_header
ktap_skip_all "Kernel architecture is not x86 ($KERNEL_INFO)"
exit 0
;;
esac
BUSYBOX_INFO="$(file -bL "$BUSYBOX" 2>/dev/null || true)"
case "$BUSYBOX_INFO" in
*x86-64*|*x86_64*|*80386*|*i386*)
;;
*)
ktap_print_header
ktap_skip_all \
"busybox binary architecture is incompatible with x86 kernel ($BUSYBOX_INFO)"
exit 0
;;
esac
if ! command -v cpio >/dev/null 2>&1; then
ktap_print_header
ktap_skip_all "cpio tool not found"
exit 0
fi
if ! command -v timeout >/dev/null 2>&1; then
ktap_print_header
ktap_skip_all "timeout tool not found"
exit 0
fi
double_timeout() {
local val="$1"
if [[ "$val" =~ ^([0-9]+)([a-z]*)$ ]]; then
local num="${BASH_REMATCH[1]}"
local unit="${BASH_REMATCH[2]}"
echo "$((num * 2))$unit"
else
echo "120s"
fi
}
run_single_test() {
local test_script="$1"
local name
local dir
name="$(basename "$test_script" .sh)"
dir="$(dirname "$test_script")"
local bconf_file=""
if [ -f "$SCRIPT_DIR/bootconfigs/$name.bconf" ]; then
bconf_file="$SCRIPT_DIR/bootconfigs/$name.bconf"
elif [ -f "$dir/$name.bconf" ]; then
bconf_file="$dir/$name.bconf"
fi
local test_cmdline=""
if [ -f "$SCRIPT_DIR/cmdlines/$name.cmdline" ]; then
test_cmdline="$(cat "$SCRIPT_DIR/cmdlines/$name.cmdline")"
elif [ -f "$dir/$name.cmdline" ]; then
test_cmdline="$(cat "$dir/$name.cmdline")"
fi
local inline_cmdline
inline_cmdline="$(grep -E '^# *CMDLINE:' "$test_script" | sed 's/^# *CMDLINE://' | xargs || true)"
if [ -n "$inline_cmdline" ]; then
test_cmdline="$test_cmdline $inline_cmdline"
fi
local test_qemuopts=""
if [ -f "$SCRIPT_DIR/qemuopts/$name.qemuopts" ]; then
test_qemuopts="$(cat "$SCRIPT_DIR/qemuopts/$name.qemuopts")"
elif [ -f "$dir/$name.qemuopts" ]; then
test_qemuopts="$(cat "$dir/$name.qemuopts")"
fi
local inline_qemuopts
inline_qemuopts="$(grep -E '^# *QEMUOPTS:' "$test_script" | sed 's/^# *QEMUOPTS://' | xargs || true)"
if [ -n "$inline_qemuopts" ]; then
test_qemuopts="$test_qemuopts $inline_qemuopts"
fi
local test_timeout="$TIMEOUT"
local inline_timeout
inline_timeout="$(grep -E '^# *TIMEOUT:' "$test_script" | sed 's/^# *TIMEOUT://' | xargs || true)"
if [ -n "$inline_timeout" ]; then
test_timeout="$inline_timeout"
fi
local is_reboot=0
if grep -q -E '^# *REBOOT: *1' "$test_script" || [ -f "$dir/$name.reboot" ]; then
is_reboot=1
fi
local effective_timeout="$test_timeout"
if [ "$is_reboot" -eq 1 ]; then
effective_timeout="$(double_timeout "$test_timeout")"
fi
local workdir
workdir="$(mktemp -d)"
if [ -z "$workdir" ] || [ ! -d "$workdir" ]; then
ktap_test_fail "$name (failed to create temporary directory)"
return 0
fi
trap 'rm -rf "$workdir"' EXIT
local test_applets=("${BUSYBOX_APPLETS[@]}")
local inline_applets
inline_applets="$(grep -E '^# *APPLETS:' "$test_script" | sed 's/^# *APPLETS://' | xargs || true)"
if [ -n "$inline_applets" ]; then
read -r -a extra_applets <<< "$inline_applets" || true
test_applets+=("${extra_applets[@]}")
fi
local rootfs="$workdir/rootfs"
mkdir -p "$rootfs"/{bin,sbin,etc,proc,sys,dev,tmp}
cp -L "$BUSYBOX" "$rootfs/bin/busybox"
chmod +x "$rootfs/bin/busybox"
(cd "$rootfs/bin" && for applet in "${test_applets[@]}"; do ln -sf busybox "$applet"; done)
if command -v ldd >/dev/null 2>&1; then
for lib in $(ldd "$BUSYBOX" 2>/dev/null | grep -o '/[^ ]*' || true); do
if [ -f "$lib" ]; then
mkdir -p "$rootfs$(dirname "$lib")"
cp -L "$lib" "$rootfs$lib" 2>/dev/null || true
fi
done
fi
cat << 'EOF' > "$rootfs/init"
#!/bin/sh
mount -t proc proc /proc 2>/dev/null
mount -t sysfs sys /sys 2>/dev/null
mount -t tracefs nodev /sys/kernel/tracing 2>/dev/null
/bin/check_test.sh
RET=$?
if [ $RET -eq 0 ]; then
echo "TEST RESULT: PASS"
else
echo "TEST RESULT: FAIL"
fi
sync
echo o > /proc/sysrq-trigger 2>/dev/null || true
poweroff -f 2>/dev/null || reboot -f 2>/dev/null || true
while true; do sleep 100 2>/dev/null || break; done
EOF
chmod +x "$rootfs/init"
cp "$test_script" "$rootfs/bin/check_test.sh"
chmod +x "$rootfs/bin/check_test.sh"
local initramfs="$workdir/initramfs.cpio"
(cd "$rootfs" && find . | cpio -o -H newc --quiet) > "$initramfs"
if [ -n "$bconf_file" ] && [ -f "$bconf_file" ]; then
"$BOOTCONFIG" -a "$bconf_file" "$initramfs" > /dev/null
fi
local logfile
local base_cmdline="bootconfig console=tty0 console=ttyS0 panic=-1"
if [ -n "$test_cmdline" ]; then
base_cmdline="$base_cmdline $test_cmdline"
fi
if [ -n "$LOGDIR" ]; then
mkdir -p "$LOGDIR"
logfile="$LOGDIR/$name.log"
base_cmdline="$base_cmdline dump_bconf"
else
logfile="$workdir/qemu.log"
base_cmdline="quiet $base_cmdline"
fi
local qemu_args=()
if [ -n "$test_qemuopts" ]; then
read -r -d '' -a qemu_args <<< "$test_qemuopts" || true
fi
if [ "$is_reboot" -ne 1 ]; then
qemu_args+=("-no-reboot")
fi
timeout "$effective_timeout" "$QEMU" -kernel "$KERNEL" \
-initrd "$initramfs" \
-append "$base_cmdline" \
-display none \
-serial stdio \
"${qemu_args[@]}" < /dev/null > "$logfile" 2>&1 || true
if grep -q "TEST RESULT: PASS" "$logfile"; then
ktap_test_pass "$name"
else
ktap_test_fail "$name"
ktap_print_msg "QEMU Console Output for $name:"
while IFS= read -r line; do
ktap_print_msg "$line"
done < "$logfile"
fi
rm -rf "$workdir"
trap - EXIT
return 0
}
TEST_SCRIPTS=()
if [ -d "$SCRIPT_DIR/tests" ]; then
while IFS= read -r f; do
[ -n "$f" ] && TEST_SCRIPTS+=("$f")
done < <(find "$SCRIPT_DIR/tests" -type f -name "*.sh" | sort)
fi
TOTAL_TESTS=0
for test_script in "${TEST_SCRIPTS[@]}"; do
name="$(basename "$test_script" .sh)"
if [ "$TARGET_TEST" != "all" ] && [ "$TARGET_TEST" != "$name" ]; then
continue
fi
TOTAL_TESTS=$((TOTAL_TESTS + 1))
done
ktap_print_header
ktap_set_plan "$TOTAL_TESTS"
for test_script in "${TEST_SCRIPTS[@]}"; do
name="$(basename "$test_script" .sh)"
if [ "$TARGET_TEST" != "all" ] && [ "$TARGET_TEST" != "$name" ]; then
continue
fi
run_single_test "$test_script"
done
ktap_finished