#!/bin/bash
# ---------------------------------------------------------------------------
# LTP engine.
#
# LTP is the only one of the three that has to be compiled, so it is built on
# an online machine by build-ltp.sh and shipped as a tarball that unpacks to
# /opt/ltp.  Tests are driven with kirk, LTP's own runner, because it writes a
# JSON report; runltp is used as a fallback when kirk cannot start.
#
#     ./tests/run-ltp.sh <run-dir>
# ---------------------------------------------------------------------------
set -uo pipefail

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

RUN_DIR=${1:-}
[ -n "$RUN_DIR" ] || kt_die "usage: run-ltp.sh <run-dir>"
mkdir -p "$RUN_DIR/ltp"
CSV="$RUN_DIR/results.csv"
LOG="$RUN_DIR/ltp/ltp.log"

kt_head "LTP"

# --- preflight -------------------------------------------------------------
if [ ! -d "$LTP_DIR/runtest" ]; then
    kt_warn "LTP not found at $LTP_DIR"
    kt_warn "Unpack the bundled tarball first:  ./install-offline.sh"
    kt_record "$CSV" ltp "" "(engine)" SKIP "" "LTP not installed at $LTP_DIR"
    exit 0
fi

mkdir -p "$LTP_TMPDIR" 2>/dev/null
if ! [ -w "$LTP_TMPDIR" ]; then
    kt_warn "LTP scratch directory $LTP_TMPDIR is not writable"
    kt_record "$CSV" ltp "" "(engine)" ERROR "" "$LTP_TMPDIR not writable"
    exit 0
fi
avail_mb=$(df -Pm "$LTP_TMPDIR" | awk 'NR==2{print $4}')
if [ "${avail_mb:-0}" -lt 1024 ]; then
    kt_warn "only ${avail_mb}MB free on $LTP_TMPDIR; filesystem tests will report failures"
fi

# LTP's kirk starts with "#!/usr/bin/env python3".  A virtualenv earlier in
# PATH would win, and its python is not necessarily the one this machine's
# LTP was built against, so the interpreter is named explicitly.
USE_KIRK=1
if [ ! -f "$LTP_DIR/kirk" ]; then
    USE_KIRK=0
    kt_log "kirk not present in this LTP build; falling back to runltp"
elif ! "$KT_PYTHON" "$LTP_DIR/kirk" --version >/dev/null 2>&1; then
    USE_KIRK=0
    kt_warn "kirk will not start under $KT_PYTHON; falling back to runltp"
fi

# --- which scenarios -------------------------------------------------------
if [ -n "${LTP_SUITES:-}" ]; then
    read -r -a WANT <<< "$LTP_SUITES"
else
    plist="$KT_ROOT/conf/ltp-${KT_PROFILE}.list"
    [ -r "$plist" ] || kt_die "no scenario list for profile '$KT_PROFILE' ($plist)"
    mapfile -t WANT < <(grep -vE '^\s*(#|$)' "$plist")
fi

SUITES=()
for s in "${WANT[@]}"; do
    [ -n "$s" ] || continue
    if [ ! -f "$LTP_DIR/runtest/$s" ]; then
        kt_warn "no such LTP scenario: $s"
        kt_record "$CSV" ltp "$s" "(scenario)" SKIP "" "runtest/$s not in this LTP build"
        continue
    fi
    SUITES+=("$s")
done
[ ${#SUITES[@]} -gt 0 ] || { kt_warn "no LTP scenarios selected"; exit 0; }
kt_log "${#SUITES[@]} LTP scenarios: ${SUITES[*]}"

# --- skip file -------------------------------------------------------------
# The skip list only lifts for "full" AND KT_SAFE=0.  Gating on KT_SAFE
# alone would let "--profile standard --unsafe" run the tests that drive the
# machine out of memory, on a profile the operator was told is safe.
SKIPFILE=""
if { [ "$KT_SAFE" = 1 ] || [ "$KT_PROFILE" != full ]; } && [ -r "$KT_ROOT/conf/ltp-skip.list" ]; then
    SKIPFILE="$RUN_DIR/ltp/skip.txt"
    grep -vE '^\s*(#|$)' "$KT_ROOT/conf/ltp-skip.list" > "$SKIPFILE"
    kt_log "$(wc -l < "$SKIPFILE") tests excluded by conf/ltp-skip.list (KT_SAFE=1)"
fi

# --- run -------------------------------------------------------------------
export TMPDIR=$LTP_TMPDIR
export LTPROOT=$LTP_DIR
export PATH=$LTP_DIR/testcases/bin:$PATH

for s in "${SUITES[@]}"; do
    kt_log "scenario $s"
    mark=$(kt_dmesg_mark)
    json="$RUN_DIR/ltp/$s.json"
    out="$RUN_DIR/ltp/$s.out"

    if [ "$USE_KIRK" = 1 ]; then
        args=( "$LTP_DIR/kirk"
               --run-suite "$s"
               --json-report "$json"
               --tmp-dir "$LTP_TMPDIR"
               --exec-timeout "$LTP_TIMEOUT"
               --suite-timeout "$LTP_SUITE_TIMEOUT"
               --no-colors )
        [ -n "$SKIPFILE" ] && args+=( --skip-file "$SKIPFILE" )
        ( cd "$LTP_DIR" && "$KT_PYTHON" "${args[@]}" ) > "$out" 2>&1
        rc=$?
        # A report that exists but is truncated (kirk killed by its suite
        # timeout, or an OOM kill mid-write) makes json.load raise.  Without
        # checking, the scenario would contribute no rows AND no error row,
        # and the run would be reported as clean.
        if [ -s "$json" ] && "$KT_PYTHON" "$KT_ROOT/lib/parse_results.py" \
                ltp-json "$json" --collection "$s" >> "$CSV"; then
            :
        elif [ -s "$json" ]; then
            kt_err "  the report for $s could not be read (kirk rc=$rc); it is probably truncated"
            kt_record "$CSV" ltp "$s" "(scenario)" ERROR "" "unreadable JSON report, kirk rc=$rc"
        else
            kt_err "  kirk produced no report for $s (rc=$rc); see ltp/$s.out"
            kt_record "$CSV" ltp "$s" "(scenario)" ERROR "" "kirk rc=$rc, no JSON report"
        fi
    else
        # runltp writes a summary table that parse_results.py can read.
        skipargs=()
        [ -n "$SKIPFILE" ] && skipargs=( -S "$SKIPFILE" )
        ( cd "$LTP_DIR" && timeout --kill-after=60s "${LTP_SUITE_TIMEOUT}s" \
            ./runltp -f "$s" -p -q -l "$RUN_DIR/ltp/$s.runltp" "${skipargs[@]}" ) > "$out" 2>&1
        rc=$?
        if [ -s "$RUN_DIR/ltp/$s.runltp" ] && "$KT_PYTHON" \
                "$KT_ROOT/lib/parse_results.py" ltp-text \
                "$RUN_DIR/ltp/$s.runltp" --collection "$s" >> "$CSV"; then
            :
        elif [ -s "$RUN_DIR/ltp/$s.runltp" ]; then
            kt_record "$CSV" ltp "$s" "(scenario)" ERROR "" "unreadable runltp log, rc=$rc"
        else
            kt_record "$CSV" ltp "$s" "(scenario)" ERROR "" "runltp rc=$rc, no result file"
        fi
    fi

    # Report what this scenario just did.
    # Fields are quoted, so the split on "," leaves a stray quote on the
    # first and last field only; gsub cleans every field the same way.
    tallies=$(awk -F'","' -v s="$s" '{ for(i=1;i<=NF;i++) gsub(/^"|"$/,"",$i) }
                                     $1=="ltp" && $2==s {print $4}' "$CSV" \
              | sort | uniq -c | tr '\n' ' ')
    kt_log "  $s: ${tallies:-no results}"

    if kt_scan_dmesg "$mark" "$RUN_DIR/ltp/$s.badness"; then
        kt_warn "  kernel log complained during $s -- see ltp/$s.badness"
    else
        rm -f "$RUN_DIR/ltp/$s.badness"
    fi

    if [ "$KT_FAIL_FAST" = 1 ] && grep -q "^\"ltp\",\"$s\",.*\"FAIL\"" "$CSV"; then
        kt_err "KT_FAIL_FAST is set; stopping after $s"
        break
    fi
done

# LTP leaves scratch directories behind when a test is killed.
find "$LTP_TMPDIR" -maxdepth 1 -name 'LTP_*' -mmin +1 -exec rm -rf {} + 2>/dev/null
exit 0
