#!/bin/bash
# ---------------------------------------------------------------------------
# Run the kernel test suites and write one report.
#
#   ./test-kernel.sh                      the "standard" profile, all engines
#   ./test-kernel.sh --profile smoke      a few minutes, safe anywhere
#   ./test-kernel.sh --engines kunit      only KUnit
#   ./test-kernel.sh --check              say what would run, run nothing
#
# Exit status: 0 when nothing failed, 1 when something did, 2 on a usage or
# environment problem.
# ---------------------------------------------------------------------------
set -uo pipefail

KT_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=lib/common.sh
. "$KT_ROOT/lib/common.sh"

TAG=""
CHECK_ONLY=0

usage() {
    sed -n '3,12p' "$0" | sed 's/^# \{0,1\}//'
    cat <<EOF

Options:
  -p, --profile NAME     smoke | standard | full          (default: standard)
  -e, --engines "LIST"   any of: kunit kselftest ltp      (default: all three)
  -o, --results-dir DIR  where the run folder is made
  -t, --tag NAME         suffix for the run folder, e.g. "before-patch"
      --unsafe           allow tests marked disruptive (needed by --profile full)
      --fail-fast        stop at the first failure
  -c, --check            print the plan and exit
  -h, --help             this text

Profiles:
  smoke     minutes.  Read-only.  Safe on a machine doing real work.
  standard  under two hours.  Does not touch the network, CPUs or kernel taint.
  full      hours.  Reconfigures interfaces, offlines CPUs, taints the kernel.
            Needs --unsafe.  Use a machine you can reboot.
EOF
}

while [ $# -gt 0 ]; do
    case $1 in
        -p|--profile)     KT_PROFILE=$2; shift 2 ;;
        -e|--engines)     KT_ENGINES=$2; shift 2 ;;
        -o|--results-dir) KT_RESULTS_DIR=$2; shift 2 ;;
        -t|--tag)         TAG=$2; shift 2 ;;
        --unsafe)         KT_SAFE=0; shift ;;
        --fail-fast)      KT_FAIL_FAST=1; shift ;;
        -c|--check)       CHECK_ONLY=1; shift ;;
        -h|--help)        usage; exit 0 ;;
        *)                kt_err "unknown option: $1"; usage; exit 2 ;;
    esac
done
export KT_PROFILE KT_ENGINES KT_RESULTS_DIR KT_SAFE KT_FAIL_FAST
kt_load_settings

case $KT_PROFILE in
    smoke|standard|full) ;;
    *) kt_die "unknown profile: $KT_PROFILE (want smoke, standard or full)" ;;
esac
if [ "$KT_PROFILE" = full ] && [ "$KT_SAFE" = 1 ]; then
    kt_err "The \"full\" profile runs tests that reconfigure the network, offline"
    kt_err "CPUs and taint the kernel.  It will not run while KT_SAFE=1."
    kt_err "Add --unsafe if this machine can take it."
    exit 2
fi

kt_require_root

# --- plan ------------------------------------------------------------------
kt_head "Plan"
kt_log "profile : $KT_PROFILE (safe mode: $KT_SAFE)"
kt_log "engines : $KT_ENGINES"
kt_log "kernel  : $(kt_kver) on $(kt_arch)"
kt_log "os      : $(kt_osrel)"
kt_log "selinux : $(kt_selinux_mode)"
kt_log "$(kt_secureboot)"

for e in $KT_ENGINES; do
    case $e in
        kunit)
            if find "/lib/modules/$(uname -r)" -name 'kunit.ko*' -print -quit 2>/dev/null | grep -q .; then
                kt_ok  "  kunit     ready"
            else
                kt_warn "  kunit     kernel-modules-internal missing -- will be skipped"
            fi ;;
        kselftest)
            if [ -x "$KSELFTEST_DIR/run_kselftest.sh" ]; then
                kt_ok  "  kselftest ready ($KSELFTEST_DIR)"
            else
                kt_warn "  kselftest kernel-selftests-internal missing -- will be skipped"
            fi ;;
        ltp)
            if [ -d "$LTP_DIR/runtest" ]; then
                kt_ok  "  ltp       ready ($LTP_DIR, version $(cat "$LTP_DIR/Version" 2>/dev/null))"
            else
                kt_warn "  ltp       not installed at $LTP_DIR -- will be skipped"
            fi ;;
        *) kt_die "unknown engine: $e" ;;
    esac
done

if [ "$CHECK_ONLY" = 1 ]; then
    kt_log "--check given, stopping here"
    exit 0
fi

# --- set up the run --------------------------------------------------------
RUN_DIR=$(kt_new_run_dir "$KT_RESULTS_DIR" "${TAG:-$KT_PROFILE}") || exit 2
kt_info "results -> $RUN_DIR"

CSV="$RUN_DIR/results.csv"
printf '%s\n' "$KT_CSV_HEADER" > "$CSV"

TAINT_BEFORE=$(kt_taint)
START_EPOCH=$(date +%s)
{
    printf 'started: %s\n'      "$(date '+%Y-%m-%d %H:%M:%S %Z')"
    printf 'profile: %s\n'      "$KT_PROFILE"
    printf 'safe_mode: %s\n'    "$KT_SAFE"
    printf 'engines: %s\n'      "$KT_ENGINES"
    printf 'kernel: %s\n'       "$(kt_kver)"
    printf 'arch: %s\n'         "$(kt_arch)"
    printf 'os: %s\n'           "$(kt_osrel)"
    printf 'selinux: %s\n'      "$(kt_selinux_mode)"
    printf 'secureboot: %s\n'   "$(kt_secureboot)"
    printf 'taint_before: %s\n' "$TAINT_BEFORE"
    printf 'cmdline: %s\n'      "$(tr -d '\n' < /proc/cmdline)"
    printf 'uptime_s: %s\n'     "$(cut -d. -f1 /proc/uptime)"
    printf 'nproc: %s\n'        "$(nproc)"
    printf 'memtotal_kb: %s\n'  "$(awk '/MemTotal/{print $2}' /proc/meminfo)"
} > "$RUN_DIR/env.txt"

# Packages that decide what the tests actually are.
rpm -qa 'kernel*' 2>/dev/null | sort > "$RUN_DIR/packages.txt"
cp /proc/cmdline "$RUN_DIR/cmdline.txt" 2>/dev/null
[ -r "/boot/config-$(uname -r)" ] && cp "/boot/config-$(uname -r)" "$RUN_DIR/kconfig.txt"

DMESG_MARK=$(kt_dmesg_mark)

# --- run the engines -------------------------------------------------------
for e in $KT_ENGINES; do
    script="$KT_ROOT/tests/run-$e.sh"
    [ -x "$script" ] || { kt_warn "no runner for engine $e"; continue; }
    "$script" "$RUN_DIR"
done

# --- wrap up ---------------------------------------------------------------
kt_dmesg_since "$DMESG_MARK" > "$RUN_DIR/dmesg-full.txt"
TAINT_AFTER=$(kt_taint)
{
    printf 'finished: %s\n'    "$(date '+%Y-%m-%d %H:%M:%S %Z')"
    printf 'taint_after: %s\n' "$TAINT_AFTER"
    printf 'wall_seconds: %s\n' "$(( $(date +%s) - START_EPOCH ))"
} >> "$RUN_DIR/env.txt"

if [ "$TAINT_BEFORE" != "$TAINT_AFTER" ]; then
    kt_warn "kernel taint changed during the run: $TAINT_BEFORE -> $TAINT_AFTER"
    kt_warn "(loading out-of-tree or test modules does this; it is expected for KUnit)"
fi

kt_head "Report"
"$KT_PYTHON" "$KT_ROOT/lib/report.py" "$RUN_DIR"
rc=$?
kt_info "report  : $RUN_DIR/report.md"
kt_info "raw csv : $RUN_DIR/results.csv"
kt_info "latest  : $KT_RESULTS_DIR/latest"
exit $rc
