#!/bin/bash
#
# Run LTP tests under Syd.
#
# Copyright 2024, 2025 Ali Polatel <alip@chesswob.org>
#
# SPDX-License-Identifier: GPL-3.0

# TEST_LIMIT: Number of tests to run at a time.
# TEST_PATTERN: An Extended Regex for tests to run.
test_pattern_set=false
case "$1" in
    '')
        TEST_LIMIT=4096
        TEST_PATTERN='.*'
        test_pattern_set=true
        ;;
    [0-9]*)
        TEST_LIMIT="${1}"
        TEST_PATTERN='.*'
        ;;
    *)
        TEST_LIMIT=1500
        TEST_PATTERN="${1}"
        test_pattern_set=true
        ;;
esac

# A Regex (PCRE) for tests to skip.
# execlp01, execvp01: Test broken fails with ENOENT without Syd.
# prctl02: PR_SET_SECCOMP filter mode returns 0, expects EACCES.
#          This is because Syd always runs with NO_NEW_PRIVS bit set.
# inotify01: Test is racy, _sometimes_ fails with
#            "inotify01.c:132: TFAIL: didn't get event: mask=08"
#            TODO: Report upstream.
# semctl06: Fails outside syd too: https://builds.sr.ht/query/log/1599402/ltp64/log
# openat03: Fails outside syd too: ^^: openat03.c:220: file mode read 3700, but expected 7700
# move_pages: Fails on loongarch64, see cfarm400.cfarm.net.
# perf_event_open01: Fails on s390x with PERF_COUNT_HW_INSTRUCTIONS failed unexpectedly: TEST_ERRNO=EACCES(13)
SKIP_PATTERN='(delete_module|exec[lv]p01|inotify01|name_to_handle_at|openat03|move_pages|perf_event_open01|prctl02|semctl06|stress|vmsplice)'

# Tests that have failed in the past.
FAIL_HISTORY=(
)
# Do not go over history, if user specified a test pattern.
$test_pattern_set && FAIL_HISTORY=()

# Make sure we don't trigger TPE.
umask 077

# Enable coredumps.
ulimit -c unlimited

# Force TTY output.
export SYD_FORCE_TTY=YesPlease

# Enable path hiding and ioctl sandboxing for wider coverage.
# This works because LTP profile includes the PALUDIS profile.
export SYD_PALUDIS_LPATH=1
export SYD_PALUDIS_IOCTL=1

# Timeout is 20 minutes per-test unless otherwise specified.
SYD_TEST_TIMEOUT=${SYD_TEST_TIMEOUT:-20m}

export SYD_LOG=${SYD_LOG:-notice}
SYD="${CARGO_BIN_EXE_syd:-syd}"

edo() {
    echo >&2 "-- $*"
    "$@"
}

run_test() {
    local name="$1"
    local file="test-${name}.log"
    [[ -n "${SYD_TEST_DMESG}" ]] && sudo dmesg -C

    "${SYD}" -pltp -mlock:on -- ./testcases/bin/"${name}" 2>&1 | tee "${file}"
    local r=$(grep -c TFAIL "${file}")

    if [[ $r == 0 ]]; then
        return 0
    fi

    if [[ -n "${SYD_TEST_DMESG}" ]]; then
        echo '--8<-- KERNEL LOG BEGIN -->8--'
        sudo dmesg
        echo '-->8-- KERNEL LOG END   --8<--'
    fi

    return $r
}

arg_depth='--depth 1'
if [[ -n "${LTP_HEAD}" ]]; then
    arg_depth=
fi

set -ex
DIR="$(mktemp -d syd-ltp.XXXXXX)"
DIR="$(readlink -f "${DIR}")"
set +e
pushd "${DIR}"
git clone ${arg_depth} --recursive https://github.com/linux-test-project/ltp.git ltp.git || exit 0
pushd ltp.git
if [[ -n "${LTP_HEAD}" ]]; then
    git checkout "${LTP_HEAD}" || exit 127
fi
git rev-parse HEAD

# List of hackpatches:
# ptrace08: Expects EINVAL but we return EFAULT for filter efficiency.
# recvfrom01 (Test 3): EFAULT has precedence over ENOTSOCK for Syd.
sed -i \
    -e 's/EINVAL/EFAULT/' \
    ./testcases/kernel/syscalls/ptrace/ptrace08.c
sed -i \
    -e '/invalid socket buffer/s/ENOTSOCK/EFAULT/' \
    -e '/invalid socket buffer/s/0, EFAULT/-1, EFAULT/' \
    ./testcases/kernel/syscalls/recvfrom/recvfrom01.c

make autotools >ltp-auto.log 2>&1
./configure --prefix="$HOME"/ltp >ltp-conf.log 2>&1 && \
make -j all >ltp-make.log 2>&1 && \
make -j install >ltp-inst.log 2>&1
pushd "$HOME"/ltp
set +x

PASS=0
FAIL=0
SKIP=0
TESTS=( $( awk '!/^($|#)/ {print $1}' runtest/{pty,syscalls} | grep -E "${TEST_PATTERN}" | grep -vE "${SKIP_PATTERN}" | shuf ) )
CTEST=${#TESTS[@]}
NTEST=${TEST_LIMIT}
if [[ ${NTEST} -gt ${CTEST} ]]; then
    NTEST=${CTEST}
fi
TESTS=( "${FAIL_HISTORY[@]}" "${TESTS[@]:0:${NTEST}}" )
NTEST=${#TESTS[@]}

idx=0
for name in "${TESTS[@]}"; do
    : $(( idx++ ))
    echo >&2 -e "\033[92m*** $name ($idx of $NTEST: $PASS ok, $FAIL notok, $SKIP todo) ***\033[0m"
    if echo "${name}" | grep -qE "${SKIP_PATTERN}"; then
        echo "ok ${idx} - ${name} # TODO"
        : $(( SKIP++ ))
    elif run_test "${name}"; then
        echo "ok ${idx} - ${name}"
        : $(( PASS++ ))
    else
        echo "not ok ${idx} - ${name} - FAIL: $?"
        : $(( FAIL++ ))
        [[ -z "${SYD_TEST_QUICK}" ]] || break
    fi
done

echo "# $PASS tests passed."
echo "# $FAIL tests failed."
echo "# $SKIP tests skipped."
exit $FAIL
