From 64cc7bff90e955ebd0cdd7426222b658e6f6ad17 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Thu, 18 Sep 2025 10:13:15 +0530 Subject: [PATCH] Adding support for IGT tests Add Display IGT Core Auth test suite Add test files for Display IGT Core Authentication validation: - Display_IGT_Core_Auth_TestValidation_Readme.md: Documentation with usage instructions - run.sh: Script to execute and validate core_auth tests Signed-off-by: Salipalli(Temp), Venkata Satya Raja Preetam Jagan --- Runner/init_env | 76 +- Runner/plans/Kernel_AU-Sanity.yaml | 6 +- Runner/plans/fastrpc-premerge.yaml | 2 +- Runner/plans/qcom-next-ci-premerge.yaml | 4 +- Runner/plans/resource-tuner-ci.yaml | 19 + Runner/plans/video_pre-merge.yaml | 81 + Runner/run-test.sh | 13 +- .../Connectivity/WiFi/WiFi_Dynamic_IP/run.sh | 87 +- .../Connectivity/WiFi/WiFi_Manual_IP/run.sh | 53 +- Runner/suites/Kernel/Baseport/Timer/run.sh | 52 +- .../Multimedia/Audio/AudioPlayback/Read_me.md | 31 +- .../Multimedia/Audio/AudioPlayback/run.sh | 369 ++- .../Multimedia/Audio/AudioRecord/Read_me.md | 8 + .../Multimedia/Audio/AudioRecord/run.sh | 492 ++- .../Multimedia/CDSP/fastrpc_test/run.sh | 85 +- .../Camera/Camera_RDI_FrameCapture/run.sh | 226 +- .../Libcamera_cam/libcamera_test_README.md | 170 ++ .../Multimedia/Camera/Libcamera_cam/run.sh | 304 ++ ...lay_IGT_Core_Auth_TestValidation_Readme.md | 149 + .../Display/igt_gpu_tools/core_auth/run.sh | 150 + .../Graphics/weston-simple-egl/run.sh | 160 +- .../Multimedia/OpenCV/opencv_suite_README.md | 336 +++ Runner/suites/Multimedia/OpenCV/run.sh | 442 +++ .../suites/Multimedia/Video/README_Video.md | 78 - .../Video/Video_V4L2_Runner/README_Video.md | 353 +++ .../h264Decoder.json | 40 +- .../h264Encoder.json | 60 +- .../Video/Video_V4L2_Runner/h265Decoder.json | 21 + .../Video/Video_V4L2_Runner/h265Encoder.json | 67 + .../Multimedia/Video/Video_V4L2_Runner/run.sh | 1391 +++++++++ .../Video/Video_V4L2_Runner/vp9Decoder.json | 21 + .../Video/iris_v4l2_video_decode/run.sh | 60 - .../Video/iris_v4l2_video_encode/run.sh | 60 - .../Performance/resource-tuner/README.md | 165 + .../suites/Performance/resource-tuner/run.sh | 518 ++++ Runner/utils/audio_common.sh | 134 +- Runner/utils/camera/lib_camera.sh | 418 +++ Runner/utils/functestlib.sh | 2686 +++++++++++++---- Runner/utils/lib_video.sh | 1952 ++++++++++++ 39 files changed, 10144 insertions(+), 1195 deletions(-) mode change 100644 => 100755 Runner/plans/Kernel_AU-Sanity.yaml create mode 100644 Runner/plans/resource-tuner-ci.yaml create mode 100755 Runner/plans/video_pre-merge.yaml create mode 100644 Runner/suites/Multimedia/Camera/Libcamera_cam/libcamera_test_README.md create mode 100755 Runner/suites/Multimedia/Camera/Libcamera_cam/run.sh create mode 100644 Runner/suites/Multimedia/Display/igt_gpu_tools/core_auth/Display_IGT_Core_Auth_TestValidation_Readme.md create mode 100755 Runner/suites/Multimedia/Display/igt_gpu_tools/core_auth/run.sh create mode 100644 Runner/suites/Multimedia/OpenCV/opencv_suite_README.md create mode 100755 Runner/suites/Multimedia/OpenCV/run.sh delete mode 100644 Runner/suites/Multimedia/Video/README_Video.md create mode 100644 Runner/suites/Multimedia/Video/Video_V4L2_Runner/README_Video.md rename Runner/suites/Multimedia/Video/{iris_v4l2_video_decode => Video_V4L2_Runner}/h264Decoder.json (67%) rename Runner/suites/Multimedia/Video/{iris_v4l2_video_encode => Video_V4L2_Runner}/h264Encoder.json (87%) create mode 100755 Runner/suites/Multimedia/Video/Video_V4L2_Runner/h265Decoder.json create mode 100755 Runner/suites/Multimedia/Video/Video_V4L2_Runner/h265Encoder.json create mode 100755 Runner/suites/Multimedia/Video/Video_V4L2_Runner/run.sh create mode 100755 Runner/suites/Multimedia/Video/Video_V4L2_Runner/vp9Decoder.json delete mode 100755 Runner/suites/Multimedia/Video/iris_v4l2_video_decode/run.sh delete mode 100755 Runner/suites/Multimedia/Video/iris_v4l2_video_encode/run.sh create mode 100644 Runner/suites/Performance/resource-tuner/README.md create mode 100755 Runner/suites/Performance/resource-tuner/run.sh create mode 100755 Runner/utils/camera/lib_camera.sh create mode 100755 Runner/utils/lib_video.sh diff --git a/Runner/init_env b/Runner/init_env index a76d5882..b49a59a4 100755 --- a/Runner/init_env +++ b/Runner/init_env @@ -29,8 +29,9 @@ if [ -z "$ROOT_DIR" ]; then fi fi -if [ ! -d "$ROOT_DIR/utils" ] || [ ! -f "$ROOT_DIR/utils/functestlib.sh" ]; then -echo "[ERROR] Could not detect testkit root (missing utils/ or functestlib.sh)" >&2 +# --- Validate and export key environment paths --- +if [ -z "${ROOT_DIR:-}" ] || [ ! -d "$ROOT_DIR/utils" ] || [ ! -f "$ROOT_DIR/utils/functestlib.sh" ]; then + echo "[ERROR] Could not detect testkit root (missing utils/ or functestlib.sh)" >&2 exit 1 fi @@ -38,3 +39,74 @@ export ROOT_DIR export TOOLS="$ROOT_DIR/utils" export __RUNNER_SUITES_DIR="$ROOT_DIR/suites" export __RUNNER_UTILS_BIN_DIR="$ROOT_DIR/common" + +# --- Ensure TOOLS is usable in all shells --- +case ":$PATH:" in + *":$TOOLS:"*) : ;; + *) + PATH="$TOOLS:$PATH" + export PATH + ;; +esac + +# --- Optional: pre-check for required tools (safe no-op for minimal builds) --- +if [ -f "$TOOLS/functestlib.sh" ]; then + # shellcheck disable=SC1090,SC1091 + . "$TOOLS/functestlib.sh" >/dev/null 2>&1 || true +fi + +############################################################################### +# Stdout/stderr capture (per-test folder) +# +# Controls (set BEFORE sourcing this file): +# RUN_STDOUT_ENABLE = 1 | 0 (default: 1) +# RUN_STDOUT_TAG = (default: basename of $PWD) +# RUN_STDOUT_FILE = (default: $PWD/_stdout_.log) +# +# Behavior: +# - Writes the capture file into the CURRENT DIRECTORY (usually the test dir). +# - No global logs/stdout directory is created/used. +############################################################################### +_runner_stdout_cleanup() { + st=$? + # restore original fds (if they were saved) + exec 1>&3 2>&4 + if [ -n "${__TEE_PID:-}" ]; then + kill "$__TEE_PID" 2>/dev/null + fi + if [ -n "${PIPE:-}" ]; then + rm -f "$PIPE" 2>/dev/null + fi + exit "$st" +} + +if [ "${RUN_STDOUT_ENABLE:-1}" -eq 1 ] && [ -z "${__RUN_STDOUT_ACTIVE:-}" ]; then + _tag="${RUN_STDOUT_TAG:-$(basename "$(pwd)")}" + _ts="$(date +%Y%m%d-%H%M%S)" + RUN_STDOUT_FILE="${RUN_STDOUT_FILE:-$(pwd)/${_tag}_stdout_${_ts}.log}" + export RUN_STDOUT_FILE + + # Save original stdout/stderr + exec 3>&1 4>&2 + + if command -v tee >/dev/null 2>&1; then + PIPE="$(mktemp -u "/tmp/stdout_pipe.XXXXXX")" + if mkfifo "$PIPE" 2>/dev/null; then + ( tee -a "$RUN_STDOUT_FILE" >&3 ) < "$PIPE" & + __TEE_PID=$! + exec > "$PIPE" 2>&1 + __RUN_STDOUT_ACTIVE=1 + trap _runner_stdout_cleanup EXIT INT TERM + else + # Fallback: file-only capture + exec >> "$RUN_STDOUT_FILE" 2>&1 + __RUN_STDOUT_ACTIVE=1 + trap _runner_stdout_cleanup EXIT INT TERM + fi + else + # Fallback: file-only capture + exec >> "$RUN_STDOUT_FILE" 2>&1 + __RUN_STDOUT_ACTIVE=1 + trap _runner_stdout_cleanup EXIT INT TERM + fi +fi diff --git a/Runner/plans/Kernel_AU-Sanity.yaml b/Runner/plans/Kernel_AU-Sanity.yaml old mode 100644 new mode 100755 index 3883584b..785e3727 --- a/Runner/plans/Kernel_AU-Sanity.yaml +++ b/Runner/plans/Kernel_AU-Sanity.yaml @@ -76,8 +76,6 @@ run: - $PWD/utils/send-to-lava.sh $PWD/suites/Connectivity/Bluetooth/BT_ON_FF/BT_ON_FF.res || true - $PWD/suites/Connectivity/Bluetooth/BT_SCAN_PAIR/run.sh || true - $PWD/utils/send-to-lava.sh $PWD/suites/Connectivity/Bluetooth/BT_SCAN_PAIR/BT_SCAN_PAIR.res || true - - $PWD/suites/Multimedia/Video/iris_v4l2_video_decode/run.sh || true - - $PWD/utils/send-to-lava.sh $PWD/suites/Multimedia/Video/iris_v4l2_video_decode/iris_v4l2_video_decode.res || true - - $PWD/suites/Multimedia/Video/iris_v4l2_video_encode/run.sh || true - - $PWD/utils/send-to-lava.sh $PWD/suites/Multimedia/Video/iris_v4l2_video_encode/iris_v4l2_video_encode.res || true + - $PWD/suites/Multimedia/Video/Video_V4L2_Runner/run.sh || true + - $PWD/utils/send-to-lava.sh $PWD/suites/Multimedia/Video/Video_V4L2_Runner/Video_V4L2_Runner.res || true - $PWD/utils/result_parse.sh diff --git a/Runner/plans/fastrpc-premerge.yaml b/Runner/plans/fastrpc-premerge.yaml index 748509bb..4e6961f9 100644 --- a/Runner/plans/fastrpc-premerge.yaml +++ b/Runner/plans/fastrpc-premerge.yaml @@ -14,6 +14,6 @@ metadata: run: steps: - cd Runner - - $PWD/suites/Multimedia/CDSP/fastrpc_test/run.sh --bin-dir /usr/local/bin || true + - $PWD/suites/Multimedia/CDSP/fastrpc_test/run.sh || true - $PWD/utils/send-to-lava.sh $PWD/suites/Multimedia/CDSP/fastrpc_test/fastrpc_test.res || true - $PWD/utils/result_parse.sh diff --git a/Runner/plans/qcom-next-ci-premerge.yaml b/Runner/plans/qcom-next-ci-premerge.yaml index 909f2940..188d839a 100755 --- a/Runner/plans/qcom-next-ci-premerge.yaml +++ b/Runner/plans/qcom-next-ci-premerge.yaml @@ -46,8 +46,8 @@ run: - $PWD/utils/send-to-lava.sh $PWD/suites/Kernel/Baseport/rngtest/rngtest.res || true - $PWD/suites/Kernel/Baseport/smmu/run.sh || true - $PWD/utils/send-to-lava.sh $PWD/suites/Kernel/Baseport/smmu/smmu.res || true - - $PWD/suites/Kernel/Baseport/storage/run.sh || true - - $PWD/utils/send-to-lava.sh $PWD/suites/Kernel/Baseport/storage/storage.res || true + - $PWD/suites/Kernel/Baseport/Storage/UFS_Validation/run.sh || true + - $PWD/utils/send-to-lava.sh $PWD/suites/Kernel/Baseport/Storage/UFS_Validation/UFS_Validation.res || true - $PWD/suites/Kernel/Baseport/wpss_remoteproc/run.sh || true - $PWD/utils/send-to-lava.sh $PWD/suites/Kernel/Baseport/wpss_remoteproc/wpss_remoteproc.res || true - $PWD/suites/Kernel/DCVS/Freq_Scaling/run.sh || true diff --git a/Runner/plans/resource-tuner-ci.yaml b/Runner/plans/resource-tuner-ci.yaml new file mode 100644 index 00000000..87002458 --- /dev/null +++ b/Runner/plans/resource-tuner-ci.yaml @@ -0,0 +1,19 @@ +metadata: + format: Lava-Test Test Definition 1.0 + name: resource-tuner + description: "Run only resource-tuner tests from qcom-linux-testkit" + maintainer: + - smuppand@qti.qualcomm.com + os: + - yocto open embedded + scope: + - Unit Test + devices: + - rb3gen2 + +run: + steps: + - cd Runner + - $PWD/suites/Performance/resource-tuner/run.sh || true + - $PWD/utils/send-to-lava.sh $PWD/suites/Performance/resource-tuner/resource-tuner.res || true + - $PWD/utils/result_parse.sh diff --git a/Runner/plans/video_pre-merge.yaml b/Runner/plans/video_pre-merge.yaml new file mode 100755 index 00000000..625cc5c0 --- /dev/null +++ b/Runner/plans/video_pre-merge.yaml @@ -0,0 +1,81 @@ +metadata: + format: Lava-Test Test Definition 1.0 + name: Video_V4L2_LocalClips + description: "Run Video V4L2 runner with local clips tar and custom .ko dir; auto-args for kodiak vs lemans/monaco; run base & overlay" + maintainer: + - smuppand@qti.qualcomm.com + os: + - openembedded + scope: + - functional +run: + steps: + - cd Runner + - export REPO_ROOT="$PWD" + - export TARGET="${TARGET:-}" # kodiak | lemans | monaco (case-insensitive) + + - | + RPATH="$REPO_ROOT/suites/Multimedia/Video/Video_V4L2_Runner/run.sh" + RESFILE="$REPO_ROOT/suites/Multimedia/Video/Video_V4L2_Runner/Video_V4L2_Runner.res" + + # Custom media bundle (local) path + CLIPS_TAR="/data/vendor/iris_test_app/video_clips_iris.tar.gz" + + # Custom downstream module location (directory containing iris_vpu.ko) + KO_DIR="/data/vendor/iris_test_app" + + # Optional: downstream firmware needed on Kodiak overlay + DS_FW="/data/vendor/iris_test_app/vpu20_1v.mbn" + + # Prefer explicit TARGET; otherwise derive from LAVA device-type + if [ -n "${TARGET:-}" ]; then + TL="$(printf '%s' "$TARGET" | tr '[:upper:]' '[:lower:]')" + else + DEVTYPE_RAW="${DEVICE_TYPE:-${LAVA_DEVICE_TYPE:-${LAVA_DEVICE_TYPE_NAME:-${DEVICE_TYPE_NAME:-}}}}" + DEVTYPE="$(printf '%s' "$DEVTYPE_RAW" | tr '[:upper:]' '[:lower:]')" + case "$DEVTYPE" in + # monaco + qcs8300-ride|qcs8300-ride-sx|iq-8275-evk) TL="monaco" ;; + # lemans + qcs9100-ride-r3|sa8775p-ride|iq-9075-evk|qcs9075-rb8|qcs9100-ride-sx) TL="lemans" ;; + # kodiak + qcs6490-rb3gen2|rb3gen2|qcs6490-rb3gen2-core-kit) TL="kodiak" ;; + # fallback to autodetect in run.sh + *) TL="" ;; + esac + fi + + case "$TL" in + kodiak|lemans|monaco) PLAT="--platform $TL" ;; + *) PLAT="" ;; # autodetect + esac + + echo "DEBUG: TARGET='${TARGET:-}' DEVTYPE='${DEVTYPE:-}' -> TL='$TL' PLAT='$PLAT'" + + # Common args (local clips; no Wi-Fi/TAR_URL needed) + ARGS_COMMON="--clips-tar $CLIPS_TAR --app /data/vendor/iris_test_app/iris_v4l2_test $PLAT --retry-on-fail 2 --loglevel 15" + + # --- BASE (upstream): NO ko args here --- + ARGS_BASE="--stack base $ARGS_COMMON" + + # --- OVERLAY (downstream): add ko args; only add FW on kodiak --- + ARGS_OVERLAY="--stack overlay $ARGS_COMMON" + if [ "$TL" = "kodiak" ]; then + ARGS_OVERLAY="$ARGS_OVERLAY --downstream-fw $DS_FW" + fi + + echo "BASE ARGS: $ARGS_BASE" + echo "OVERLAY ARGS: $ARGS_OVERLAY" + + # Run BASE (upstream) + # shellcheck disable=SC2086 + sh -lc "$RPATH $ARGS_BASE" || true + "$REPO_ROOT/utils/send-to-lava.sh" "$RESFILE" || true + + # Run OVERLAY (downstream) + # shellcheck disable=SC2086 + sh -lc "$RPATH $ARGS_OVERLAY" || true + "$REPO_ROOT/utils/send-to-lava.sh" "$RESFILE" || true + + # Optional roll-up (ignored if absent) + "$REPO_ROOT/utils/result_parse.sh" || true diff --git a/Runner/run-test.sh b/Runner/run-test.sh index 4ff29647..f675fd3f 100755 --- a/Runner/run-test.sh +++ b/Runner/run-test.sh @@ -9,6 +9,10 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # Set TOOLS path to utils under the script directory TOOLS="$SCRIPT_DIR/utils" +# Disable wrapper-level capture; each test will capture in its own folder +export RUN_STDOUT_ENABLE=0 +unset RUN_STDOUT_TAG RUN_STDOUT_FILE + # Safely source init_env from the same directory as this script if [ -f "$SCRIPT_DIR/init_env" ]; then # shellcheck source=/dev/null @@ -46,7 +50,11 @@ execute_test_case() { run_script="$test_path/run.sh" if [ -f "$run_script" ]; then log "Executing test case: $test_name" - (cd "$test_path" && sh "./run.sh" "$@") + ( + cd "$test_path" || exit 2 + # Enable per-test capture in the test folder with a clear tag + RUN_STDOUT_ENABLE=1 RUN_STDOUT_TAG="$test_name" sh "./run.sh" "$@" + ) res_file="$test_path/$test_name.res" if [ -f "$res_file" ]; then if grep -q "SKIP" "$res_file"; then @@ -131,7 +139,8 @@ Usage: Notes: - Extra args are forwarded only when a single is specified. - 'all' runs every test and does not accept additional args. - - Use -h or --help to display this message. + - Each test captures stdout/stderr next to its .res file as: + _stdout_.log EOF } diff --git a/Runner/suites/Connectivity/WiFi/WiFi_Dynamic_IP/run.sh b/Runner/suites/Connectivity/WiFi/WiFi_Dynamic_IP/run.sh index 77517a31..ddafbc4f 100755 --- a/Runner/suites/Connectivity/WiFi/WiFi_Dynamic_IP/run.sh +++ b/Runner/suites/Connectivity/WiFi/WiFi_Dynamic_IP/run.sh @@ -1,5 +1,4 @@ #!/bin/sh - # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. # SPDX-License-Identifier: BSD-3-Clause-Clear @@ -33,42 +32,106 @@ TESTNAME="WiFi_Dynamic_IP" test_path=$(find_test_case_by_name "$TESTNAME") cd "$test_path" || exit 1 +# ---------------- CLI (SSID/PASSWORD) ---------------- +SSID="" +PASSWORD="" + +while [ $# -gt 0 ]; do + case "$1" in + --ssid) + shift + if [ -n "${1:-}" ]; then + SSID="$1" + fi + ;; + --password) + shift + if [ -n "${1:-}" ]; then + PASSWORD="$1" + fi + ;; + --help|-h) + echo "Usage: $0 [--ssid SSID] [--password PASS]" + exit 0 + ;; + *) + log_warn "Unknown argument: $1" + ;; + esac + shift +done + log_info "-------------------------------------------------------------" log_info "------------------- Starting $TESTNAME Test -----------------" +log_info "Host: $(hostname 2>/dev/null || printf 'unknown')" +log_info "Kernel: $(uname -r 2>/dev/null || printf 'unknown')" +log_info "Date: $(date -u 2>/dev/null || printf 'unknown')" # Credential extraction -creds=$(get_wifi_credentials "$1" "$2") || log_skip_exit "$TESTNAME" "WiFi: SSID and/or password missing. Skipping test." wifi_cleanup "" +creds=$(get_wifi_credentials "$SSID" "$PASSWORD") || \ + log_skip_exit "$TESTNAME" "WiFi: SSID and/or password missing. Skipping test." wifi_cleanup "" + SSID=$(echo "$creds" | awk '{print $1}') PASSWORD=$(echo "$creds" | awk '{print $2}') + log_info "Using SSID='$SSID' and PASSWORD='[hidden]'" check_dependencies iw ping # If not a kernel-only/minimal build, systemd is checked, else skipped automatically -check_systemd_services systemd-networkd.service || log_fail_exit "$TESTNAME" "Network services check failed" wifi_cleanup "" +log_info "Checking network service: systemd-networkd.service" +check_systemd_services systemd-networkd.service || \ + log_fail_exit "$TESTNAME" "Network services check failed" wifi_cleanup "" + +WIFI_IFACE=$(get_wifi_interface) || \ + log_fail_exit "$TESTNAME" "No WiFi interface found" wifi_cleanup "" -WIFI_IFACE=$(get_wifi_interface) || log_fail_exit "$TESTNAME" "No WiFi interface found" wifi_cleanup "" log_info "Using WiFi interface: $WIFI_IFACE" +# Prepare a ping log file for command output (appended across retries) +PING_LOG="./wifi_ping_${WIFI_IFACE}.log" +: > "$PING_LOG" +log_info "Ping output will be logged to: $PING_LOG" + # nmcli with retry +log_info "Attempting connection via nmcli…" if wifi_connect_nmcli "$WIFI_IFACE" "$SSID" "$PASSWORD"; then IP=$(wifi_get_ip "$WIFI_IFACE") - [ -z "$IP" ] && log_fail_exit "$TESTNAME" "No IP after nmcli" wifi_cleanup "$WIFI_IFACE" - if retry_command "ping -I \"$WIFI_IFACE\" -c 3 -W 2 8.8.8.8 >/dev/null 2>&1" 3 3; then - log_pass_exit "$TESTNAME" "Internet connectivity verified via ping" wifi_cleanup "$WIFI_IFACE" + + if [ -z "$IP" ]; then + log_fail_exit "$TESTNAME" "No IP after nmcli" wifi_cleanup "$WIFI_IFACE" + fi + + log_info "Acquired IP via nmcli: $IP" + + PING_CMD="ping -I \"$WIFI_IFACE\" -c 3 -W 2 8.8.8.8 2>&1 | tee -a \"$PING_LOG\"" + log_info "Connectivity check command: $PING_CMD" + + if retry_command "$PING_CMD" 3 3; then + log_pass_exit "$TESTNAME" "Internet connectivity verified via ping (iface=$WIFI_IFACE ip=$IP)" wifi_cleanup "$WIFI_IFACE" else - log_fail_exit "$TESTNAME" "Ping test failed after nmcli connection" wifi_cleanup "$WIFI_IFACE" + log_fail_exit "$TESTNAME" "Ping test failed after nmcli connection (iface=$WIFI_IFACE ip=$IP). See $PING_LOG" wifi_cleanup "$WIFI_IFACE" fi fi # wpa_supplicant+udhcpc with retry +log_info "Attempting connection via wpa_supplicant + udhcpc…" if wifi_connect_wpa_supplicant "$WIFI_IFACE" "$SSID" "$PASSWORD"; then IP=$(wifi_get_ip "$WIFI_IFACE") - [ -z "$IP" ] && log_fail_exit "$TESTNAME" "No IP after wpa_supplicant" wifi_cleanup "$WIFI_IFACE" - if retry_command "ping -I \"$WIFI_IFACE\" -c 3 -W 2 8.8.8.8 >/dev/null 2>&1" 3 3; then - log_pass_exit "$TESTNAME" "Internet connectivity verified via ping" wifi_cleanup "$WIFI_IFACE" + + if [ -z "$IP" ]; then + log_fail_exit "$TESTNAME" "No IP after wpa_supplicant" wifi_cleanup "$WIFI_IFACE" + fi + + log_info "Acquired IP via wpa_supplicant: $IP" + + PING_CMD="ping -I \"$WIFI_IFACE\" -c 3 -W 2 8.8.8.8 2>&1 | tee -a \"$PING_LOG\"" + log_info "Connectivity check command: $PING_CMD" + + if retry_command "$PING_CMD" 3 3; then + log_pass_exit "$TESTNAME" "Internet connectivity verified via ping (iface=$WIFI_IFACE ip=$IP)" wifi_cleanup "$WIFI_IFACE" else - log_fail_exit "$TESTNAME" "Ping test failed after wpa_supplicant connection" wifi_cleanup "$WIFI_IFACE" + log_fail_exit "$TESTNAME" "Ping test failed after wpa_supplicant connection (iface=$WIFI_IFACE ip=$IP). See $PING_LOG" wifi_cleanup "$WIFI_IFACE" fi fi diff --git a/Runner/suites/Connectivity/WiFi/WiFi_Manual_IP/run.sh b/Runner/suites/Connectivity/WiFi/WiFi_Manual_IP/run.sh index c45101ab..b8f3484f 100755 --- a/Runner/suites/Connectivity/WiFi/WiFi_Manual_IP/run.sh +++ b/Runner/suites/Connectivity/WiFi/WiFi_Manual_IP/run.sh @@ -29,32 +29,65 @@ fi . "$TOOLS/functestlib.sh" TESTNAME="WiFi_Manual_IP" -test_path=$(find_test_case_by_name "$TESTNAME") +test_path="$(find_test_case_by_name "$TESTNAME")" cd "$test_path" || exit 1 log_info "--------------------------------------------------------------------------" log_info "-------------------Starting $TESTNAME Testcase----------------------------" log_info "=== Test Initialization ===" +# ---------------- CLI (SSID/PASSWORD) ---------------- +SSID="" +PASSWORD="" + +while [ $# -gt 0 ]; do + case "$1" in + --ssid) + shift + if [ -n "${1:-}" ]; then + SSID="$1" + fi + ;; + --password) + shift + if [ -n "${1:-}" ]; then + PASSWORD="$1" + fi + ;; + --help|-h) + echo "Usage: $0 [--ssid SSID] [--password PASS]" + exit 0 + ;; + *) + log_warn "Unknown argument: $1" + ;; + esac + shift +done + # Trap to always restore udhcpc script trap 'restore_udhcpc_script' EXIT -# Credential extraction (from arguments, env, or ssid_list.txt) -if ! CRED=$(get_wifi_credentials "$1" "$2") || [ -z "$CRED" ]; then +# Credential extraction (from CLI, env, or ssid_list.txt via helper) +if ! CRED="$(get_wifi_credentials "$SSID" "$PASSWORD")" || [ -z "$CRED" ]; then log_skip_exit "$TESTNAME" "WiFi: SSID and/or password missing. Skipping test." fi -SSID=$(echo "$CRED" | awk '{print $1}') -PASSWORD=$(echo "$CRED" | awk '{print $2}') +SSID="$(echo "$CRED" | awk '{print $1}')" +PASSWORD="$(echo "$CRED" | awk '{print $2}')" log_info "Using SSID='$SSID' and PASSWORD='[hidden]'" check_dependencies iw wpa_supplicant udhcpc ip -WIFI_IF=$(get_wifi_interface) -[ -z "$WIFI_IF" ] && log_fail_exit "$TESTNAME" "No WiFi interface detected." +WIFI_IF="$(get_wifi_interface)" +if [ -z "$WIFI_IF" ]; then + log_fail_exit "$TESTNAME" "No WiFi interface detected." +fi -UDHCPC_SCRIPT=$(ensure_udhcpc_script) -[ ! -x "$UDHCPC_SCRIPT" ] && log_fail_exit "$TESTNAME" "Failed to create udhcpc script." +UDHCPC_SCRIPT="$(ensure_udhcpc_script)" +if [ ! -x "$UDHCPC_SCRIPT" ]; then + log_fail_exit "$TESTNAME" "Failed to create udhcpc script." +fi wifi_cleanup() { killall -q wpa_supplicant 2>/dev/null @@ -76,7 +109,7 @@ sleep 4 udhcpc -i "$WIFI_IF" -s "$UDHCPC_SCRIPT" -n -q & sleep 8 -IP=$(ip addr show "$WIFI_IF" | awk '/inet / {print $2}' | cut -d/ -f1) +IP="$(ip addr show "$WIFI_IF" | awk '/inet / {print $2}' | cut -d/ -f1)" if [ -n "$IP" ]; then log_info "WiFi got IP: $IP (manual DHCP via udhcpc)" if ping -I "$WIFI_IF" -c 3 -W 2 8.8.8.8 >/dev/null 2>&1; then diff --git a/Runner/suites/Kernel/Baseport/Timer/run.sh b/Runner/suites/Kernel/Baseport/Timer/run.sh index 7abc3d16..3e0e6712 100755 --- a/Runner/suites/Kernel/Baseport/Timer/run.sh +++ b/Runner/suites/Kernel/Baseport/Timer/run.sh @@ -29,6 +29,39 @@ fi # shellcheck disable=SC1090,SC1091 . "$TOOLS/functestlib.sh" +# --- Parse CLI argument for custom binary path --- +show_help() { + cat < "$res_file" + exit 0 +fi # Run the binary and capture the output -OUTPUT=$($BINARY_PATH) +OUTPUT=$($BINARY) +echo $OUTPUT # Check if "pass:7" is in the output if echo "${OUTPUT}" | grep "pass:7"; then diff --git a/Runner/suites/Multimedia/Audio/AudioPlayback/Read_me.md b/Runner/suites/Multimedia/Audio/AudioPlayback/Read_me.md index 2a9701d8..a095d948 100644 --- a/Runner/suites/Multimedia/Audio/AudioPlayback/Read_me.md +++ b/Runner/suites/Multimedia/Audio/AudioPlayback/Read_me.md @@ -7,7 +7,6 @@ This suite automates the validation of audio playback capabilities on Qualcomm L ## Features - - Supports **PipeWire** and **PulseAudio** backends - Plays audio clips with configurable format, duration, and loop count - Automatically downloads and extracts audio assets if missing @@ -63,22 +62,45 @@ ssh user@target_device_ip **Using Unified Runner** cd /Runner + # Run AudioPlayback using PipeWire (auto-detects backend if not specified) ./run-test.sh AudioPlayback + # Force PulseAudio backend AUDIO_BACKEND=pulseaudio ./run-test.sh AudioPlayback + # Custom options via environment variables AUDIO_BACKEND=pipewire PLAYBACK_TIMEOUT=20s PLAYBACK_LOOPS=2 ./run-test.sh AudioPlayback +# Disable asset extraction (offline mode) +EXTRACT_AUDIO_ASSETS=false ./run-test.sh AudioPlayback + +# Provide Wi-Fi credentials for asset download +SSID="MyNetwork" PASSWORD="MyPassword" ./run-test.sh AudioPlayback + +# Override network probe targets (useful in restricted networks) +NET_PROBE_ROUTE_IP=192.168.1.1 NET_PING_HOST=192.168.1.254 ./run-test.sh AudioPlayback + + **Directly from Test Directory** cd Runner/suites/Multimedia/Audio/AudioPlayback # Show usage/help ./run.sh --help + # Run with PipeWire, 3 loops, 10s timeout, speakers sink ./run.sh --backend pipewire --sink speakers --loops 3 --timeout 10s + # Run with PulseAudio, null sink, strict mode, verbose ./run.sh --backend pulseaudio --sink null --strict --verbose +# Disable asset extraction (offline mode) +./run.sh --no-extract-assets + +# Provide JUnit output and disable dmesg scan +./run.sh --junit results.xml --no-dmesg + + + Environment Variables: Variable Description Default AUDIO_BACKEND Selects backend: pipewire or pulseaudio auto-detect @@ -92,6 +114,11 @@ DMESG_SCAN Scan dmesg for errors after playback 1 VERBOSE Enable verbose logging 0 EXTRACT_AUDIO_ASSETS Download/extract audio assets if missing true JUNIT_OUT Path to write JUnit XML output unset +SSID Wi-Fi SSID for network connection unset +PASSWORD Wi-Fi password for network connection unset +NET_PROBE_ROUTE_IP IP used for route probing (default: 1.1.1.1) 1.1.1.1 +NET_PING_HOST Host used for ping reachability check 8.8.8.8 + CLI Options Option Description @@ -142,6 +169,8 @@ Diagnostic logs: dmesg snapshots, mixer dumps, playback logs per test case - If any critical tool is missing, the script exits with an error message. - Logs include dmesg snapshots, mixer dumps, and playback logs. - Asset download requires network connectivity. +- Pass Wi-Fi credentials via SSID and PASSWORD environment variables to enable network access for asset downloads and playback validation. +- You can override default network probe targets using NET_PROBE_ROUTE_IP and NET_PING_HOST to avoid connectivity-related failures in restricted environments. - Evidence-based PASS/FAIL logic ensures reliability even if backend quirks occur. ## License diff --git a/Runner/suites/Multimedia/Audio/AudioPlayback/run.sh b/Runner/suites/Multimedia/Audio/AudioPlayback/run.sh index c684c6f8..35191439 100755 --- a/Runner/suites/Multimedia/Audio/AudioPlayback/run.sh +++ b/Runner/suites/Multimedia/Audio/AudioPlayback/run.sh @@ -5,18 +5,41 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # ---- Source init_env & tools ---- +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" INIT_ENV="" SEARCH="$SCRIPT_DIR" + while [ "$SEARCH" != "/" ]; do - if [ -f "$SEARCH/init_env" ]; then INIT_ENV="$SEARCH/init_env"; break; fi - SEARCH=$(dirname "$SEARCH") + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") done -[ -z "$INIT_ENV" ] && echo "[ERROR] init_env not found" >&2 && exit 1 + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# Only source once (idempotent) +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + # shellcheck disable=SC1090 -[ -z "$__INIT_ENV_LOADED" ] && . "$INIT_ENV" -# shellcheck disable=SC1090,SC1091 +. "$INIT_ENV" +# shellcheck disable=SC1091 . "$TOOLS/functestlib.sh" +# shellcheck disable=SC1091 . "$TOOLS/audio_common.sh" +# shellcheck disable=SC1091 +. "$TOOLS/lib_video.sh" TESTNAME="AudioPlayback" RES_FILE="./${TESTNAME}.res" @@ -38,7 +61,17 @@ STRICT="${STRICT:-0}" DMESG_SCAN="${DMESG_SCAN:-1}" VERBOSE=0 EXTRACT_AUDIO_ASSETS="${EXTRACT_AUDIO_ASSETS:-true}" -JUNIT_OUT="" + +# Network bring-up knobs (match video behavior) +if [ -z "${NET_STABILIZE_SLEEP:-}" ]; then + NET_STABILIZE_SLEEP="5" +fi +if [ -z "${TOP_LEVEL_RUN:-}" ]; then + TOP_LEVEL_RUN="1" +fi + +SSID="" +PASSWORD="" usage() { cat </dev/null || echo "$SCRIPT_DIR")" -cd "$test_path" || { log_error "cd failed: $test_path"; echo "$TESTNAME FAIL" >"$RES_FILE"; exit 1; } +if ! cd "$test_path"; then + log_error "cd failed: $test_path" + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 1 +fi log_info "---------------- Starting $TESTNAME ----------------" -log_info "SoC: $(soc_id)" +# --- Platform details (robust logging; prefer helpers) --- +if command -v detect_platform >/dev/null 2>&1; then + detect_platform >/dev/null 2>&1 || true + log_info "Platform Details: machine='${PLATFORM_MACHINE:-unknown}' target='${PLATFORM_TARGET:-unknown}' kernel='${PLATFORM_KERNEL:-}' arch='${PLATFORM_ARCH:-}'" +else + log_info "Platform Details: unknown" +fi + log_info "Args: backend=${AUDIO_BACKEND:-auto} sink=$SINK_CHOICE loops=$LOOPS timeout=$TIMEOUT formats='$FORMATS' durations='$DURATIONS' strict=$STRICT dmesg=$DMESG_SCAN extract=$EXTRACT_AUDIO_ASSETS" +# --- Rootfs minimum size check (mirror video policy) --- +if [ "$TOP_LEVEL_RUN" -eq 1 ]; then + ensure_rootfs_min_size 2 +else + log_info "Sub-run: skipping rootfs size check (already performed)." +fi + +# --- Network preflight identical to video gating --- +if [ "$TOP_LEVEL_RUN" -eq 1 ]; then + if [ "${EXTRACT_AUDIO_ASSETS}" = "true" ]; then + NET_RC="1" + + if command -v check_network_status_rc >/dev/null 2>&1; then + check_network_status_rc + NET_RC="$?" + elif command -v check_network_status >/dev/null 2>&1; then + check_network_status >/dev/null 2>&1 + NET_RC="$?" + fi + + if [ "$NET_RC" -ne 0 ]; then + video_step "" "Bring network online (Wi-Fi credentials if provided)" + ensure_network_online || true + sleep "${NET_STABILIZE_SLEEP}" + else + sleep "${NET_STABILIZE_SLEEP}" + fi + fi +else + log_info "Sub-run: skipping initial network bring-up." +fi + # Resolve backend -[ -z "$AUDIO_BACKEND" ] && AUDIO_BACKEND="$(detect_audio_backend)" +if [ -z "$AUDIO_BACKEND" ]; then + AUDIO_BACKEND="$(detect_audio_backend)" +fi if [ -z "$AUDIO_BACKEND" ]; then log_skip "$TESTNAME SKIP - no audio backend running" - echo "$TESTNAME SKIP" >"$RES_FILE"; exit 0 + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 fi log_info "Using backend: $AUDIO_BACKEND" if ! check_audio_daemon "$AUDIO_BACKEND"; then log_skip "$TESTNAME SKIP - backend not available: $AUDIO_BACKEND" - echo "$TESTNAME SKIP" >"$RES_FILE"; exit 0 + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 fi # Dependencies per backend if [ "$AUDIO_BACKEND" = "pipewire" ]; then - check_dependencies wpctl pw-play || { log_skip "$TESTNAME SKIP - missing PipeWire utils"; echo "$TESTNAME SKIP" >"$RES_FILE"; exit 0; } + if ! check_dependencies wpctl pw-play; then + log_skip "$TESTNAME SKIP - missing PipeWire utils" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi else - check_dependencies pactl paplay || { log_skip "$TESTNAME SKIP - missing PulseAudio utils"; echo "$TESTNAME SKIP" >"$RES_FILE"; exit 0; } + if ! check_dependencies pactl paplay; then + log_skip "$TESTNAME SKIP - missing PulseAudio utils" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi fi # ----- Route sink (set default; player uses default sink) ----- SINK_ID="" case "$AUDIO_BACKEND:$SINK_CHOICE" in - pipewire:null) SINK_ID="$(pw_default_null)" ;; - pipewire:*) SINK_ID="$(pw_default_speakers)" ;; - pulseaudio:null) SINK_ID="$(pa_default_null)" ;; - pulseaudio:*) SINK_ID="$(pa_default_speakers)" ;; + pipewire:null) + SINK_ID="$(pw_default_null)" + ;; + pipewire:*) + SINK_ID="$(pw_default_speakers)" + ;; + pulseaudio:null) + SINK_ID="$(pa_default_null)" + ;; + pulseaudio:*) + SINK_ID="$(pa_default_speakers)" + ;; esac + if [ -z "$SINK_ID" ]; then log_skip "$TESTNAME SKIP - requested sink '$SINK_CHOICE' not found for $AUDIO_BACKEND" - echo "$TESTNAME SKIP" >"$RES_FILE"; exit 0 + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 fi if [ "$AUDIO_BACKEND" = "pipewire" ]; then SINK_NAME="$(pw_sink_name_safe "$SINK_ID")" wpctl set-default "$SINK_ID" >/dev/null 2>&1 || true - [ -n "$SINK_NAME" ] || SINK_NAME="unknown" + if [ -z "$SINK_NAME" ]; then + SINK_NAME="unknown" + fi log_info "Routing to sink: id=$SINK_ID name='$SINK_NAME' choice=$SINK_CHOICE" else SINK_NAME="$(pa_sink_name "$SINK_ID")" - [ -n "$SINK_NAME" ] || SINK_NAME="$SINK_ID" + if [ -z "$SINK_NAME" ]; then + SINK_NAME="$SINK_ID" + fi pa_set_default_sink "$SINK_ID" >/dev/null 2>&1 || true log_info "Routing to sink: name='$SINK_NAME' choice=$SINK_CHOICE" fi -# JUnit init -if [ -n "$JUNIT_OUT" ]; then JUNIT_TMP="$LOGDIR/.junit_cases.xml"; : > "$JUNIT_TMP"; fi -append_junit() { - name="$1"; elapsed="$2"; status="$3"; logf="$4" - [ -n "$JUNIT_OUT" ] || return 0 - safe_msg="$(tail -n 50 "$logf" 2>/dev/null | sed 's/&/\&/g;s//\>/g')" - { - printf ' \n' "Audio.Playback" "$name" "$elapsed" - case "$status" in - PASS) : ;; - SKIP) printf ' \n' ;; - FAIL) printf ' \n' "failed"; printf '%s\n' "$safe_msg"; printf ' \n' ;; - esac - printf ' \n' - } >> "$JUNIT_TMP" -} - # Decide minimum ok seconds if timeout>0 dur_s="$(duration_to_secs "$TIMEOUT" 2>/dev/null || echo 0)" -[ -z "$dur_s" ] && dur_s=0 +if [ -z "$dur_s" ]; then + dur_s=0 +fi + min_ok=0 if [ "$dur_s" -gt 0 ] 2>/dev/null; then - min_ok=$((dur_s - 1)); [ "$min_ok" -lt 1 ] && min_ok=1 + min_ok=$((dur_s - 1)) + if [ "$min_ok" -lt 1 ]; then + min_ok=1 + fi log_info "Watchdog/timeout: ${TIMEOUT}" else log_info "Watchdog/timeout: disabled (no timeout)" fi # ------------- Matrix execution ------------- -total=0; pass=0; fail=0; skip=0; suite_rc=0 +total=0 +pass=0 +fail=0 +skip=0 +suite_rc=0 for fmt in $FORMATS; do for dur in $DURATIONS; do clip="$(resolve_clip "$fmt" "$dur")" case_name="play_${fmt}_${dur}" total=$((total + 1)) - logf="$LOGDIR/${case_name}.log"; : > "$logf" + logf="$LOGDIR/${case_name}.log" + : > "$logf" export AUDIO_LOGCTX="$logf" if [ -z "$clip" ]; then log_warn "[$case_name] No clip mapping for format=$fmt duration=$dur" echo "$case_name SKIP (no clip mapping)" >> "$LOGDIR/summary.txt" - append_junit "$case_name" "0" "SKIP" "$logf"; skip=$((skip + 1)); continue + skip=$((skip + 1)) + continue fi - if [ "${EXTRACT_AUDIO_ASSETS:-true}" = "true" ]; then - ensure_playback_clip "$clip" || { - log_warn "[$case_name] Clip missing and could not be fetched: $clip" - echo "$case_name SKIP (clip missing)" >> "$LOGDIR/summary.txt" - append_junit "$case_name" "0" "SKIP" "$logf"; skip=$((skip + 1)); continue - } + if [ "${EXTRACT_AUDIO_ASSETS}" = "true" ]; then + if [ -f "$clip" ] && [ -s "$clip" ]; then + CLIP_BYTES="$(wc -c < "$clip" 2>/dev/null || echo 0)" + log_info "[$case_name] Clip already present: $clip (${CLIP_BYTES} bytes) — skipping fetch/extract." + else + log_info "[$case_name] Preparing assets for clip: $clip (not found locally)" + log_info "[$case_name] Attempting fetch/extract from: $AUDIO_TAR_URL" + + audio_ensure_clip_ready "$clip" "$AUDIO_TAR_URL" + rc=$? + + if [ "$rc" -eq 0 ] && [ -f "$clip" ]; then + CLIP_BYTES="$(wc -c < "$clip" 2>/dev/null || echo 0)" + log_info "[$case_name] Clip ready: $clip (${CLIP_BYTES} bytes)" + fi + + if [ "$rc" -eq 2 ] || [ "$rc" -eq 1 ]; then + log_skip "[$case_name] SKIP: Required clip missing and network unavailable or fetch failed." + echo "$case_name SKIP (clip missing)" >> "$LOGDIR/summary.txt" + skip=$((skip + 1)) + continue + fi + fi fi - i=1; ok_runs=0; last_elapsed=0 + i=1 + ok_runs=0 + last_elapsed=0 + while [ "$i" -le "$LOOPS" ]; do iso="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + if [ "$AUDIO_BACKEND" = "pipewire" ]; then loop_hdr="sink=$SINK_CHOICE($SINK_ID)" else loop_hdr="sink=$SINK_CHOICE($SINK_NAME)" fi + log_info "[$case_name] loop $i/$LOOPS start=$iso clip=$clip backend=$AUDIO_BACKEND $loop_hdr" + start_s="$(date +%s 2>/dev/null || echo 0)" if [ "$AUDIO_BACKEND" = "pipewire" ]; then @@ -204,29 +370,43 @@ for fmt in $FORMATS; do fi end_s="$(date +%s 2>/dev/null || echo 0)" - last_elapsed=$((end_s - start_s)); [ "$last_elapsed" -lt 0 ] && last_elapsed=0 + last_elapsed=$((end_s - start_s)) + if [ "$last_elapsed" -lt 0 ]; then + last_elapsed=0 + fi # Evidence - pw_ev=$(audio_evidence_pw_streaming || echo 0) - pa_ev=$(audio_evidence_pa_streaming || echo 0) - # ---- minimal PulseAudio fallback so pa_streaming doesn't read as 0 after teardown ---- + pw_ev="$(audio_evidence_pw_streaming || echo 0)" + pa_ev="$(audio_evidence_pa_streaming || echo 0)" + + # Minimal PulseAudio fallback so pa_streaming doesn't read as 0 after teardown if [ "$AUDIO_BACKEND" = "pulseaudio" ] && [ "$pa_ev" -eq 0 ]; then if [ "$rc" -eq 0 ] || { [ "$rc" -eq 124 ] && [ "$dur_s" -gt 0 ] 2>/dev/null && [ "$last_elapsed" -ge "$min_ok" ]; }; then pa_ev=1 fi fi - alsa_ev=$(audio_evidence_alsa_running_any || echo 0) - asoc_ev=$(audio_evidence_asoc_path_on || echo 0) - pwlog_ev=$(audio_evidence_pw_log_seen || echo 0) - [ "$AUDIO_BACKEND" = "pulseaudio" ] && pwlog_ev=0 + + alsa_ev="$(audio_evidence_alsa_running_any || echo 0)" + asoc_ev="$(audio_evidence_asoc_path_on || echo 0)" + pwlog_ev="$(audio_evidence_pw_log_seen || echo 0)" + if [ "$AUDIO_BACKEND" = "pulseaudio" ]; then + pwlog_ev=0 + fi + # Fast teardown fallback: if user-space stream was active, trust ALSA/ASoC too. if [ "$alsa_ev" -eq 0 ]; then - if [ "$AUDIO_BACKEND" = "pipewire" ] && [ "$pw_ev" -eq 1 ]; then alsa_ev=1; fi - if [ "$AUDIO_BACKEND" = "pulseaudio" ] && [ "$pa_ev" -eq 1 ]; then alsa_ev=1; fi + if [ "$AUDIO_BACKEND" = "pipewire" ] && [ "$pw_ev" -eq 1 ]; then + alsa_ev=1 + fi + if [ "$AUDIO_BACKEND" = "pulseaudio" ] && [ "$pa_ev" -eq 1 ]; then + alsa_ev=1 + fi fi - if [ "$asoc_ev" -eq 0 ] && [ "$alsa_ev" -eq 1 ]; then + + if [ "$asoc_ev" -eq 0 ] && [ "$alsa_ev" -eq 1 ]; then asoc_ev=1 fi + log_info "[$case_name] evidence: pw_streaming=$pw_ev pa_streaming=$pa_ev alsa_running=$alsa_ev asoc_path_on=$asoc_ev pw_log=$pwlog_ev" if [ "$rc" -eq 0 ]; then @@ -250,33 +430,32 @@ for fmt in $FORMATS; do dump_mixers "$LOGDIR/mixer_dump.txt" fi - status="FAIL"; [ "$ok_runs" -ge 1 ] && status="PASS" - append_junit "$case_name" "$last_elapsed" "$status" "$logf" - case "$status" in - PASS) pass=$((pass + 1)); echo "$case_name PASS" >> "$LOGDIR/summary.txt" ;; - SKIP) skip=$((skip + 1)); echo "$case_name SKIP" >> "$LOGDIR/summary.txt" ;; - FAIL) fail=$((fail + 1)); echo "$case_name FAIL" >> "$LOGDIR/summary.txt"; suite_rc=1 ;; - esac + if [ "$ok_runs" -ge 1 ]; then + pass=$((pass + 1)) + echo "$case_name PASS" >> "$LOGDIR/summary.txt" + else + fail=$((fail + 1)) + echo "$case_name FAIL" >> "$LOGDIR/summary.txt" + suite_rc=1 + fi done done log_info "Summary: total=$total pass=$pass fail=$fail skip=$skip" -if [ -n "$JUNIT_OUT" ]; then - tests=$((pass + fail + skip)) - failures="$fail" - skipped="$skip" - { - printf '\n' "$TESTNAME" "$tests" "$failures" "$skipped" - cat "$JUNIT_TMP" - printf '\n' - } > "$JUNIT_OUT" - log_info "Wrote JUnit: $JUNIT_OUT" +# --- Proper exit codes: PASS=0, FAIL=1, SKIP-only=0 --- +if [ "$pass" -eq 0 ] && [ "$fail" -eq 0 ] && [ "$skip" -gt 0 ]; then + log_skip "$TESTNAME SKIP" + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 0 fi if [ "$suite_rc" -eq 0 ]; then - log_pass "$TESTNAME PASS"; echo "$TESTNAME PASS" > "$RES_FILE" -else - log_fail "$TESTNAME FAIL"; echo "$TESTNAME FAIL" > "$RES_FILE" + log_pass "$TESTNAME PASS" + echo "$TESTNAME PASS" > "$RES_FILE" + exit 0 fi -exit "$suite_rc" + +log_fail "$TESTNAME FAIL" +echo "$TESTNAME FAIL" > "$RES_FILE" +exit 1 diff --git a/Runner/suites/Multimedia/Audio/AudioRecord/Read_me.md b/Runner/suites/Multimedia/Audio/AudioRecord/Read_me.md index 7e6a657d..70cc9c32 100644 --- a/Runner/suites/Multimedia/Audio/AudioRecord/Read_me.md +++ b/Runner/suites/Multimedia/Audio/AudioRecord/Read_me.md @@ -63,19 +63,26 @@ ssh user@target_device_ip **Using Unified Runner** cd Runner + # Run Audiorecord using PipeWire (auto-detects backend if not specified) ./run-test.sh Audiorecord + # Force PulseAudio backend AUDIO_BACKEND=pulseaudio ./run-test.sh Audiorecord + # Custom options via environment variables AUDIO_BACKEND=pipewire RECORD_TIMEOUT=20s RECORD_LOOPS=2 RECORD_VOLUME=0.5 ./run-test.sh Audiorecord + **Directly from Test Directory** cd Runner/suites/Multimedia/Audio/Audiorecord + # Show usage/help ./run.sh --help + # Run with PipeWire, 3 loops, 10s timeout, mic source ./run.sh --backend pipewire --source mic --loops 3 --timeout 10s + # Run with PulseAudio, null source, strict mode, verbose ./run.sh --backend pulseaudio --source null --strict --verbose @@ -93,6 +100,7 @@ DMESG_SCAN Scan dmesg for errors after recording 1 VERBOSE Enable verbose logging 0 JUNIT_OUT Path to write JUnit XML output unset + CLI Options: Option Description --backend Select backend: pipewire or pulseaudio diff --git a/Runner/suites/Multimedia/Audio/AudioRecord/run.sh b/Runner/suites/Multimedia/Audio/AudioRecord/run.sh index c8ff0d82..db4cc9b7 100755 --- a/Runner/suites/Multimedia/Audio/AudioRecord/run.sh +++ b/Runner/suites/Multimedia/Audio/AudioRecord/run.sh @@ -8,14 +8,26 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" INIT_ENV="" SEARCH="$SCRIPT_DIR" while [ "$SEARCH" != "/" ]; do - if [ -f "$SEARCH/init_env" ]; then INIT_ENV="$SEARCH/init_env"; break; fi + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi SEARCH=$(dirname "$SEARCH") done -[ -z "$INIT_ENV" ] && echo "[ERROR] init_env not found" >&2 && exit 1 + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] init_env not found" >&2 + exit 1 +fi + # shellcheck disable=SC1090 -[ -z "$__INIT_ENV_LOADED" ] && . "$INIT_ENV" -# shellcheck disable=SC1090,SC1091 +if [ -z "$__INIT_ENV_LOADED" ]; then + . "$INIT_ENV" +fi + +# shellcheck disable=SC1091 . "$TOOLS/functestlib.sh" +# shellcheck disable=SC1091 . "$TOOLS/audio_common.sh" TESTNAME="AudioRecord" @@ -54,73 +66,245 @@ EOF while [ $# -gt 0 ]; do case "$1" in - --backend) AUDIO_BACKEND="$2"; shift 2 ;; - --source) SRC_CHOICE="$2"; shift 2 ;; - --durations) DURATIONS="$2"; shift 2 ;; - --rec-secs) REC_SECS="$2"; shift 2 ;; - --loops) LOOPS="$2"; shift 2 ;; - --timeout) TIMEOUT="$2"; shift 2 ;; - --strict) STRICT=1; shift ;; - --no-dmesg) DMESG_SCAN=0; shift ;; - --junit) JUNIT_OUT="$2"; shift 2 ;; - --verbose) export VERBOSE=1; shift ;; - --help|-h) usage; exit 0 ;; - *) log_warn "Unknown option: $1"; shift ;; + --backend) + AUDIO_BACKEND="$2" + shift 2 + ;; + --source) + SRC_CHOICE="$2" + shift 2 + ;; + --durations) + DURATIONS="$2" + shift 2 + ;; + --rec-secs) + REC_SECS="$2" + shift 2 + ;; + --loops) + LOOPS="$2" + shift 2 + ;; + --timeout) + TIMEOUT="$2" + shift 2 + ;; + --strict) + STRICT=1 + shift + ;; + --no-dmesg) + DMESG_SCAN=0 + shift + ;; + --junit) + JUNIT_OUT="$2" + shift 2 + ;; + --verbose) + export VERBOSE=1 + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + log_warn "Unknown option: $1" + shift + ;; esac done # Ensure we run from the testcase dir test_path="$(find_test_case_by_name "$TESTNAME" 2>/dev/null || echo "$SCRIPT_DIR")" -cd "$test_path" || { log_error "cd failed: $test_path"; echo "$TESTNAME FAIL" >"$RES_FILE"; exit 1; } +if ! cd "$test_path"; then + log_error "cd failed: $test_path" + echo "$TESTNAME FAIL" > "$RES_FILE" + exit 1 +fi log_info "---------------- Starting $TESTNAME ----------------" -log_info "SoC: $(soc_id)" +# --- Platform details (robust logging; prefer helpers) --- +if command -v detect_platform >/dev/null 2>&1; then + detect_platform >/dev/null 2>&1 || true + log_info "Platform Details: machine='${PLATFORM_MACHINE:-unknown}' target='${PLATFORM_TARGET:-unknown}' kernel='${PLATFORM_KERNEL:-}' arch='${PLATFORM_ARCH:-}'" +else + log_info "Platform Details: unknown" +fi + log_info "Args: backend=${AUDIO_BACKEND:-auto} source=$SRC_CHOICE loops=$LOOPS durations='$DURATIONS' rec_secs=$REC_SECS timeout=$TIMEOUT strict=$STRICT dmesg=$DMESG_SCAN" # Resolve backend -[ -z "$AUDIO_BACKEND" ] && AUDIO_BACKEND="$(detect_audio_backend)" +if [ -z "$AUDIO_BACKEND" ]; then + AUDIO_BACKEND="$(detect_audio_backend)" +fi +BACKENDS_TO_TRY="$(build_backend_chain)" +# Use it for visibility and to satisfy shellcheck usage +log_info "Backend fallback chain: $BACKENDS_TO_TRY" if [ -z "$AUDIO_BACKEND" ]; then log_skip "$TESTNAME SKIP - no audio backend running" - echo "$TESTNAME SKIP" >"$RES_FILE"; exit 0 + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 2 fi log_info "Using backend: $AUDIO_BACKEND" if ! check_audio_daemon "$AUDIO_BACKEND"; then log_skip "$TESTNAME SKIP - backend not available: $AUDIO_BACKEND" - echo "$TESTNAME SKIP" >"$RES_FILE"; exit 0 + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 2 fi # Dependencies per backend if [ "$AUDIO_BACKEND" = "pipewire" ]; then - check_dependencies wpctl pw-record || { log_skip "$TESTNAME SKIP - missing PipeWire utils"; echo "$TESTNAME SKIP" >"$RES_FILE"; exit 0; } + if ! check_dependencies wpctl pw-record; then + log_skip "$TESTNAME SKIP - missing PipeWire utils" + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 2 + fi else - check_dependencies pactl parecord || { log_skip "$TESTNAME SKIP - missing PulseAudio utils"; echo "$TESTNAME SKIP" >"$RES_FILE"; exit 0; } + if ! check_dependencies pactl parecord; then + log_skip "$TESTNAME SKIP - missing PulseAudio utils" + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 2 + fi fi # ----- Route source (set default; recorder uses default source) ----- SRC_ID="" case "$AUDIO_BACKEND:$SRC_CHOICE" in - pipewire:null) SRC_ID="$(pw_default_null_source)" ;; - pipewire:*) SRC_ID="$(pw_default_mic)" ;; - pulseaudio:null) SRC_ID="$(pa_default_null_source)" ;; - pulseaudio:*) SRC_ID="$(pa_default_mic)" ;; + pipewire:null) + SRC_ID="$(pw_default_null_source)" + ;; + pipewire:*) + SRC_ID="$(pw_default_mic)" + ;; + pulseaudio:null) + SRC_ID="$(pa_default_null_source)" + ;; + pulseaudio:*) + SRC_ID="$(pa_default_mic)" + ;; esac -if [ -z "$SRC_ID" ]; then - log_skip "$TESTNAME SKIP - requested source '$SRC_CHOICE' not found for $AUDIO_BACKEND" - echo "$TESTNAME SKIP" >"$RES_FILE"; exit 0 + +# ---- Dynamic fallback when mic is missing on the chosen backend ---- +# Stay on PipeWire even if SRC_ID is empty; pw-record and arecord -D pipewire can use the default source. +if [ -z "$SRC_ID" ] && [ "$SRC_CHOICE" = "mic" ] && [ "$AUDIO_BACKEND" != "pipewire" ]; then + for b in $BACKENDS_TO_TRY; do + [ "$b" = "$AUDIO_BACKEND" ] && continue + case "$b" in + pipewire) + cand="$(pw_default_mic)" + if [ -n "$cand" ]; then + AUDIO_BACKEND="pipewire"; SRC_ID="$cand" + log_info "Falling back to backend: pipewire (source id=$SRC_ID)" + break + fi + ;; + pulseaudio) + cand="$(pa_default_mic)" + if [ -n "$cand" ]; then + AUDIO_BACKEND="pulseaudio"; SRC_ID="$cand" + log_info "Falling back to backend: pulseaudio (source=$SRC_ID)" + break + fi + ;; + alsa) + cand="$(alsa_pick_capture)" + if [ -n "$cand" ]; then + AUDIO_BACKEND="alsa"; SRC_ID="$cand" + log_info "Falling back to backend: alsa (device=$SRC_ID)" + break + fi + ;; + esac + done +fi + +# Only skip if no source AND not on PipeWire. +if [ -z "$SRC_ID" ] && [ "$AUDIO_BACKEND" != "pipewire" ]; then + log_skip "$TESTNAME SKIP - requested source '$SRC_CHOICE' not available on any backend ($BACKENDS_TO_TRY)" + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 2 +fi + +# ---- Normalize ALSA device id (fix "hw:0 1," → "hw:0,1") ---- +if [ "$AUDIO_BACKEND" = "alsa" ]; then + case "$SRC_ID" in + hw:*" "*,) + SRC_ID=$(printf '%s' "$SRC_ID" | sed -E 's/^hw:([0-9]+) ([0-9]+),$/hw:\1,\2/') + ;; + hw:*" "*) + SRC_ID=$(printf '%s' "$SRC_ID" | sed -E 's/^hw:([0-9]+) ([0-9]+)$/hw:\1,\2/') + ;; + esac fi +# ---- Validate/auto-pick ALSA device if invalid (prevents "hw:,") ---- +if [ "$AUDIO_BACKEND" = "alsa" ]; then + case "$SRC_ID" in + hw:[0-9]*,[0-9]*|plughw:[0-9]*,[0-9]*) + : ;; + *) + cand="$(arecord -l 2>/dev/null | sed -n 's/^card[[:space:]]*\([0-9][0-9]*\).*device[[:space:]]*\([0-9][0-9]*\).*/hw:\1,\2/p' | head -n 1)" + if [ -z "$cand" ]; then + cand="$(sed -n 's/^\([0-9][0-9]*\)-\([0-9][0-9]*\):.*capture.*/hw:\1,\2/p' /proc/asound/pcm 2>/dev/null | head -n 1)" + fi + if [ -z "$cand" ]; then + cand="$(sed -n 's/.*\[\s*\([0-9][0-9]*\)-\s*\([0-9][0-9]*\)\]:.*capture.*/hw:\1,\2/p' /proc/asound/devices 2>/dev/null | head -n 1)" + fi + if printf '%s\n' "$cand" | grep -Eq '^hw:[0-9]+,[0-9]+$'; then + SRC_ID="$cand" + log_info "ALSA auto-pick: using $SRC_ID" + else + log_skip "$TESTNAME SKIP - no valid ALSA capture device found" + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 2 + fi + ;; + esac +fi + +# ---- Routing log / defaults per backend ---- if [ "$AUDIO_BACKEND" = "pipewire" ]; then - SRC_LABEL="$(pw_source_label_safe "$SRC_ID")" - wpctl set-default "$SRC_ID" >/dev/null 2>&1 || true - [ -n "$SRC_LABEL" ] || SRC_LABEL="unknown" - log_info "Routing to source: id/name=$SRC_ID label='$SRC_LABEL' choice=$SRC_CHOICE" -else + if [ -n "$SRC_ID" ]; then + SRC_LABEL="$(pw_source_label_safe "$SRC_ID")" + wpctl set-default "$SRC_ID" >/dev/null 2>&1 || true + [ -z "$SRC_LABEL" ] && SRC_LABEL="unknown" + log_info "Routing to source: id/name=$SRC_ID label='$SRC_LABEL' choice=$SRC_CHOICE" + else + SRC_LABEL="default" + log_info "Routing to source: id/name=default label='default' choice=$SRC_CHOICE" + fi +elif [ "$AUDIO_BACKEND" = "pulseaudio" ]; then SRC_LABEL="$(pa_source_name "$SRC_ID" 2>/dev/null || echo "$SRC_ID")" pa_set_default_source "$SRC_ID" >/dev/null 2>&1 || true log_info "Routing to source: name='$SRC_LABEL' choice=$SRC_CHOICE" +else # ALSA + SRC_LABEL="$SRC_ID" + log_info "Routing to source: name='$SRC_LABEL' choice=$SRC_CHOICE" fi +# If fallback changed backend, ensure deps are present (non-fatal → SKIP) +case "$AUDIO_BACKEND" in + pipewire) + if ! check_dependencies wpctl pw-record; then + log_skip "$TESTNAME SKIP - missing PipeWire utils" + echo "$TESTNAME SKIP" > "$RES_FILE"; exit 2 + fi ;; + pulseaudio) + if ! check_dependencies pactl parecord; then + log_skip "$TESTNAME SKIP - missing PulseAudio utils" + echo "$TESTNAME SKIP" > "$RES_FILE"; exit 2 + fi ;; + alsa) + if ! check_dependencies arecord; then + log_skip "$TESTNAME SKIP - missing arecord" + echo "$TESTNAME SKIP" > "$RES_FILE"; exit 2 + fi ;; +esac + # Watchdog info dur_s="$(duration_to_secs "$TIMEOUT" 2>/dev/null || echo 0)" [ -z "$dur_s" ] && dur_s=0 @@ -131,23 +315,42 @@ else fi # JUnit init (optional) -if [ -n "$JUNIT_OUT" ]; then JUNIT_TMP="$LOGDIR/.junit_cases.xml"; : > "$JUNIT_TMP"; fi +if [ -n "$JUNIT_OUT" ]; then + JUNIT_TMP="$LOGDIR/.junit_cases.xml" + : > "$JUNIT_TMP" +fi + append_junit() { - name="$1"; elapsed="$2"; status="$3"; logf="$4" - [ -n "$JUNIT_OUT" ] || return 0 - safe_msg="$(tail -n 50 "$logf" 2>/dev/null | sed 's/&/\&/g;s//\>/g')" + name="$1" + elapsed="$2" + status="$3" + logf="$4" + + if [ -z "$JUNIT_OUT" ]; then + return 0 + fi + + safe_msg="$( + tail -n 50 "$logf" 2>/dev/null \ + | sed 's/&/\&/g;s//\>/g' + )" + { printf ' \n' "Audio.Record" "$name" "$elapsed" case "$status" in PASS) : ;; SKIP) printf ' \n' ;; - FAIL) printf ' \n' "failed"; printf '%s\n' "$safe_msg"; printf ' \n' ;; + FAIL) + printf ' \n' "failed" + printf '%s\n' "$safe_msg" + printf ' \n' + ;; esac printf ' \n' } >> "$JUNIT_TMP" } -# Auto map if REC_SECS=auto, and accept numeric tokens in DURATIONS like 35s/35sec/35secs/35seconds +# Auto map if REC_SECS=auto, and accept numeric tokens like 35s/35sec/35secs/35seconds auto_secs_for() { case "$1" in short) echo "5s" ;; @@ -157,13 +360,35 @@ auto_secs_for() { esac } +# Prefer virtual capture PCMs (PipeWire/Pulse) over raw hw: when a sound server is present +alsa_pick_virtual_pcm() { + command -v arecord >/dev/null 2>&1 || return 1 + + pcs="$(arecord -L 2>/dev/null | sed -n 's/^[[:space:]]*\([[:alnum:]_]\+\)[[:space:]]*$/\1/p')" + + for pcm in pipewire pulse default; do + if printf '%s\n' "$pcs" | grep -m1 -x "$pcm" >/dev/null 2>&1; then + printf '%s\n' "$pcm" + return 0 + fi + done + + return 1 +} + # ---------------- Matrix execution ---------------- -total=0; pass=0; fail=0; skip=0; suite_rc=0 +total=0 +pass=0 +fail=0 +skip=0 +suite_rc=0 for dur in $DURATIONS; do case_name="record_${dur}" total=$((total + 1)) - logf="$LOGDIR/${case_name}.log"; : > "$logf" + + logf="$LOGDIR/${case_name}.log" + : > "$logf" export AUDIO_LOGCTX="$logf" secs="$REC_SECS" @@ -177,78 +402,162 @@ for dur in $DURATIONS; do fi fi - i=1; ok_runs=0; last_elapsed=0 + i=1 + ok_runs=0 + last_elapsed=0 + while [ "$i" -le "$LOOPS" ]; do iso="$(date -u +%Y-%m-%dT%H:%M:%SZ)" effective_timeout="$secs" if [ -n "$TIMEOUT" ] && [ "$TIMEOUT" != "0" ]; then effective_timeout="$TIMEOUT" fi + loop_hdr="source=$SRC_CHOICE" - [ "$AUDIO_BACKEND" = "pipewire" ] && loop_hdr="$loop_hdr($SRC_ID)" || loop_hdr="$loop_hdr($SRC_LABEL)" + if [ "$AUDIO_BACKEND" = "pipewire" ]; then + if [ -n "$SRC_ID" ]; then + loop_hdr="$loop_hdr($SRC_ID)" + else + loop_hdr="$loop_hdr(default)" + fi + else + loop_hdr="$loop_hdr($SRC_LABEL)" + fi + log_info "[$case_name] loop $i/$LOOPS start=$iso secs=$secs backend=$AUDIO_BACKEND $loop_hdr" + out="$LOGDIR/${case_name}.wav" : > "$out" start_s="$(date +%s 2>/dev/null || echo 0)" + if [ "$AUDIO_BACKEND" = "pipewire" ]; then log_info "[$case_name] exec: pw-record -v \"$out\"" - audio_exec_with_timeout "$effective_timeout" pw-record -v "$out" >>"$logf" 2>&1 + audio_exec_with_timeout "$effective_timeout" pw-record -v "$out" >> "$logf" 2>&1 rc=$? bytes="$(stat -c '%s' "$out" 2>/dev/null || wc -c < "$out")" + # If we already got real audio, accept and skip fallbacks + if [ "${bytes:-0}" -gt 1024 ] 2>/dev/null; then + if [ "$rc" -ne 0 ]; then + log_warn "[$case_name] nonzero rc=$rc but recording looks valid (bytes=$bytes) - PASS" + rc=0 + fi + else + # Only if output is tiny/empty do we try a virtual PCM (pipewire/pulse/default) + if command -v arecord >/dev/null 2>&1; then + pcm="$(alsa_pick_virtual_pcm || true)" + if [ -n "$pcm" ]; then + secs_int="$(audio_parse_secs "$secs" 2>/dev/null || echo 0)"; [ -z "$secs_int" ] && secs_int=0 + : > "$out" + log_info "[$case_name] fallback: arecord -D $pcm -f S16_LE -r 48000 -c 2 -d $secs_int \"$out\"" + audio_exec_with_timeout "$effective_timeout" \ + arecord -D "$pcm" -f S16_LE -r 48000 -c 2 -d "$secs_int" "$out" >> "$logf" 2>&1 + rc=$? + bytes="$(stat -c '%s' "$out" 2>/dev/null || wc -c < "$out")" + fi + fi + + # As a last resort, retry pw-record with --target (only if we have a source id) + if { [ "$rc" -ne 0 ] || [ "${bytes:-0}" -le 1024 ] 2>/dev/null; } && [ -n "$SRC_ID" ]; then + : > "$out" + log_info "[$case_name] exec: pw-record -v --target \"$SRC_ID\" \"$out\"" + audio_exec_with_timeout "$effective_timeout" pw-record -v --target "$SRC_ID" "$out" >> "$logf" 2>&1 + rc=$? + bytes="$(stat -c '%s' "$out" 2>/dev/null || wc -c < "$out")" + fi + fi + + # (Optional safety) If nonzero rc but output is clearly valid, accept. if [ "$rc" -ne 0 ] && [ "${bytes:-0}" -gt 1024 ] 2>/dev/null; then - log_warn "[$case_name] nonzero rc=$rc but recording looks valid (bytes=$bytes) - PASS" + log_warn "[$case_name] nonzero rc==$rc but recording looks valid (bytes=$bytes) - PASS" rc=0 fi + else + if [ "$AUDIO_BACKEND" = "alsa" ]; then + secs_int="$(audio_parse_secs "$secs" 2>/dev/null || echo 0)" + [ -z "$secs_int" ] && secs_int=0 + log_info "[$case_name] exec: arecord -D \"$SRC_ID\" -f S16_LE -r 48000 -c 2 -d $secs_int \"$out\"" + audio_exec_with_timeout "$effective_timeout" \ + arecord -D "$SRC_ID" -f S16_LE -r 48000 -c 2 -d "$secs_int" "$out" >> "$logf" 2>&1 + rc=$? + bytes="$(stat -c '%s' "$out" 2>/dev/null || wc -c < "$out")" + + if [ "$rc" -ne 0 ] || [ "${bytes:-0}" -le 1024 ] 2>/dev/null; then + if printf '%s\n' "$SRC_ID" | grep -q '^hw:'; then + alt_dev="plughw:${SRC_ID#hw:}" + else + alt_dev="$SRC_ID" + fi + for combo in "S16_LE 48000 2" "S16_LE 44100 2" "S16_LE 16000 1"; do + fmt=$(printf '%s\n' "$combo" | awk '{print $1}') + rate=$(printf '%s\n' "$combo" | awk '{print $2}') + ch=$(printf '%s\n' "$combo" | awk '{print $3}') + [ -z "$fmt" ] || [ -z "$rate" ] || [ -z "$ch" ] && continue + : > "$out" + log_info "[$case_name] retry: arecord -D \"$alt_dev\" -f $fmt -r $rate -c $ch -d $secs_int \"$out\"" + audio_exec_with_timeout "$effective_timeout" \ + arecord -D "$alt_dev" -f "$fmt" -r "$rate" -c "$ch" -d "$secs_int" "$out" >> "$logf" 2>&1 + rc=$? + bytes="$(stat -c '%s' "$out" 2>/dev/null || wc -c < "$out")" + if [ "$rc" -eq 0 ] && [ "${bytes:-0}" -gt 1024 ] 2>/dev/null; then + break + fi + done + fi - if [ "$rc" -ne 0 ] && [ "${bytes:-0}" -le 1024 ] 2>/dev/null; then - log_warn "[$case_name] first attempt rc=$rc bytes=$bytes; retry with --target $SRC_ID" - : > "$out" - log_info "[$case_name] exec: pw-record -v --target \"$SRC_ID\" \"$out\"" - audio_exec_with_timeout "$effective_timeout" pw-record -v --target "$SRC_ID" "$out" >>"$logf" 2>&1 + if [ "$rc" -ne 0 ] && [ "${bytes:-0}" -gt 1024 ] 2>/dev/null; then + log_warn "[$case_name] nonzero rc=$rc but recording looks valid (bytes=$bytes) - PASS" + rc=0 + fi + else + log_info "[$case_name] exec: parecord --file-format=wav \"$out\"" + audio_exec_with_timeout "$effective_timeout" parecord --file-format=wav "$out" >> "$logf" 2>&1 rc=$? bytes="$(stat -c '%s' "$out" 2>/dev/null || wc -c < "$out")" if [ "$rc" -ne 0 ] && [ "${bytes:-0}" -gt 1024 ] 2>/dev/null; then - log_warn "[$case_name] nonzero rc=$rc after retry but recording looks valid (bytes=$bytes) - PASS" + log_warn "[$case_name] nonzero rc=$rc but recording looks valid (bytes=$bytes) - PASS" rc=0 fi fi - else - log_info "[$case_name] exec: parecord --file-format=wav \"$out\"" - audio_exec_with_timeout "$effective_timeout" parecord --file-format=wav "$out" >>"$logf" 2>&1 - rc=$? - bytes="$(stat -c '%s' "$out" 2>/dev/null || wc -c < "$out")" - if [ "$rc" -ne 0 ] && [ "${bytes:-0}" -gt 1024 ] 2>/dev/null; then - log_warn "[$case_name] nonzero rc=$rc but recording looks valid (bytes=$bytes) - PASS" - rc=0 - fi fi + end_s="$(date +%s 2>/dev/null || echo 0)" - last_elapsed=$((end_s - start_s)); [ "$last_elapsed" -lt 0 ] && last_elapsed=0 + last_elapsed=$((end_s - start_s)) + [ "$last_elapsed" -lt 0 ] && last_elapsed=0 # Evidence pw_ev=$(audio_evidence_pw_streaming || echo 0) pa_ev=$(audio_evidence_pa_streaming || echo 0) - # ---- minimal PulseAudio fallback so pa_streaming doesn't read as 0 after teardown ---- + if [ "$AUDIO_BACKEND" = "pulseaudio" ] && [ "$pa_ev" -eq 0 ]; then - if [ "$rc" -eq 0 ] && [ "${bytes:-0}" -gt 1024 ] 2>/dev/null; then pa_ev=1; fi + if [ "$rc" -eq 0 ] && [ "${bytes:-0}" -gt 1024 ] 2>/dev/null; then + pa_ev=1 + fi fi + alsa_ev=$(audio_evidence_alsa_running_any || echo 0) asoc_ev=$(audio_evidence_asoc_path_on || echo 0) pwlog_ev=$(audio_evidence_pw_log_seen || echo 0) - [ "$AUDIO_BACKEND" = "pulseaudio" ] && pwlog_ev=0 - # Fast teardown fallback: if user-space stream was active, trust ALSA/ASoC too. + if [ "$AUDIO_BACKEND" = "pulseaudio" ]; then + pwlog_ev=0 + fi + if [ "$alsa_ev" -eq 0 ]; then - if [ "$AUDIO_BACKEND" = "pipewire" ] && [ "$pw_ev" -eq 1 ]; then alsa_ev=1; fi - if [ "$AUDIO_BACKEND" = "pulseaudio" ] && [ "$pa_ev" -eq 1 ]; then alsa_ev=1; fi + if [ "$AUDIO_BACKEND" = "pipewire" ] && [ "$pw_ev" -eq 1 ]; then + alsa_ev=1 + fi + if [ "$AUDIO_BACKEND" = "pulseaudio" ] && [ "$pa_ev" -eq 1 ]; then + alsa_ev=1 + fi fi + if [ "$asoc_ev" -eq 0 ] && [ "$alsa_ev" -eq 1 ]; then - asoc_ev=1 + asoc_ev=1 fi + log_info "[$case_name] evidence: pw_streaming=$pw_ev pa_streaming=$pa_ev alsa_running=$alsa_ev asoc_path_on=$asoc_ev bytes=${bytes:-0} pw_log=$pwlog_ev" - # Final PASS/FAIL if [ "$rc" -eq 0 ] && [ "${bytes:-0}" -gt 1024 ] 2>/dev/null; then log_pass "[$case_name] loop $i OK (rc=0, ${last_elapsed}s, bytes=$bytes)" ok_runs=$((ok_runs + 1)) @@ -264,12 +573,27 @@ for dur in $DURATIONS; do dump_mixers "$LOGDIR/mixer_dump.txt" fi - status="FAIL"; [ "$ok_runs" -ge 1 ] && status="PASS" + status="FAIL" + if [ "$ok_runs" -ge 1 ]; then + status="PASS" + fi + append_junit "$case_name" "$last_elapsed" "$status" "$logf" + case "$status" in - PASS) pass=$((pass + 1)); echo "$case_name PASS" >> "$LOGDIR/summary.txt" ;; - SKIP) skip=$((skip + 1)); echo "$case_name SKIP" >> "$LOGDIR/summary.txt" ;; - FAIL) fail=$((fail + 1)); echo "$case_name FAIL" >> "$LOGDIR/summary.txt"; suite_rc=1 ;; + PASS) + pass=$((pass + 1)) + echo "$case_name PASS" >> "$LOGDIR/summary.txt" + ;; + SKIP) + skip=$((skip + 1)) + echo "$case_name SKIP" >> "$LOGDIR/summary.txt" + ;; + FAIL) + fail=$((fail + 1)) + echo "$case_name FAIL" >> "$LOGDIR/summary.txt" + suite_rc=1 + ;; esac done @@ -287,9 +611,19 @@ if [ -n "$JUNIT_OUT" ]; then log_info "Wrote JUnit: $JUNIT_OUT" fi +# Exit codes: PASS=0, FAIL=1, SKIP=2 +if [ "$pass" -eq 0 ] && [ "$fail" -eq 0 ] && [ "$skip" -gt 0 ]; then + log_skip "$TESTNAME SKIP" + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 2 +fi + if [ "$suite_rc" -eq 0 ]; then - log_pass "$TESTNAME PASS"; echo "$TESTNAME PASS" > "$RES_FILE" -else - log_fail "$TESTNAME FAIL"; echo "$TESTNAME FAIL" > "$RES_FILE" + log_pass "$TESTNAME PASS" + echo "$TESTNAME PASS" > "$RES_FILE" + exit 0 fi -exit "$suite_rc" + +log_fail "$TESTNAME FAIL" +echo "$TESTNAME FAIL" > "$RES_FILE" +exit 1 diff --git a/Runner/suites/Multimedia/CDSP/fastrpc_test/run.sh b/Runner/suites/Multimedia/CDSP/fastrpc_test/run.sh index 27afa1a0..c5b73a43 100755 --- a/Runner/suites/Multimedia/CDSP/fastrpc_test/run.sh +++ b/Runner/suites/Multimedia/CDSP/fastrpc_test/run.sh @@ -36,7 +36,7 @@ REPEAT=1 TIMEOUT="" ARCH="" BIN_DIR="" # directory that CONTAINS fastrpc_test -ASSETS_DIR="" # informational; assets should be alongside binary (…/linux) +ASSETS_DIR="" # kept for compatibility/logging (not used by new layout) VERBOSE=0 USER_PD_FLAG=0 # default: -U 0 (system/signed PD) CLI_DOMAIN="" @@ -48,8 +48,8 @@ Usage: $0 [OPTIONS] Options: --arch Architecture (only if explicitly provided) - --bin-dir Directory containing 'fastrpc_test' (default: /usr/bin) - --assets-dir Directory that CONTAINS 'linux/' (info only) + --bin-dir Directory containing 'fastrpc_test' (default: /usr/local/bin) + --assets-dir (compat) previously used when assets lived under 'linux/' --domain <0|1|2|3> DSP domain: 0=ADSP, 1=MDSP, 2=SDSP, 3=CDSP --domain-name DSP domain by name: adsp|mdsp|sdsp|cdsp --user-pd Use '-U 1' (user/unsigned PD). Default is '-U 0' @@ -66,9 +66,14 @@ Env: ALLOW_BIN_FASTRPC=1 Permit using /bin/fastrpc_test when --bin-dir=/bin. Notes: -- Script *cd*s into the binary's directory and launches ./fastrpc_test so - 'linux/' next to the binary (e.g. /usr/bin/linux) is discovered reliably. -- If domain not provided, we auto-pick: CDSP if present; else ADSP; else SDSP; else 3. +- Script *cd*s into the binary directory and launches ./fastrpc_test. +- Libraries are resolved via: + LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib/fastrpc_test[:\$LD_LIBRARY_PATH] +- DSP skeletons are resolved via (if present): + ADSP_LIBRARY_PATH=/usr/local/share/fastrpc_test/v75[:v68] + CDSP_LIBRARY_PATH=/usr/local/share/fastrpc_test/v75[:v68] + SDSP_LIBRARY_PATH=/usr/local/share/fastrpc_test/v75[:v68] +- If domain not provided, auto-pick: CDSP if present; else ADSP; else SDSP; else 3. EOF } @@ -89,6 +94,13 @@ while [ $# -gt 0 ]; do esac done +# ---- Back-compat: accept --assets-dir but ignore in the new /usr/local layout. +# Export so external tooling (or legacy wrappers) can still read it. +if [ -n "${ASSETS_DIR:-}" ]; then + export ASSETS_DIR + log_info "(compat) --assets-dir provided: $ASSETS_DIR (ignored with /usr/local layout)" +fi + # ---------- Validation ---------- case "$REPEAT" in *[!0-9]*|"") log_error "Invalid --repeat: $REPEAT"; echo "$TESTNAME : FAIL" >"$RESULT_FILE"; exit 1 ;; esac if [ -n "$TIMEOUT" ]; then @@ -126,16 +138,6 @@ cmd_to_string() { printf "%s" "$out" } -log_soc_info() { - m=""; s=""; pv="" - [ -r /sys/devices/soc0/machine ] && m="$(cat /sys/devices/soc0/machine 2>/dev/null)" - [ -r /sys/devices/soc0/soc_id ] && s="$(cat /sys/devices/soc0/soc_id 2>/dev/null)" - [ -r /sys/devices/soc0/platform_version ] && pv="$(cat /sys/devices/soc0/platform_version 2>/dev/null)" - [ -n "$m" ] && log_info "SoC.machine: $m" - [ -n "$s" ] && log_info "SoC.soc_id: $s" - [ -n "$pv" ] && log_info "SoC.platform_version: $pv" -} - log_dsp_remoteproc_status() { fw_list="adsp cdsp cdsp0 cdsp1 sdsp gdsp0 gdsp1" any=0 @@ -192,14 +194,14 @@ log_soc_info if [ -n "$BIN_DIR" ]; then : else - BIN_DIR="/usr/bin" + BIN_DIR="/usr/local/bin" fi case "$BIN_DIR" in /bin) if [ "${ALLOW_BIN_FASTRPC:-0}" -ne 1 ]; then - log_fail "Refusing /bin by default (set ALLOW_BIN_FASTRPC=1 or use --bin-dir /usr/bin)" - echo "$TESTNAME : FAIL" >"$RESULT_FILE" + log_skip "$TESTNAME SKIP - unsupported layout: /bin. Set ALLOW_BIN_FASTRPC=1 or pass --bin-dir." + echo "$TESTNAME : SKIP" >"$RESULT_FILE" exit 1 fi ;; @@ -209,15 +211,23 @@ RUN_DIR="$BIN_DIR" RUN_BIN="$RUN_DIR/fastrpc_test" if [ ! -x "$RUN_BIN" ]; then - log_fail "fastrpc_test not executable at: $RUN_BIN" - echo "$TESTNAME : FAIL" >"$RESULT_FILE" + log_skip "$TESTNAME SKIP - fastrpc_test not installed (expected at: $RUN_BIN)" + echo "$TESTNAME : SKIP" >"$RESULT_FILE" exit 1 fi -if [ -n "$ASSETS_DIR" ]; then - [ -d "$ASSETS_DIR/linux" ] || log_warn "--assets-dir provided but no 'linux/' inside: $ASSETS_DIR" -fi -[ -d "$RUN_DIR/linux" ] || log_warn "No 'linux/' under $RUN_DIR; the sample libs may be missing" +# New layout checks (replace legacy 'linux/' checks) +LIB_SYS_DIR="/usr/local/lib" +LIB_TEST_DIR="/usr/local/lib/fastrpc_test" +SKEL_BASE="/usr/local/share/fastrpc_test" + +SKEL_PATH="" +[ -d "$SKEL_BASE/v75" ] && SKEL_PATH="${SKEL_PATH:+$SKEL_PATH:}$SKEL_BASE/v75" +[ -d "$SKEL_BASE/v68" ] && SKEL_PATH="${SKEL_PATH:+$SKEL_PATH:}$SKEL_BASE/v68" + +[ -d "$LIB_SYS_DIR" ] || log_warn "Missing system libs dir: $LIB_SYS_DIR (lib{adsp,cdsp,sdsp}rpc*.so expected)" +[ -d "$LIB_TEST_DIR" ] || log_warn "Missing test libs dir: $LIB_TEST_DIR (libcalculator.so, etc.)" +[ -n "$SKEL_PATH" ] || log_warn "No DSP skeleton dirs found under: $SKEL_BASE (expected v75/ v68/)" log_info "Using binary: $RUN_BIN" log_info "Run dir: $RUN_DIR (launching ./fastrpc_test)" @@ -225,6 +235,22 @@ log_info "Binary details:" log_info " ls -l: $(ls -l "$RUN_BIN" 2>/dev/null || echo 'N/A')" log_info " file : $(file "$RUN_BIN" 2>/dev/null || echo 'N/A')" +# >>>>>>>>>>>>>>>>>>>>>> ENV for your initramfs layout <<<<<<<<<<<<<<<<<<<<<< +# Libraries: system + test payloads +export LD_LIBRARY_PATH="/usr/local/lib:/usr/local/lib/fastrpc_test${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" +# Skeletons: export if present (don’t clobber if user already set) +[ -n "$SKEL_PATH" ] && { + : "${ADSP_LIBRARY_PATH:=$SKEL_PATH}"; export ADSP_LIBRARY_PATH + : "${CDSP_LIBRARY_PATH:=$SKEL_PATH}"; export CDSP_LIBRARY_PATH + : "${SDSP_LIBRARY_PATH:=$SKEL_PATH}"; export SDSP_LIBRARY_PATH +} +log_info "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" +[ -n "$ADSP_LIBRARY_PATH" ] && log_info "ADSP_LIBRARY_PATH=${ADSP_LIBRARY_PATH}" +[ -n "$CDSP_LIBRARY_PATH" ] && log_info "CDSP_LIBRARY_PATH=${CDSP_LIBRARY_PATH}" +[ -n "$SDSP_LIBRARY_PATH" ] && log_info "SDSP_LIBRARY_PATH=${SDSP_LIBRARY_PATH}" +# <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +# Ensure /usr/lib/dsp has the expected DSP artifacts (generic, idempotent) +ensure_usr_lib_dsp_symlinks # Log *dsp remoteproc statuses via existing helpers log_dsp_remoteproc_status @@ -254,7 +280,7 @@ case "$DOMAIN" in log_info "Domain auto-picked: -d $DOMAIN (CDSP=3, ADSP=0, SDSP=2)" ;; * ) - log_warn "Invalid domain '$DOMAIN'; auto-picking" + log_warn "Invalid domain '$DOMAIN' auto-picking" DOMAIN="$(pick_default_domain)" ;; esac @@ -323,6 +349,9 @@ while [ "$i" -le "$REPEAT" ]; do echo "RUN_BIN=$RUN_BIN" echo "PATH=$PATH" echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH:-}" + echo "ADSP_LIBRARY_PATH=${ADSP_LIBRARY_PATH:-}" + echo "CDSP_LIBRARY_PATH=${CDSP_LIBRARY_PATH:-}" + echo "SDSP_LIBRARY_PATH=${SDSP_LIBRARY_PATH:-}" echo "ARCH=${ARCH:-}" echo "PD_VAL=$PD_VAL" echo "DOMAIN=$DOMAIN ($dom_name)" @@ -337,7 +366,7 @@ while [ "$i" -le "$REPEAT" ]; do ( cd "$RUN_DIR" || exit 127 if [ $HAVE_STDBUF -eq 1 ]; then - run_with_timeout "$TIMEOUT" stdbuf -oL -eL ./fastrpc_test "$@" + runWithTimeoutIfSet stdbuf -oL -eL ./fastrpc_test "$@" elif [ $HAVE_SCRIPT -eq 1 ]; then cmd_str="./fastrpc_test$(cmd_to_string "$@")" if [ -n "$TIMEOUT" ] && [ $HAVE_TIMEOUT -eq 1 ]; then @@ -346,7 +375,7 @@ while [ "$i" -le "$REPEAT" ]; do script -q -c "$cmd_str" /dev/null fi else - run_with_timeout "$TIMEOUT" ./fastrpc_test "$@" + runWithTimeoutIfSet ./fastrpc_test "$@" fi ) >"$iter_log" 2>&1 rc=$? diff --git a/Runner/suites/Multimedia/Camera/Camera_RDI_FrameCapture/run.sh b/Runner/suites/Multimedia/Camera/Camera_RDI_FrameCapture/run.sh index 8dc80e12..edff66da 100755 --- a/Runner/suites/Multimedia/Camera/Camera_RDI_FrameCapture/run.sh +++ b/Runner/suites/Multimedia/Camera/Camera_RDI_FrameCapture/run.sh @@ -2,20 +2,37 @@ # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. # SPDX-License-Identifier: BSD-3-Clause-Clear # --- Robustly find and source init_env --------------------------- -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# ---------- Repo env + helpers ---------- +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" INIT_ENV="" SEARCH="$SCRIPT_DIR" + while [ "$SEARCH" != "/" ]; do if [ -f "$SEARCH/init_env" ]; then - INIT_ENV="$SEARCH/init_env"; break + INIT_ENV="$SEARCH/init_env" + break fi SEARCH=$(dirname "$SEARCH") done -[ -z "$INIT_ENV" ] && echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 && exit 1 + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# Only source once (idempotent) +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi # shellcheck disable=SC1090 -[ -z "$__INIT_ENV_LOADED" ] && . "$INIT_ENV" -# shellcheck disable=SC1090,SC1091 +. "$INIT_ENV" +# shellcheck disable=SC1091 . "$TOOLS/functestlib.sh" TESTNAME="Camera_RDI_FrameCapture" @@ -46,71 +63,16 @@ while [ $# -gt 0 ]; do --format) shift; USER_FORMAT="$1" ;; --frames) shift; FRAMES="$1" ;; --help) print_usage; exit 0 ;; - *) log_error "Unknown argument: $1"; print_usage; exit 1 ;; + *) log_error "Unknown argument: $1"; print_usage; echo "$TESTNAME FAIL" >"$RES_FILE"; exit 1 ;; esac shift done -# Helper: print planned media-ctl / yavta sequence for CI logs -print_planned_commands() { - media_node="$1" - pixfmt="$2" - - log_info "[CI] Planned sequence:" - log_info " media-ctl -d $media_node --reset" - - # media-ctl -V (show with possible TARGET_FORMAT substitution exactly like configure helper does) - if [ -n "$MEDIA_CTL_V_LIST" ]; then - printf '%s\n' "$MEDIA_CTL_V_LIST" | while IFS= read -r vline; do - [ -z "$vline" ] && continue - vline_out="$(printf '%s' "$vline" | sed -E "s/fmt:[^/]+\/([0-9]+x[0-9]+)/fmt:${pixfmt}\/\1/g")" - log_info " media-ctl -d $media_node -V '$vline_out'" - done - fi - # media-ctl -l - if [ -n "$MEDIA_CTL_L_LIST" ]; then - printf '%s\n' "$MEDIA_CTL_L_LIST" | while IFS= read -r lline; do - [ -z "$lline" ] && continue - log_info " media-ctl -d $media_node -l '$lline'" - done - fi - # yavta control writes (pre) - if [ -n "$YAVTA_CTRL_PRE_LIST" ]; then - printf '%s\n' "$YAVTA_CTRL_PRE_LIST" | while IFS= read -r ctrl; do - [ -z "$ctrl" ] && continue - dev="$(printf '%s' "$ctrl" | awk '{print $1}')" - reg="$(printf '%s' "$ctrl" | awk '{print $2}')" - val="$(printf '%s' "$ctrl" | awk '{print $3}')" - [ -n "$dev" ] && [ -n "$reg" ] && [ -n "$val" ] && \ - log_info " yavta --no-query -w '$reg $val' $dev" - done - fi - # main yavta capture (dimensions may be empty occasionally) - size_arg="" - if [ -n "$YAVTA_W" ] && [ -n "$YAVTA_H" ]; then - size_arg="-s ${YAVTA_W}x${YAVTA_H}" - fi - if [ -n "$YAVTA_DEV" ]; then - log_info " yavta -B capture-mplane -c -I -n $FRAMES -f $pixfmt $size_arg -F $YAVTA_DEV --capture=$FRAMES --file='frame-#.bin'" - fi - # yavta control writes (post) - if [ -n "$YAVTA_CTRL_POST_LIST" ]; then - printf '%s\n' "$YAVTA_CTRL_POST_LIST" | while IFS= read -r ctrl; do - [ -z "$ctrl" ] && continue - dev="$(printf '%s' "$ctrl" | awk '{print $1}')" - reg="$(printf '%s' "$ctrl" | awk '{print $2}')" - val="$(printf '%s' "$ctrl" | awk '{print $3}')" - [ -n "$dev" ] && [ -n "$reg" ] && [ -n "$val" ] && \ - log_info " yavta --no-query -w '$reg $val' $dev" - done - fi -} - # --------- DT Precheck --------- if ! dt_confirm_node_or_compatible "isp" "cam" "camss"; then log_skip "$TESTNAME SKIP – No ISP/camera node/compatible found in DT" echo "$TESTNAME SKIP" >"$RES_FILE" - exit 0 + exit 2 fi # --------- Kernel config sanity (MANDATORY bits only gate if totally absent) --------- @@ -150,7 +112,7 @@ elif printf '%s\n' "$DMESG_CACHE" | grep -qiE 'qcom[-_]camss'; then else log_skip "Camera_RDI_FrameCapture SKIP – CAMSS driver not present (module or built-in)" echo "$TESTNAME SKIP" >"$RES_FILE" - exit 0 + exit 2 fi # --------- Module inventory (visibility only; no gating) --------- @@ -193,14 +155,14 @@ DMESG_EXCLUDE='dummy regulator|supply [^ ]+ not found|using dummy regulator|Fail if scan_dmesg_errors "$SCRIPT_DIR" "$DMESG_MODULES" "$DMESG_EXCLUDE"; then log_skip "$TESTNAME SKIP – $DRIVER_MOD probe errors detected in dmesg" echo "$TESTNAME SKIP" >"$RES_FILE" - exit 0 + exit 2 fi # --------- Dependency Checks --------- check_dependencies media-ctl yavta python3 v4l2-ctl || { log_skip "$TESTNAME SKIP – Required tools missing" echo "$TESTNAME SKIP" >"$RES_FILE" - exit 0 + exit 2 } # --------- Media Node Detection --------- @@ -208,7 +170,7 @@ MEDIA_NODE="$(detect_media_node)" if [ -z "$MEDIA_NODE" ]; then log_skip "$TESTNAME SKIP – Media node not found" echo "$TESTNAME SKIP" >"$RES_FILE" - exit 0 + exit 2 fi log_info "Detected media node: $MEDIA_NODE" @@ -227,7 +189,7 @@ PYTHON_PIPELINES="$(run_camera_pipeline_parser "$TOPO_FILE")" if [ -z "$PYTHON_PIPELINES" ]; then log_skip "$TESTNAME SKIP – No valid pipelines found" echo "$TESTNAME SKIP" >"$RES_FILE" - exit 0 + exit 2 fi printf '%s\n' "$PYTHON_PIPELINES" >"$TMP_PIPELINES_FILE" @@ -287,6 +249,132 @@ while IFS= read -r line || [ -n "$line" ]; do RET=$? fi + ######################## Format/Resolution fallbacks ######################## + if [ "$RET" -ne 0 ]; then + if printf '%s' "$TARGET_FORMAT" | grep -q 'P$'; then + ALT_FMT_A="$(printf '%s' "$TARGET_FORMAT" | sed 's/P$//')" + + SAVE_V="$MEDIA_CTL_V_LIST"; SAVE_W="$YAVTA_W"; SAVE_H="$YAVTA_H" + MEDIA_CTL_V_LIST="$(printf '%s\n' "$MEDIA_CTL_V_LIST" | sed -E "s/fmt:[^/]+\//fmt:${ALT_FMT_A}\//g")" + + log_info "Applying format fallback (A1): $TARGET_FORMAT → $ALT_FMT_A" + print_planned_commands "$MEDIA_NODE" "$ALT_FMT_A" + configure_pipeline_block "$MEDIA_NODE" "$ALT_FMT_A" + execute_capture_block "$FRAMES" "$ALT_FMT_A" + RET=$? + + if [ "$RET" -ne 0 ] && [ -n "$SAVE_W" ] && [ -n "$SAVE_H" ]; then + NEW_W=$(( (SAVE_W/2)*2 )) + NEW_H=$(( (SAVE_H/2)*2 )) + MEDIA_CTL_V_LIST="$(printf '%s\n' "$MEDIA_CTL_V_LIST" | sed -E "s/([0-9]+x[0-9]+)/${NEW_W}x${NEW_H}/g")" + YAVTA_W="$NEW_W"; YAVTA_H="$NEW_H" + log_info "Applying resolution fallback (A2): ${SAVE_W}x${SAVE_H} → ${NEW_W}x${NEW_H} (format $ALT_FMT_A)" + print_planned_commands "$MEDIA_NODE" "$ALT_FMT_A" + configure_pipeline_block "$MEDIA_NODE" "$ALT_FMT_A" + execute_capture_block "$FRAMES" "$ALT_FMT_A" + RET=$? + fi + + MEDIA_CTL_V_LIST="$SAVE_V"; YAVTA_W="$SAVE_W"; YAVTA_H="$SAVE_H" + fi + fi + ###################### end ############################################### + + ######################## Try other RDI/Video indices ##################### + if [ "$RET" -ne 0 ]; then + CUR_RDI="$(printf '%s\n%s\n' "$MEDIA_CTL_V_LIST" "$MEDIA_CTL_L_LIST" \ + | sed -n 's/.*msm_vfe[0-9]_rdi\([0-9]\).*/\1/p' | head -n1)" + CUR_VIDIDX="$(printf '%s\n%s\n' "$MEDIA_CTL_V_LIST" "$MEDIA_CTL_L_LIST" \ + | sed -n 's/.*msm_vfe[0-9]_video\([0-9]\).*/\1/p' | head -n1)" + [ -z "$CUR_VIDIDX" ] && CUR_VIDIDX="$(printf '%s' "$YAVTA_DEV" | sed -n 's#.*/video\([0-9]\+\)$#\1#p')" + + if [ -n "$CUR_RDI" ] && [ -n "$CUR_VIDIDX" ]; then + for ALT_IDX in 0 1 2; do + [ "$ALT_IDX" = "$CUR_RDI" ] && continue + + SAVE_V="$MEDIA_CTL_V_LIST" + SAVE_L="$MEDIA_CTL_L_LIST" + SAVE_DEV="$YAVTA_DEV" + SAVE_W="$YAVTA_W" + SAVE_H="$YAVTA_H" + + MEDIA_CTL_V_LIST="$(printf '%s\n' "$MEDIA_CTL_V_LIST" \ + | sed -E "s/(msm_vfe[0-9]_rdi)[0-2]/\1${ALT_IDX}/g; s/(msm_vfe[0-9]_video)[0-2]/\1${ALT_IDX}/g")" + MEDIA_CTL_L_LIST="$(printf '%s\n' "$MEDIA_CTL_L_LIST" \ + | sed -E "s/(msm_vfe[0-9]_rdi)[0-2]/\1${ALT_IDX}/g; s/(msm_vfe[0-9]_video)[0-2]/\1${ALT_IDX}/g")" + + if printf '%s' "$YAVTA_DEV" | grep -qE '/dev/video[0-9]+$'; then + YAVTA_DEV="$(printf '%s' "$YAVTA_DEV" | sed -E "s#/dev/video[0-9]+#/dev/video${ALT_IDX}#")" + fi + + log_info "Applying path fallback (B1): switch to RDI/video index ${ALT_IDX} with format $TARGET_FORMAT" + print_planned_commands "$MEDIA_NODE" "$TARGET_FORMAT" + configure_pipeline_block "$MEDIA_NODE" "$TARGET_FORMAT" + execute_capture_block "$FRAMES" "$TARGET_FORMAT" + RET=$? + + if [ "$RET" -ne 0 ] && [ -n "$SAVE_W" ] && [ -n "$SAVE_H" ]; then + YAVTA_W=""; YAVTA_H="" + log_info "Retrying (B2) letting driver choose size on index ${ALT_IDX}" + print_planned_commands "$MEDIA_NODE" "$TARGET_FORMAT" + configure_pipeline_block "$MEDIA_NODE" "$TARGET_FORMAT" + execute_capture_block "$FRAMES" "$TARGET_FORMAT" + RET=$? + fi + + MEDIA_CTL_V_LIST="$SAVE_V" + MEDIA_CTL_L_LIST="$SAVE_L" + YAVTA_DEV="$SAVE_DEV" + YAVTA_W="$SAVE_W" + YAVTA_H="$SAVE_H" + + [ "$RET" -eq 0 ] && break + done + fi + fi + ###################### end ############################################### + + ############### Inline device-supported format fallback ################## + if [ "$RET" -ne 0 ]; then + SUP_FMTS="$(v4l2-ctl -d "$YAVTA_DEV" --list-formats 2>/dev/null \ + | sed -n "s/^[[:space:]]*'\([^']*\)'.*/\1/p")" + + if [ -n "$SUP_FMTS" ]; then + ALT_FMT_C="" + if printf '%s\n' "$SUP_FMTS" | grep -qx "$TARGET_FORMAT"; then + ALT_FMT_C="$TARGET_FORMAT" + elif printf '%s\n' "$TARGET_FORMAT" | grep -q 'P$' && \ + printf '%s\n' "$SUP_FMTS" | grep -qx "$(printf '%s' "$TARGET_FORMAT" | sed 's/P$//')"; then + ALT_FMT_C="$(printf '%s' "$TARGET_FORMAT" | sed 's/P$//')" + else + ALT_FMT_C="$(printf '%s\n' "$SUP_FMTS" | grep -E '^S[RGB]+[0-9]{2}P?$' | head -n1)" + [ -z "$ALT_FMT_C" ] && ALT_FMT_C="$(printf '%s\n' "$SUP_FMTS" | head -n1)" + fi + + if [ -n "$ALT_FMT_C" ]; then + SAVE_V="$MEDIA_CTL_V_LIST" + SAVE_W="$YAVTA_W" + SAVE_H="$YAVTA_H" + + MEDIA_CTL_V_LIST="$(printf '%s\n' "$MEDIA_CTL_V_LIST" \ + | sed -E "s/fmt:[^/]+\//fmt:${ALT_FMT_C}\//g")" + YAVTA_W="" + YAVTA_H="" + + log_info "Applying device-supported format fallback (C): $TARGET_FORMAT → $ALT_FMT_C (letting driver choose size)" + print_planned_commands "$MEDIA_NODE" "$ALT_FMT_C" + configure_pipeline_block "$MEDIA_NODE" "$ALT_FMT_C" + execute_capture_block "$FRAMES" "$ALT_FMT_C" + RET=$? + + MEDIA_CTL_V_LIST="$SAVE_V" + YAVTA_W="$SAVE_W" + YAVTA_H="$SAVE_H" + fi + fi + fi + ###################### end ############################################### + case "$RET" in 0) log_pass "$SENSOR $VIDEO $TARGET_FORMAT PASS"; PASS=$((PASS+1)) ;; 1) log_fail "$SENSOR $VIDEO $TARGET_FORMAT FAIL (capture failed)"; FAIL=$((FAIL+1)) ;; @@ -305,9 +393,11 @@ done < "$TMP_PIPELINES_FILE" log_info "Test Summary: Passed: $PASS, Failed: $FAIL, Skipped: $SKIP" if [ "$PASS" -gt 0 ]; then echo "$TESTNAME PASS" >"$RES_FILE" + exit 0 elif [ "$FAIL" -gt 0 ]; then echo "$TESTNAME FAIL" >"$RES_FILE" + exit 1 else echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 fi -exit 0 diff --git a/Runner/suites/Multimedia/Camera/Libcamera_cam/libcamera_test_README.md b/Runner/suites/Multimedia/Camera/Libcamera_cam/libcamera_test_README.md new file mode 100644 index 00000000..4925a1bd --- /dev/null +++ b/Runner/suites/Multimedia/Camera/Libcamera_cam/libcamera_test_README.md @@ -0,0 +1,170 @@ +# Libcamera Camera Test Runner + +This repository contains a **POSIX shell** test harness for exercising `libcamera` via its `cam` utility, with robust post‑capture validation and device‑tree (DT) checks. It is designed to run on embedded Linux targets (BusyBox-friendly), including Qualcomm RB platforms. + +--- + +## What this test does + +1. **Discovers repo context** (finds `init_env`, sources `functestlib.sh`, and the camera helpers `Runner/utils/camera/lib_camera.sh`). +2. **Checks DT readiness** using `dt_confirm_node_or_compatible` for: + - Sensor compatible(s), e.g. `sony,imx577` + - ISP / camera blocks, e.g. `isp`, `cam`, `camss` +3. **Lists available cameras** with `cam -l` (warning/error lines are tolerated when camera listing succeeds). +4. **Captures frames** with `cam` for one or multiple indices, storing artifacts per camera under `OUT_DIR`. +5. **Validates output**: sequence continuity, content sanity (PPM/BIN), duplicate detection, and log scanning with noise suppression. +6. **Summarizes per‑camera PASS/FAIL**, with overall suite verdict and exit code. + +--- + +## Requirements + +- `cam` (from libcamera) +- Standard tools: `awk`, `sed`, `grep`, `sort`, `cut`, `tr`, `wc`, `find`, `stat`, `head`, `tail`, `dd` +- Optional: `sha256sum` or `md5sum` (for duplicate BIN detection) +- **BusyBox compatibility**: + - We avoid `find -printf` and `od -A` options (not available on BusyBox). + +> The harness tolerates noisy `cam -l` / `cam -I` output (WARN/ERROR lines). It only requires that cameras and/or stream info are ultimately reported. + +--- + +## Quick start + +From the test directory (e.g. `Runner/suites/Multimedia/Camera/Libcamera_cam/`): + +```sh +./run.sh +``` + +Default behavior: +- Auto‑detect first camera index (`cam -l`). +- Capture **10 frames** per selected camera. +- Write outputs under `./cam_out/` (per‑camera subfolders `cam#`). +- Validate and print a summary. + +### Common options + +```text +--index N|all|n,m Camera index (default: auto from `cam -l`; `all` = run on every camera) +--count N Frames to capture (default: 10) +--out DIR Output directory (default: ./cam_out) +--ppm Save frames as PPM files (frame-#.ppm) +--bin Save frames as BIN files (default; frame-#.bin) +--args "STR" Extra args passed to `cam` +--strict Enforce strict validation (default) +--no-strict Relax validation (no seq/err strictness) +--dup-max-ratio R Fail if max duplicate bucket/total > R (default: 0.5) +--bin-tol-pct P BIN size tolerance vs bytesused in % (default: 5) +-h, --help Help +``` + +Examples: +```sh +# Run default capture (first detected camera, 10 frames) +./run.sh + +# Run on all cameras, 20 frames, save PPM +./run.sh --index all --count 20 --ppm + +# Run on cameras 0 and 2, pass explicit stream config to cam +./run.sh --index 0,2 --args "-s width=1920,height=1080,role=viewfinder" +``` + +--- + +## Device‑tree checks + +The runner verifies DT node presence **before** capture: + +- First it looks for known sensor compatibles (e.g. `sony,imx577`). +- If the sensor isn’t found, it looks for ISP / camera nodes (e.g. `isp`, `cam`, `camss`). +- Matching entries are printed cleanly (name, path, compatible). + +If neither sensor nor ISP/camera blocks are found, the test **SKIPs** with a message: +``` +SKIP – No ISP/camera node/compatible found in DT +``` + +> On large DTs, this scan can take time. The log prints “Verifying the availability of DT nodes, this process may take some time.” + +--- + +## IPA file workaround (simple pipeline) + +On some builds, allocation may fail if `uncalibrated.yaml` exists for the `simple` IPA. The runner guards this by **renaming** it pre‑run: + +```sh +if [ -f /usr/share/libcamera/ipa/simple/uncalibrated.yaml ]; then + mv /usr/share/libcamera/ipa/simple/uncalibrated.yaml \ + /usr/share/libcamera/ipa/simple/uncalibrated.yaml.bk +fi +``` + +It’s restored automatically at the end (if it was present). + +--- + +## Output & artifacts + +Per‑camera subfolder under `OUT_DIR`: +- `cam-run--camX.log` – raw cam output +- `cam-info--camX.log` – `cam -l` and `cam -I` info +- `frame-...` files (`.bin` or `.ppm`) – captured frames +- `.file_seq_map.txt`, `.bytesused.txt`, etc. – validation sidecar files +- `summary.txt` – per‑camera PASS/FAIL + +Console prints a **per‑camera** and **overall** summary. Exit codes: +- `0` PASS +- `1` FAIL +- `2` SKIP + +--- + +## Validation details + +- **Sequence integrity**: checks that frame sequence numbers are contiguous (unless `--no-strict`). +- **PPM sanity**: header/magic checks and basic content entropy (sampled). +- **BIN sanity**: size compared to `bytesused` (±`BIN_TOL_PCT`), entropy sample, duplicate detection via hashes. +- **Error scan**: scans `cam` logs for fatal indicators, then applies **noise suppression** to ignore known benign warnings from `simple` pipeline and sensors like `imx577`. + +You can relax strictness with `--no-strict` (skips contiguous sequence enforcement and strict error gating). + +--- + +## Multicamera behavior + +- `--index all`: detects all indices from `cam -l` and iterates. +- `--index 0,2,5`: runs each listed index. +- Each index is independently validated and reported: if **any** camera fails, the **overall** result is **FAIL**. The summary lists which indices passed/failed. + +--- + +## Environment overrides + +- `INIT_ENV`: If set, the runner uses it instead of walking upward to find `init_env`. +- `LIBCAM_PATH`: If set, the runner sources this path for `lib_camera.sh` helper functions. +- Otherwise, the runner searches typical repo locations: + - `Runner/utils/camera/lib_camera.sh` + - `Runner/utils/lib_camera.sh` + - `utils/camera/lib_camera.sh` + - `utils/lib_camera.sh` + +--- + +## Troubleshooting + +- **`cam -l` prints WARN/ERROR but lists cameras**: This is tolerated. The runner parses indices from the “Available cameras” section. +- **BusyBox `find`/`od` compatibility**: We avoid GNU-only flags; if you see issues, ensure BusyBox provides the required applets mentioned above. +- **No DT matches**: Ensure your DT exposes sensor compatibles (e.g. `sony,imx577`) or ISP/camera nodes (`isp`, `cam`, `camss`). On dev boards, DT overlays may need to be applied. +- **Content flagged “near‑constant”**: This typically indicates all-same bytes in sampled regions. Verify the lens cap, sensor mode, or try `--args` with a smaller resolution/role to confirm live changes. +- **IPA config missing**: See the **IPA file workaround** above. + +--- + +## Maintainers + +- Multimedia/Camera QA +- Platform Integration + +Please submit issues and PRs with logs from `cam-run-*.log`, `cam-info-*.log`, and `summary.txt`. diff --git a/Runner/suites/Multimedia/Camera/Libcamera_cam/run.sh b/Runner/suites/Multimedia/Camera/Libcamera_cam/run.sh new file mode 100755 index 00000000..d021c5a3 --- /dev/null +++ b/Runner/suites/Multimedia/Camera/Libcamera_cam/run.sh @@ -0,0 +1,304 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. +# SPDX-License-Identifier: BSD-3-Clause-Clear +# libcamera 'cam' runner with strong post-capture validation (CLI-config only) +# ---------- Repo env + helpers (single-pass) ---------- +SCRIPT_DIR=$(cd "$(dirname "$0")" || exit 1; pwd) +SEARCH="$SCRIPT_DIR" +INIT_ENV="${INIT_ENV:-}" +LIBCAM_PATH="${LIBCAM_PATH:-}" + +# Find init_env quickly (single upward walk) +while [ "$SEARCH" != "/" ] && [ -z "$INIT_ENV" ]; do + [ -f "$SEARCH/init_env" ] && INIT_ENV="$SEARCH/init_env" && break + SEARCH=${SEARCH%/*} +done + +if [ -z "$INIT_ENV" ]; then + printf '%s\n' "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" + +# Prefer direct repo-root locations for lib_camera.sh +if [ -z "$LIBCAM_PATH" ]; then + REPO_ROOT=$(dirname "$INIT_ENV") + for cand in \ + "$REPO_ROOT/Runner/utils/camera/lib_camera.sh" \ + "$REPO_ROOT/utils/camera/lib_camera.sh" + do + [ -f "$cand" ] && { LIBCAM_PATH="$cand"; break; } + done +fi + +# Fallback upward walk only if still not found +if [ -z "$LIBCAM_PATH" ]; then + SEARCH="$SCRIPT_DIR" + while [ "$SEARCH" != "/" ]; do + for cand in \ + "$SEARCH/Runner/utils/camera/lib_camera.sh" \ + "$SEARCH/utils/camera/lib_camera.sh" + do + [ -f "$cand" ] && { LIBCAM_PATH="$cand"; break 2; } + done + SEARCH=${SEARCH%/*} + done +fi + +if [ -z "$LIBCAM_PATH" ]; then + if command -v log_error >/dev/null 2>&1; then + log_error "lib_camera.sh not found (searched under Runner/utils/camera and utils/camera)" + else + printf '%s\n' "ERROR: lib_camera.sh not found (searched under Runner/utils/camera and utils/camera)" >&2 + fi + exit 1 +fi + +# shellcheck source=../../../../utils/camera/lib_camera.sh disable=SC1091 +. "$LIBCAM_PATH" + +TESTNAME="Libcamera_cam" +RES_FILE="./${TESTNAME}.res" +: > "$RES_FILE" + +# ---------- Defaults (override via CLI only) ---------- +CAM_INDEX="auto" # --index |all|n,m,k ; auto = first from `cam -l` +CAPTURE_COUNT="10" # --count +OUT_DIR="./cam_out" # --out +SAVE_AS_PPM="no" # --ppm | --bin +CAM_EXTRA_ARGS="" # --args "" + +# Validation knobs +SEQ_STRICT="yes" # --no-strict to relax +ERR_STRICT="yes" # --no-strict to relax +DUP_MAX_RATIO="0.5" # --dup-max-ratio <0..1> +BIN_TOL_PCT="5" # --bin-tol-pct +PPM_SAMPLE_BYTES="65536" +BIN_SAMPLE_BYTES="65536" + +print_usage() { + cat < R (default: 0.5) + --bin-tol-pct P BIN size tolerance vs bytesused in % (default: 5) + -h, --help Show this help +EOF +} + +# ---------- CLI ---------- +while [ $# -gt 0 ]; do + case "$1" in + --index) shift; CAM_INDEX="$1" ;; + --count) shift; CAPTURE_COUNT="$1" ;; + --out) shift; OUT_DIR="$1" ;; + --ppm) SAVE_AS_PPM="yes" ;; + --bin) SAVE_AS_PPM="no" ;; + --args) shift; CAM_EXTRA_ARGS="$1" ;; + --strict) SEQ_STRICT="yes"; ERR_STRICT="yes" ;; + --no-strict) SEQ_STRICT="no"; ERR_STRICT="no" ;; + --dup-max-ratio) shift; DUP_MAX_RATIO="$1" ;; + --bin-tol-pct) shift; BIN_TOL_PCT="$1" ;; + -h|--help) print_usage; exit 0 ;; + *) log_warn "Unknown option: $1"; print_usage; exit 2 ;; + esac + shift +done + +# ---------- DT / platform readiness ---------- +# Print both sensor and CAMSS/ISP matches if they exist; skip only if neither is present. +log_info "Verifying the availability of DT nodes, this process may take some time." + +PATTERNS="sony,imx577 imx577 isp cam camss" +found_any=0 +missing_list="" + +for pat in $PATTERNS; do + out="$(dt_confirm_node_or_compatible "$pat" 2>/dev/null || true)" + if [ -n "$out" ]; then + printf '%s\n' "$out" + found_any=1 + else + [ -n "$missing_list" ] && missing_list="$missing_list, $pat" || missing_list="$pat" + fi +done + +if [ "$found_any" -eq 1 ]; then + log_info "DT nodes present (see matches above)." +else + log_skip "$TESTNAME SKIP – missing DT patterns: $missing_list" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + +# ---------- Dependencies ---------- +log_info "Reviewing the dependencies needed to run the cam test." +check_dependencies cam || { + log_error "cam utility not found" + echo "$TESTNAME FAIL" > "$RES_FILE" + exit 1 +} +# nice-to-haves for validation +check_dependencies awk sed grep sort cut tr wc find stat head tail dd || true +(check_dependencies sha256sum || check_dependencies md5sum) || true + +# ---------- Setup ---------- +mkdir -p "$OUT_DIR" 2>/dev/null || true +RUN_TS="$(date -u +%Y%m%d-%H%M%S)" + +log_info "Test: $TESTNAME" +log_info "OUT_DIR=$OUT_DIR | COUNT=$CAPTURE_COUNT | SAVE_AS_PPM=$SAVE_AS_PPM" +log_info "Extra args: ${CAM_EXTRA_ARGS:-}" + +# ---- IPA workaround: disable simple/uncalibrated to avoid buffer allocation failures ---- +UNCALIB="/usr/share/libcamera/ipa/simple/uncalibrated.yaml" +if [ -f "$UNCALIB" ]; then + log_info "Renaming $UNCALIB -> ${UNCALIB}.bk to avoid IPA buffer allocation issues" + mv "$UNCALIB" "${UNCALIB}.bk" 2>/dev/null || log_warn "Failed to rename $UNCALIB (continuing)" +elif [ -f "${UNCALIB}.bk" ]; then + # Already renamed in a previous run; just log for clarity + log_info "IPA workaround already applied: ${UNCALIB}.bk present" +fi + +# ---------- Sensor presence ---------- +SENSOR_COUNT="$(libcam_list_sensors_count 2>/dev/null)" +# harden: ensure numeric +case "$SENSOR_COUNT" in ''|*[!0-9]*) SENSOR_COUNT=0 ;; esac +log_info "[cam -l] detected ${SENSOR_COUNT} camera(s)" + +if [ "$SENSOR_COUNT" -lt 1 ]; then + log_skip "No sensors reported by 'cam -l' - marking SKIP" + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 0 +fi + + +# ---------- Resolve indices (supports: auto | all | 0,2,5) ---------- +INDICES="$(libcam_resolve_indices "$CAM_INDEX")" +if [ -z "$INDICES" ]; then + log_skip "No valid camera indices resolved (cam -l empty?) - SKIP" + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 0 +fi +log_info "Resolved indices: $INDICES" + +OVERALL_PASS=1 +ANY_RC_NONZERO=0 +PASS_LIST="" +FAIL_LIST="" +: > "$OUT_DIR/summary.txt" + +for IDX in $INDICES; do + # Per-camera logs & output dir + CAM_DIR="${OUT_DIR%/}/cam${IDX}" + mkdir -p "$CAM_DIR" 2>/dev/null || true + RUN_LOG="${CAM_DIR%/}/cam-run-${RUN_TS}-cam${IDX}.log" + INFO_LOG="${CAM_DIR%/}/cam-info-${RUN_TS}-cam${IDX}.log" + + log_info "---- Camera idx: $IDX ----" + { + echo "== cam -l ==" + cam -l || true + echo + echo "== cam -I (index $IDX) ==" + cam -c "$IDX" -I || true + } >"$INFO_LOG" 2>&1 + + # Capture + FILE_TARGET="$CAM_DIR/" + [ "$SAVE_AS_PPM" = "yes" ] && FILE_TARGET="$CAM_DIR/frame-#.ppm" + + log_info "cmd:" + log_info " cam -c $IDX --capture=$CAPTURE_COUNT \\" + log_info " --file=\"$FILE_TARGET\" ${CAM_EXTRA_ARGS:+\\}" + [ -n "$CAM_EXTRA_ARGS" ] && log_info " $CAM_EXTRA_ARGS" + + # shellcheck disable=SC2086 + ( cam -c "$IDX" --capture="$CAPTURE_COUNT" --file="$FILE_TARGET" $CAM_EXTRA_ARGS ) \ + >"$RUN_LOG" 2>&1 + RC=$? + + tail -n 50 "$RUN_LOG" | sed "s/^/[cam idx $IDX] /" + + # Per-camera validation + BIN_COUNT=$(find "$CAM_DIR" -maxdepth 1 -type f -name 'frame-*.bin' | wc -l | tr -d ' ') + PPM_COUNT=$(find "$CAM_DIR" -maxdepth 1 -type f -name 'frame-*.ppm' | wc -l | tr -d ' ') + TOTAL=$((BIN_COUNT + PPM_COUNT)) + log_info "[idx $IDX] Produced files: bin=$BIN_COUNT ppm=$PPM_COUNT total=$TOTAL (requested $CAPTURE_COUNT)" + + PASS=1 + [ "$TOTAL" -ge "$CAPTURE_COUNT" ] || { log_warn "[idx $IDX] Fewer files than requested"; PASS=0; } + + SEQ_REPORT="$(libcam_log_seqs "$RUN_LOG" | wc -l | tr -d ' ')" + [ "$SEQ_REPORT" -ge "$CAPTURE_COUNT" ] || { log_warn "[idx $IDX] cam log shows fewer seq lines ($SEQ_REPORT) than requested ($CAPTURE_COUNT)"; PASS=0; } + + if [ "$SEQ_STRICT" = "yes" ]; then + CSUM="$(libcam_log_seqs "$RUN_LOG" | libcam_check_contiguous 2>&1)" + echo "$CSUM" | sed 's/^/[seq] /' + echo "$CSUM" | grep -q 'MISSING=0' || { log_warn "[idx $IDX] non-contiguous sequences in log"; PASS=0; } + fi + + libcam_files_and_seq "$CAM_DIR" "$SEQ_STRICT" || PASS=0 + libcam_validate_content "$CAM_DIR" "$RUN_LOG" "$PPM_SAMPLE_BYTES" "$BIN_SAMPLE_BYTES" "$BIN_TOL_PCT" "$DUP_MAX_RATIO" || PASS=0 + libcam_scan_errors "$RUN_LOG" "$ERR_STRICT" || PASS=0 + + [ $RC -eq 0 ] || { ANY_RC_NONZERO=1; PASS=0; } + + if [ "$PASS" -eq 1 ]; then + log_pass "[idx $IDX] PASS" + PASS_LIST="$PASS_LIST $IDX" + echo "cam$IDX PASS" >> "$OUT_DIR/summary.txt" + else + log_fail "[idx $IDX] FAIL" + FAIL_LIST="$FAIL_LIST $IDX" + echo "cam$IDX FAIL" >> "$OUT_DIR/summary.txt" + OVERALL_PASS=0 + fi +done + +# ---------- Per-camera summary (always printed) ---------- +pass_trim="$(printf '%s' "$PASS_LIST" | sed 's/^ //')" +fail_trim="$(printf '%s' "$FAIL_LIST" | sed 's/^ //')" +log_info "---------- Per-camera summary ----------" +if [ -n "$pass_trim" ]; then + log_info "PASS: $pass_trim" +else + log_info "PASS: (none)" +fi +if [ -n "$fail_trim" ]; then + log_info "FAIL: $fail_trim" +else + log_info "FAIL: (none)" +fi +log_info "Summary file: $OUT_DIR/summary.txt" + +# ---------- Final verdict ---------- +if [ "$OVERALL_PASS" -eq 1 ] && [ $ANY_RC_NONZERO -eq 0 ]; then + echo "$TESTNAME PASS" > "$RES_FILE" + log_pass "$TESTNAME PASS" + exit 0 +else + echo "$TESTNAME FAIL" > "$RES_FILE" + log_fail "$TESTNAME FAIL" + exit 1 +fi + +# ---------- Artifacts ---------- +log_info "Artifacts under: $OUT_DIR/" +for IDX in $INDICES; do + CAM_DIR="${OUT_DIR%/}/cam${IDX}" + log_info " - $CAM_DIR/" +done diff --git a/Runner/suites/Multimedia/Display/igt_gpu_tools/core_auth/Display_IGT_Core_Auth_TestValidation_Readme.md b/Runner/suites/Multimedia/Display/igt_gpu_tools/core_auth/Display_IGT_Core_Auth_TestValidation_Readme.md new file mode 100644 index 00000000..00a1d86d --- /dev/null +++ b/Runner/suites/Multimedia/Display/igt_gpu_tools/core_auth/Display_IGT_Core_Auth_TestValidation_Readme.md @@ -0,0 +1,149 @@ +# License +Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +SPDX-License-Identifier: BSD-3-Clause-Clear + +# IGT Core Auth Test Script + +## Overview + +This script automates the validation of authentication mechanisms within the IGT Core framework. It performs a series of tests to ensure that the authentication processes are functioning correctly and securely. The script captures detailed logs and provides a comprehensive summary of the test results with subtest-level analysis. + +## Features + +- Comprehensive IGT authentication tests +- Environment setup for required dependencies +- Detailed logging of test processes and results +- Color-coded pass/fail summaries +- Output stored in structured results directory +- Auto-check for required libraries and dependencies +- Compatible with various Linux distributions +- Help system with usage information + +## Prerequisites + +Ensure the following components are present in the target environment: + +- Core authentication binary (core_auth) - must be executable +- Required authentication libraries and dependencies +- Write access to the filesystem (for environment setup and logging) + +## Directory Structure +```bash +Runner/ +├──suites/ +├ ├── Multimedia/ +│ ├ ├── Display/ +│ ├ ├ ├── igt_gpu_tools/ +│ ├ ├ ├ ├── core_auth/ +│ ├ ├ ├ ├ ├── run.sh +├ ├ ├ ├ ├ └── Display_IGT_Core_Auth_TestValidation_Readme.md +``` + +## Usage + +1. Copy the script to your target system and make it executable: + +```bash +chmod +x run.sh +``` + +2. Run the script using one of the following methods: + +**Method 1: Positional argument** +```bash +./run.sh +``` + +**Method 2: Named argument** +```bash +./run.sh --core-auth-path +``` + +**Method 3: Get help** +```bash +./run.sh --help +./run.sh -h +``` + +Examples: +```bash +./run.sh /usr/libexec/igt-gpu-tools/core_auth +./run.sh --core-auth-path /usr/libexec/igt-gpu-tools/core_auth +``` + +3. Logs and test results will be available in the test directory: + - `core_auth_log.txt` - Detailed test execution log with subtest results + - `core_auth.res` - Test result (PASS/FAIL/SKIP) + +## Output + +- **Console Output**: Real-time display of test execution and results with structured logging +- **Log File**: `core_auth_log.txt` - Contains detailed output from the core_auth binary including all subtests +- **Result File**: `core_auth.res` - Contains final test status (PASS/FAIL/SKIP) +- **Subtest Analysis**: Detailed parsing and counting of individual subtest results +- **Test Status Determination**: + - **FAIL**: Return code ≠ 0 OR any subtest failed (fail_count > 0) + - **SKIP**: Return code = 0 AND all subtests skipped (skip_count > 0, success_count = 0) + - **PASS**: Return code = 0 AND no failures (success_count > 0 OR mixed results with no failures) + +## Notes + +- The script requires the path to the core_auth binary (via positional or named argument). +- Enhanced argument validation includes checking for missing values in named parameters. +- Comprehensive validation ensures the core_auth binary exists and is executable. +- Automatic Weston compositor management using the `weston_stop` function from functestlib.sh. +- Built-in help system provides usage information with improved syntax display. +- Robust error handling with appropriate exit codes and result file generation. +- Test results are determined by both return codes and log content analysis. + +## Maintenance + +- Ensure the authentication libraries remain compatible with your system. +- Update test cases as per new authentication requirements or updates in the IGT Core framework. + +## Run test using: +```bash +git clone +cd +scp -r Runner user@target_device_ip: +ssh user@target_device_ip +``` + +- **Using Unified Runner** +```bash +cd /Runner +``` + +- **Run Core_auth testcase** +```bash +./run-test.sh core_auth +``` + +Example: +```bash +./run-test.sh core_auth /usr/libexec/igt-gpu-tools/core_auth +``` + +## EXAMPLE OUTPUT + +[Executing test case: core_auth] 2025-05-31 17:39:52 - +[INFO] 2025-05-31 17:39:52 - -------------------Starting core_auth Testcase----------------------------- +[INFO] 2025-05-31 17:39:52 - Using core_auth binary at: /usr/libexec/igt-gpu-tools/core_auth +[INFO] 2025-05-31 17:39:52 - Weston is not running. +[INFO] 2025-05-31 17:39:52 - Logs written to: /var/qcom-linux-testkit/Runner/suites/Multimedia/Display/core_auth/core_auth_log.txt +[INFO] 2025-05-31 17:39:52 - Subtest Results: SUCCESS=4, FAIL=0, SKIP=0, TOTAL=4 +[INFO] 2025-05-31 17:39:52 - results will be written to "./core_auth.res" +[INFO] 2025-05-31 17:39:52 - -------------------Completed core_auth Testcase---------------------------- +[PASS] 2025-05-31 17:39:52 - core_auth : Test Passed - All 4 subtest(s) succeeded +[PASS] 2025-05-31 17:39:52 - core_auth passed + +[INFO] 2025-05-31 17:39:52 - ========== Test Summary ========== +PASSED: +core_auth + +FAILED: + None + +SKIPPED: + None +[INFO] 2025-05-31 17:39:52 - ================================= diff --git a/Runner/suites/Multimedia/Display/igt_gpu_tools/core_auth/run.sh b/Runner/suites/Multimedia/Display/igt_gpu_tools/core_auth/run.sh new file mode 100755 index 00000000..3934737b --- /dev/null +++ b/Runner/suites/Multimedia/Display/igt_gpu_tools/core_auth/run.sh @@ -0,0 +1,150 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause-Clear + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# ---- Source init_env & tools ---- +INIT_ENV="" +SEARCH="$SCRIPT_DIR" +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then INIT_ENV="$SEARCH/init_env"; break; fi + SEARCH="$(dirname "$SEARCH")" +done +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# Only source if not already loaded (idempotent) +if [ -z "$__INIT_ENV_LOADED" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" +fi +# Always source functestlib.sh, using $TOOLS exported by init_env +# shellcheck disable=SC1090,SC1091 +. "$TOOLS/functestlib.sh" + +TESTNAME="core_auth" +result_file="./${TESTNAME}.res" +CORE_AUTH_CMD="" + +test_path="$(find_test_case_by_name "$TESTNAME" 2>/dev/null)" +if [ -z "$test_path" ] || [ ! -d "$test_path" ]; then + test_path="$SCRIPT_DIR" +fi + +if [ ! -w "$test_path" ]; then + log_error "Cannot write to test directory: $test_path" + echo "$TESTNAME FAIL" >"$result_file" + exit 1 +fi + +while [ $# -gt 0 ]; do + case "$1" in + --core-auth-path) + shift + if [ -n "${1:-}" ]; then + CORE_AUTH_CMD="$1" + else + log_error "Missing value for --core-auth-path parameter" + echo "$TESTNAME FAIL" >"$result_file" + exit 1 + fi + ;; + --help|-h) + log_info "Usage: $0 [--core-auth-path BINARY_PATH] | [BINARY_PATH]" + exit 0 + ;; + -*) + log_warn "Unknown argument: $1" + ;; + *) + if [ -z "$CORE_AUTH_CMD" ]; then + CORE_AUTH_CMD="$1" + else + log_warn "Multiple paths specified, ignoring: $1" + fi + ;; + esac + shift +done + +if ! cd "$test_path"; then + log_error "cd failed: $test_path" + echo "$TESTNAME FAIL" >"$result_file" + exit 1 +fi + +log_info "-------------------Starting $TESTNAME Testcase-----------------------------" + +if [ -z "$CORE_AUTH_CMD" ]; then + log_error "core_auth binary not specified" + echo "$TESTNAME FAIL" > "$result_file" + exit 1 +fi + +if [ ! -x "$CORE_AUTH_CMD" ]; then + log_error "FAIL: core_auth binary not found or not executable at: $CORE_AUTH_CMD" + echo "$TESTNAME FAIL" > "$result_file" + exit 1 +fi + +log_info "Using core_auth binary at: $CORE_AUTH_CMD" + +if ! weston_stop; then + log_error "Failed to stop Weston" + echo "$TESTNAME FAIL" > "$result_file" + exit 1 +fi + +log_file="$test_path/core_auth_log.txt" +"$CORE_AUTH_CMD" > "$log_file" 2>&1 +RC="$?" + +log_info "Logs written to: $log_file" + +success_count=$(grep -c "SUCCESS" "$log_file" 2>/dev/null || echo "0") +fail_count=$(grep -c "FAIL" "$log_file" 2>/dev/null || echo "0") +skip_count=$(grep -c "SKIP" "$log_file" 2>/dev/null || echo "0") + +# Ensure we have valid numbers +success_count=${success_count:-0} +fail_count=${fail_count:-0} +skip_count=${skip_count:-0} + +case "$success_count" in ''|*[!0-9]*) success_count=0 ;; esac +case "$fail_count" in ''|*[!0-9]*) fail_count=0 ;; esac +case "$skip_count" in ''|*[!0-9]*) skip_count=0 ;; esac + +total_subtests=$((success_count + fail_count + skip_count)) + +log_info "Subtest Results: SUCCESS=$success_count, FAIL=$fail_count, SKIP=$skip_count, TOTAL=$total_subtests" +log_info "results will be written to \"$result_file\"" +log_info "-------------------Completed $TESTNAME Testcase----------------------------" + +if [ "$RC" -ne 0 ]; then + log_fail "$TESTNAME : Test Failed (exit code: $RC)" + echo "$TESTNAME FAIL" > "$result_file" + exit 1 +elif [ "$fail_count" -gt 0 ]; then + log_fail "$TESTNAME : Test Failed - $fail_count subtest(s) failed out of $total_subtests" + echo "$TESTNAME FAIL" > "$result_file" + exit 1 +elif [ "$skip_count" -gt 0 ] && [ "$success_count" -eq 0 ]; then + log_skip "$TESTNAME : Test Skipped - All $skip_count subtest(s) were skipped" + echo "$TESTNAME SKIP" > "$result_file" + exit 0 +else + if [ "$success_count" -gt 0 ]; then + if [ "$skip_count" -gt 0 ]; then + log_pass "$TESTNAME : Test Passed - $success_count subtest(s) succeeded, $skip_count skipped" + else + log_pass "$TESTNAME : Test Passed - All $success_count subtest(s) succeeded" + fi + else + log_pass "$TESTNAME : Test Passed (return code $RC)" + fi + echo "$TESTNAME PASS" > "$result_file" + exit 0 +fi diff --git a/Runner/suites/Multimedia/Graphics/weston-simple-egl/run.sh b/Runner/suites/Multimedia/Graphics/weston-simple-egl/run.sh index 9107651c..3d30c633 100755 --- a/Runner/suites/Multimedia/Graphics/weston-simple-egl/run.sh +++ b/Runner/suites/Multimedia/Graphics/weston-simple-egl/run.sh @@ -1,108 +1,128 @@ #!/bin/sh # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. # SPDX-License-Identifier: BSD-3-Clause-Clear +# # Validate weston-simple-egl runs under a working Wayland session. -# - Robust Wayland env resolution (adopts socket & fixes XDG_RUNTIME_DIR perms) -# - CI-friendly logs and PASS/FAIL semantics -# - Optional FPS parsing if build prints it (lenient if not present) +# - Wayland env resolution (adopts socket & fixes XDG_RUNTIME_DIR perms) +# - CI-friendly logs and PASS/FAIL/SKIP semantics (0/1/2) +# - Optional FPS parsing (best-effort) # ---------- Source init_env and functestlib ---------- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" INIT_ENV="" SEARCH="$SCRIPT_DIR" + while [ "$SEARCH" != "/" ]; do - if [ -f "$SEARCH/init_env" ]; then INIT_ENV="$SEARCH/init_env"; break; fi + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi SEARCH=$(dirname "$SEARCH") done + if [ -z "$INIT_ENV" ]; then echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 exit 1 fi -if [ -z "$__INIT_ENV_LOADED" ]; then + +if [ -z "${__INIT_ENV_LOADED:-}" ]; then # shellcheck disable=SC1090 . "$INIT_ENV" + __INIT_ENV_LOADED=1 fi + # shellcheck disable=SC1090,SC1091 . "$TOOLS/functestlib.sh" TESTNAME="weston-simple-egl" -# Tunables (env override) + +# ---------- Tunables (env override) ---------- DURATION="${DURATION:-30s}" # how long to run the client -STOP_GRACE="${STOP_GRACE:-3s}" # grace period on stop -EXPECT_FPS="${EXPECT_FPS:-60}" # nominal refresh (used for logging) +STOP_GRACE="${STOP_GRACE:-3s}" # grace period on stop (reserved for future) +EXPECT_FPS="${EXPECT_FPS:-60}" # nominal refresh (used for logs) FPS_TOL_PCT="${FPS_TOL_PCT:-10}" # +/- tolerance % REQUIRE_FPS="${REQUIRE_FPS:-0}" # 1=require FPS lines & threshold; 0=best effort +# ---------- Paths / logs ---------- test_path="$(find_test_case_by_name "$TESTNAME" 2>/dev/null || echo "$SCRIPT_DIR")" -cd "$test_path" || exit 1 +if ! cd "$test_path"; then + log_error "cd failed: $test_path" + exit 1 +fi + RES_FILE="./$TESTNAME.res" LOG_FILE="./${TESTNAME}_run.log" rm -f "$RES_FILE" "$LOG_FILE" log_info "--------------------------------------------------------------------------" log_info "------------------- Starting $TESTNAME Testcase --------------------------" -# FIX #1: Use ASCII '+/-' and keep it in a normal string + +# --- Platform details (robust logging; prefer helpers) --- +if command -v detect_platform >/dev/null 2>&1; then + detect_platform >/dev/null 2>&1 || true + log_info "Platform Details: machine='${PLATFORM_MACHINE:-unknown}' target='${PLATFORM_TARGET:-unknown}' kernel='${PLATFORM_KERNEL:-}' arch='${PLATFORM_ARCH:-}'" +else + log_info "Platform Details: unknown" +fi + log_info "Config: DURATION=$DURATION STOP_GRACE=$STOP_GRACE EXPECT_FPS=${EXPECT_FPS}+/-${FPS_TOL_PCT}% REQUIRE_FPS=$REQUIRE_FPS" -# Dependencies -check_dependencies weston-simple-egl || { - log_fail "$TESTNAME : weston-simple-egl binary not found in PATH" +# ---------- Dependencies ---------- +if ! check_dependencies weston-simple-egl; then + log_skip "$TESTNAME : SKIP (weston-simple-egl not found in PATH)" echo "$TESTNAME SKIP" > "$RES_FILE" - exit 0 -} + exit 2 +fi + BIN="$(command -v weston-simple-egl 2>/dev/null || true)" log_info "Using weston-simple-egl: ${BIN:-}" -# Resolve Wayland socket: -# 1) If current env points to a real socket, use it. -sock="" -if [ -n "$XDG_RUNTIME_DIR" ] && [ -n "$WAYLAND_DISPLAY" ] && [ -e "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]; then - sock="$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" -fi +# ----- Display presence check (DP/HDMI/etc.) ----- +# Quick snapshot for debugging (lists DRM nodes, sysfs connectors, weston outputs) +display_debug_snapshot "pre-display-check" + +have_connector=0 -# 2) Otherwise, scan common locations. -if [ -z "$sock" ]; then - for s in $(find_wayland_sockets); do - if [ -e "$s" ]; then sock="$s"; break; fi - done +# sysfs-based summary (existing helper) +sysfs_summary="$(display_connected_summary 2>/dev/null || printf '%s' '')" +if [ -n "$sysfs_summary" ] && [ "$sysfs_summary" != "none" ]; then + have_connector=1 + log_info "Connected display (sysfs): $sysfs_summary" fi -# 3) If still no socket, try to start Weston and wait a bit. -if [ -z "$sock" ]; then - if weston_is_running; then - log_warn "Weston running but no Wayland socket visible; attempting to continue." - else - log_info "Weston not running. Attempting to start..." - weston_start - fi - # Wait for socket to appear (up to ~5s) - n=0 - while [ $n -lt 5 ] && [ -z "$sock" ]; do - for s in $(find_wayland_sockets); do - if [ -e "$s" ]; then sock="$s"; break; fi - done - [ -n "$sock" ] && break - sleep 1 - n=$((n+1)) - done +if [ "$have_connector" -eq 0 ]; then + log_skip "$TESTNAME : SKIP (no connected display detected)" + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 2 fi -if [ -z "$sock" ]; then +wayland_debug_snapshot "weston-simple-egl: start" + +# ---------- Choose/adopt Wayland socket (using helper) ---------- +# Capture only the actual socket path from helper output (filter out logs) +sock="$( + wayland_choose_or_start 2>/dev/null \ + | grep -E '/(run/user/[0-9]+|tmp|dev/socket/weston)/wayland-[0-9]+$' \ + | tail -n 1 +)" +if [ -n "$sock" ]; then + log_info "Found Wayland socket: $sock" +else log_fail "$TESTNAME : FAIL (no Wayland socket found after attempting to start Weston)" echo "$TESTNAME FAIL" > "$RES_FILE" + wayland_debug_snapshot "weston-simple-egl: no-socket" exit 1 fi -# Adopt env and fix runtime dir perms (done inside helper(s)) -adopt_wayland_env_from_socket "$sock" - -# FIX #2: Avoid duplicate prints — helpers can log the chosen socket/env once. -# If your helpers are quiet instead, uncomment the two lines below: -# log_info "Selected Wayland socket: $sock" -# log_info "Wayland env: XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR WAYLAND_DISPLAY=$WAYLAND_DISPLAY" +adopt_wayland_env_from_socket "$sock" || log_warn "adopt_wayland_env_from_socket: invalid: $sock" +log_info "Final Wayland env: XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-}WAYLAND_DISPLAY=${WAYLAND_DISPLAY:-}" +# Replace to avoid confusion in CI logs: +# shellcheck disable=SC2016 +printf '%s\n' "" | sed 's/.*/[DBG] (env adopted)/' >/dev/null 2>&1 || true +# ---------- Sanity check Wayland connectivity ---------- if wayland_connection_ok; then - log_info "Wayland connection test: OK (wayland-info/env)" + log_info "Wayland connection test: OK" else log_fail "$TESTNAME : FAIL (Wayland connection test failed)" print_path_meta "$XDG_RUNTIME_DIR" | sed 's/^/[DBG] /' @@ -111,22 +131,25 @@ else exit 1 fi -# Try to enable FPS prints if supported by the client build (best effort). +# Try to enable FPS prints if supported by the client (best effort). export SIMPLE_EGL_FPS=1 export WESTON_SIMPLE_EGL_FPS=1 -# Run the client for DURATION with a stopwatch for CI logs. -log_info "Running weston-simple-egl for $DURATION ..." +# ---------- Run the client ---------- +log_info "Launching weston-simple-egl for $DURATION …" start_ts="$(date +%s 2>/dev/null || echo 0)" + if command -v run_with_timeout >/dev/null 2>&1; then + log_info "Using helper: run_with_timeout" run_with_timeout "$DURATION" weston-simple-egl >"$LOG_FILE" 2>&1 rc=$? else if command -v timeout >/dev/null 2>&1; then + log_info "Using coreutils timeout" timeout "$DURATION" weston-simple-egl >"$LOG_FILE" 2>&1 rc=$? else - # Last resort: background and sleep. + log_info "No timeout helpers; running in background with manual sleep-stop" sh -c 'weston-simple-egl' >"$LOG_FILE" 2>&1 & pid=$! # DURATION like "30s" → "30" @@ -138,24 +161,23 @@ else rc=143 fi fi + end_ts="$(date +%s 2>/dev/null || echo 0)" elapsed=$(( end_ts - start_ts )) [ "$elapsed" -lt 0 ] && elapsed=0 +log_info "Client finished: rc=$rc elapsed=${elapsed}s" -# FPS parsing (best effort) +# ---------- FPS parsing (best effort) ---------- fps="-" fps_line="$(grep -E '([Ff][Pp][Ss]|frames per second|^fps:)' "$LOG_FILE" 2>/dev/null | tail -n 1)" if [ -n "$fps_line" ]; then - # Extract last numeric token (integer or float) - fps="$(printf '%s\n' "$fps_line" | awk '{ - for (i=NF;i>=1;i--) if ($i ~ /^[0-9]+(\.[0-9]+)?$/) {print $i; exit} - }')" + fps="$(printf '%s\n' "$fps_line" | awk '{ for (i=NF;i>=1;i--) if ($i ~ /^[0-9]+(\.[0-9]+)?$/) {print $i; exit} }')" [ -z "$fps" ] && fps="-" fi -# CI debugging summary log_info "Result summary: rc=$rc elapsed=${elapsed}s fps=${fps} (expected ~${EXPECT_FPS}+/-${FPS_TOL_PCT}%)" -# Quick duration gate: must have run at least (DURATION-1) seconds to be considered OK. + +# ---------- Gating ---------- dur_s="$(printf '%s' "$DURATION" | sed -n 's/^\([0-9][0-9]*\)s$/\1/p')" [ -z "$dur_s" ] && dur_s="$DURATION" min_ok=$(( dur_s - 1 )) @@ -163,6 +185,7 @@ min_ok=$(( dur_s - 1 )) final="PASS" +# Must have run ~DURATION seconds if [ "$elapsed" -lt "$min_ok" ]; then final="FAIL" log_fail "$TESTNAME : FAIL (exited after ${elapsed}s; expected ~${dur_s}s) — rc=$rc" @@ -174,7 +197,6 @@ if [ "$final" = "PASS" ] && [ "$REQUIRE_FPS" -eq 1 ]; then final="FAIL" log_fail "$TESTNAME : FAIL (no FPS lines found but REQUIRE_FPS=1)" else - # Integer-only tolerance check (portable) lo=$(( (EXPECT_FPS * (100 - FPS_TOL_PCT)) / 100 )) hi=$(( (EXPECT_FPS * (100 + FPS_TOL_PCT)) / 100 )) fps_int="$(printf '%s' "$fps" | cut -d. -f1)" @@ -185,13 +207,21 @@ if [ "$final" = "PASS" ] && [ "$REQUIRE_FPS" -eq 1 ]; then fi fi +# ---------- Epilogue / exit codes ---------- case "$final" in PASS) log_pass "$TESTNAME : PASS" echo "$TESTNAME PASS" > "$RES_FILE" exit 0 ;; + SKIP) + # (Not used here, but keeping consistent mapping) + log_skip "$TESTNAME : SKIP" + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 2 + ;; *) + log_fail "$TESTNAME : FAIL" echo "$TESTNAME FAIL" > "$RES_FILE" exit 1 ;; diff --git a/Runner/suites/Multimedia/OpenCV/opencv_suite_README.md b/Runner/suites/Multimedia/OpenCV/opencv_suite_README.md new file mode 100644 index 00000000..c395555b --- /dev/null +++ b/Runner/suites/Multimedia/OpenCV/opencv_suite_README.md @@ -0,0 +1,336 @@ +# OpenCV Test/Perf Suite Runner — README +*(Updated 2025-10-15 10:33:13Z)* + +This README documents the **OpenCV-only** test/performance suite runner (`run.sh`) that auto-discovers +OpenCV gtest binaries (`opencv_test_*`) and perf binaries (`opencv_perf_*`), runs them with a unified +interface, and produces a single summary with correct exit codes. Camera tests are **not** included here. + +--- + +## What’s new / enhancements + +- **Auto-discovery** of `opencv_test_*` and `opencv_perf_*` from `--build-dir`, its `bin/`, and `$PATH`. +- **Per-binary skip** when a test/perf binary isn’t installed—suite continues and overall result only fails when any test fails. +- **Filter precedence**: `--filter` > `GTEST_FILTER` > `GTEST_FILTER_STRING` > `*` (default). +- **OPENCV_TEST_DATA_PATH**: auto-exported if not set (tries common locations), or override via `--testdata`. +- **Detailed logging**: start/end banners per test, exact binary path, **full arguments**, and log file path. +- **Performance defaults** for `opencv_perf_*`: + `--perf_impl=plain --perf_min_samples=1 --perf_force_samples=1 --perf_verify_sanity --skip_unstable=1` + (override with `--perf-args "..."`). +- **List mode** (`--list`): shows gtest test names for each binary without executing them. +- **Robust summary**: per-binary PASS/FAIL/SKIP table, **Totals**, and overall result written to `opencv_suite.res`. +- **Zero-tests treated as SKIP**: when a binary returns success but runs zero tests. +- **Timeout** support per run (`--timeout `, requires `timeout` tool). + +--- + +## Usage + +```bash +# Common env (optional) +export OPENCV_TEST_DATA_PATH=/usr/share/opencv4/testdata +export GTEST_FILTER_STRING='-tracking_GOTURN.GOTURN/*' # Global default filter + +# Basic help +./run.sh --help +``` + +### Run a single binary +```bash +# Let the script locate it on $PATH/build-dir +./run.sh --bin opencv_test_sfm + +# With an explicit filter (overrides env) +./run.sh --bin opencv_test_sfm --filter 'PoseEstimation*' + +# With extra args passed through +./run.sh --bin opencv_test_sfm --args '--gtest_also_run_disabled_tests' +``` + +### Run full suites (auto-discovery) +```bash +# Accuracy only (opencv_test_*) +./run.sh --suite accuracy + +# Performance only (opencv_perf_*) with default perf args +./run.sh --suite performance + +# Everything +./run.sh --suite all +``` + +### Performance args +```bash +# Override the defaults (example: more samples) +./run.sh --suite performance --perf-args '--perf_impl=plain --perf_min_samples=10 --perf_force_samples=10' +``` + +### Test data path +```bash +# Explicit test data root (exported to children) +./run.sh --testdata /usr/share/opencv4/testdata +``` + +### Repeat / shuffle / seed +```bash +./run.sh --bin opencv_test_core --repeat 5 --shuffle --seed 123 +``` + +### List tests in a binary +```bash +./run.sh --bin opencv_test_imgproc --list +``` + +### Timeout +```bash +./run.sh --suite accuracy --timeout 600 +``` + +### Working directory for execution +```bash +./run.sh --cwd /tmp +``` + +--- + +## Environment variables honored + +- `OPENCV_TEST_DATA_PATH` — path provided to OpenCV tests. Auto-discovered if not set; can be overridden by `--testdata`. +- `GTEST_FILTER` — gtest filter (overridden by `--filter`). +- `GTEST_FILTER_STRING` — alternate filter env (used if `--filter` and `GTEST_FILTER` are not set). + +**Filter resolution order:** `--filter` > `GTEST_FILTER` > `GTEST_FILTER_STRING` > `*`. + +Example: +```bash +export GTEST_FILTER_STRING='-tracking_GOTURN.GOTURN/*' +./run.sh --suite accuracy +``` + +--- + +## Logs, artifacts, and exit codes + +- **Per-run logs**: logs are written under a logs directory (default: `./logs`) as `_.log` +- **Summary table**: printed to stdout and written to `opencv_suite.summary` +- **Result list**: per-binary results in `opencv_suite.reslist` +- **Overall result**: `opencv_suite.res` contains `PASS`, `FAIL`, or `SKIP` + +**Exit codes:** +- `0` — At least one test ran and **all** executed tests passed +- `1` — At least one test failed +- `2` — No tests executed (all missing/empty) → suite considered **SKIP** + +**Zero tests** (e.g., filtered out) are treated as **SKIP** for that binary. + +--- + +## Example session + +```text +[INFO] 1970-01-01 02:36:14 - ========= Starting OpenCV Suite at 1970-01-01 02:36:14 ========= +[INFO] 1970-01-01 02:36:14 - ----- START opencv_test_sfm @ 1970-01-01 02:36:14 ----- +[INFO] 1970-01-01 02:36:14 - Running opencv_test_sfm +[INFO] 1970-01-01 02:36:14 - Binary : /usr/bin/opencv_test_sfm +[INFO] 1970-01-01 02:36:14 - OPENCV_TEST_DATA_PATH=/usr/share/opencv4/testdata +[INFO] 1970-01-01 02:36:14 - GTEST_FILTER_STRING=-tracking_GOTURN.GOTURN/* +[INFO] 1970-01-01 02:36:14 - Args : --gtest_color=yes --gtest_filter=-tracking_GOTURN.GOTURN/* +[INFO] 1970-01-01 02:36:14 - Log : /var/Runner/suites/Multimedia/OpenCV/opencv_suite/logs/opencv_test_sfm_19700101-023614.log +[PASS] 1970-01-01 02:36:14 - opencv_test_sfm : PASS +[INFO] 1970-01-01 02:36:14 - ----- END opencv_test_sfm (rc=0, PASS) @ 1970-01-01 02:36:14 ----- + +========= OpenCV Suite Summary ========= +TEST RES LOG +opencv_test_sfm PASS /var/Runner/suites/Multimedia/OpenCV/opencv_suite/logs/opencv_test_sfm_19700101-023614.log +---------------------------------------- +Totals: PASS=1 FAIL=0 SKIP=0 +``` + +--- + +## Troubleshooting + +- **Binary not found** → reported as `SKIP`; check that the package containing that `opencv_test_*`/`opencv_perf_*` is installed, or point `--build-dir` to your build root. +- **No tests run** → verify `--filter`/`GTEST_FILTER(_STRING)` patterns; tests filtered out lead to `SKIP` for that binary. +- **Test data missing** → set `--testdata` or `OPENCV_TEST_DATA_PATH` to your `opencv_extra/testdata` (or distro path like `/usr/share/opencv4/testdata`). +- **Long perf runs** → adjust `--perf-args` (e.g., reduce `--perf_min_samples`) or use `--timeout`. + +--- + +## Original README (verbatim, user-supplied) + +> The content below is pasted verbatim from your uploaded README for reference. + +--- + +# OpenCV Test/Perf Suite Runner (`run.sh`) + +A single script that auto-discovers and runs all installed **OpenCV accuracy tests** (`opencv_test_*`) and **performance tests** (`opencv_perf_*`) on the target. +It handles per-binary **PASS/FAIL/SKIP**, prints a **suite summary**, writes artifacts, and returns CI-friendly **exit codes**: + +- **0 = PASS**, **1 = FAIL**, **2 = SKIP** (overall PASS unless any binary fails) + +The script also exports helpful environment variables (e.g., `OPENCV_TEST_DATA_PATH`, `GTEST_FILTER_STRING`) and applies sane defaults for perf runs. + +--- + +## Download + +Pick one option and replace placeholders with your real path: + +**Option A — from your GitHub repo (raw link):** +```bash +curl -fsSL https://raw.githubusercontent.com////path/to/run.sh -o run.sh +chmod +x run.sh +``` + +**Option B — from a hosted file URL:** +```bash +curl -fsSL https:///artifacts/opencv/run.sh -o run.sh +chmod +x run.sh +``` + +> If your project uses this script in-tree, you can obviously skip the download step. + +--- + +## Requirements + +- POSIX shell (`/bin/sh`) and coreutils (`find`, `sort`, etc.) +- Optional: `timeout` (GNU coreutils) for `--timeout` support +- The script sources your existing environment helpers: + - `init_env` (auto-located, walking up from the script directory) + - `functestlib.sh` (via `$TOOLS/functestlib.sh`) +- OpenCV gtest/perf binaries on the **PATH** or under your **build directory**: + - Examples (from your target list): + `opencv_test_core`, `opencv_test_imgproc`, `opencv_perf_imgproc`, … + (auto-discovered; no static list required) + +--- + +## Quick Start + +Run **all** discovered accuracy + performance suites: +```bash +./run.sh +``` + +Only **accuracy** tests: +```bash +./run.sh --suite accuracy +``` + +Only **performance** tests: +```bash +./run.sh --suite performance +``` + +Run a **single** binary: +```bash +./run.sh --bin opencv_test_sfm +``` + +--- + +## Environment + +- `OPENCV_TEST_DATA_PATH` + Path to OpenCV test data (auto-detected if not provided: `/var/testdata`, `/usr/share/opencv4/testdata`, etc.). + +- `GTEST_FILTER` or `GTEST_FILTER_STRING` + Global GoogleTest filter applied to every run. + Example (exclude GOTURN): + ```bash + export GTEST_FILTER_STRING='-tracking_GOTURN.GOTURN/*' + ./run.sh + ``` + +> Precedence: `--filter` (CLI) → `GTEST_FILTER` → `GTEST_FILTER_STRING` → `*` + +--- + +## Common Options + +```text +--suite Defaults to all +--bin Run only one binary (e.g., opencv_test_core) +--build-dir Where to search for binaries (default . and ./bin) +--cwd Working directory for each run (default .) +--testdata Export OPENCV_TEST_DATA_PATH to this dir +--filter GoogleTest filter (e.g., "*", "-tracking_GOTURN.GOTURN/*") +--repeat GoogleTest repeat count +--shuffle Enable GoogleTest shuffle +--seed GoogleTest random seed +--args "" Extra args passed to ALL binaries +--perf-args "" Extra args for perf binaries (defaults provided) +--timeout Kill a run after N seconds (requires `timeout`) +--list List tests (per-binary) and exit (treated as PASS) +``` + +**Default perf args** (if not provided): +`--perf_impl=plain --perf_min_samples=1 --perf_force_samples=1 --perf_verify_sanity --skip_unstable=1` + +--- + +## Behavior & Exit Codes + +- Each discovered binary is executed independently: + - **Not found** → **SKIP** + - **Runs but zero tests** (e.g., filter excludes everything) → **SKIP** + - **Exit code 0** → **PASS** + - **Non-zero / timeout** → **FAIL** +- **Overall result**: + - Any **FAIL** → overall **FAIL** (exit **1**) + - Else if at least one **PASS** → overall **PASS** (exit **0**) + - Else (only SKIPs) → overall **SKIP** (exit **2**) + +--- + +## Artifacts + +- Logs directory: `${LOG_DIR:-./logs}` + Each binary writes `logs/_.log` +- Suite summary: `./opencv_suite.summary` (table of results + log paths) +- Result file: `./opencv_suite.res` with `PASS`/`FAIL`/`SKIP` + +Example summary line: +``` +opencv_test_sfm PASS logs/opencv_test_sfm_20250101-120000.log +``` + +--- + +## Examples + +Exclude GOTURN for all runs + custom testdata: +```bash +export OPENCV_TEST_DATA_PATH=/var/testdata +export GTEST_FILTER_STRING='-tracking_GOTURN.GOTURN/*' +./run.sh --suite all +``` + +Run only performance with explicit args and timeout: +```bash +./run.sh --suite performance --perf-args "--perf_impl=plain --skip_unstable=1" --timeout 600 +``` + +Run one binary with extra args: +```bash +./run.sh --bin opencv_test_core --args "--gapi_backend=cpu" +``` + +--- + +## Troubleshooting + +- **No binaries discovered** → check `PATH` and `--build-dir`. +- **Zero tests executed** → your `--filter` (or env filter) may exclude all tests. +- **Missing testdata** → export `OPENCV_TEST_DATA_PATH` explicitly. +- **Timeout fails** → ensure `timeout` is installed or drop `--timeout`. + +--- + +## License + +BSD-3-Clause-Clear (same as the script). + diff --git a/Runner/suites/Multimedia/OpenCV/run.sh b/Runner/suites/Multimedia/OpenCV/run.sh new file mode 100755 index 00000000..1c2f89c2 --- /dev/null +++ b/Runner/suites/Multimedia/OpenCV/run.sh @@ -0,0 +1,442 @@ +#!/bin/sh +# OpenCV test/perf suite runner (auto-discovery; per-binary skip; summary; proper exit codes) +# SPDX-License-Identifier: BSD-3-Clause-Clear + +# ----- locate and source init_env ----- +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" +INIT_ENV="" +SEARCH="$SCRIPT_DIR" + +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# Only source once (idempotent) +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" + +TESTNAME="OpenCV" +LOG_DIR="${LOG_DIR:-$SCRIPT_DIR/logs}" +mkdir -p "$LOG_DIR" >/dev/null 2>&1 || true +RES_FILE="$SCRIPT_DIR/$TESTNAME.res" +SUMMARY_FILE="$SCRIPT_DIR/$TESTNAME.summary" +RESLIST_FILE="$SCRIPT_DIR/${TESTNAME}.reslist" +: > "$RESLIST_FILE" +: > "$SUMMARY_FILE" + +# ---------- defaults / cli ---------- +# We compute the final filter AFTER parsing CLI to honor precedence: +# CLI --filter > env GTEST_FILTER > env GTEST_FILTER_STRING > DEFAULT_FILTER +DEFAULT_FILTER="-tracking_GOTURN.GOTURN/*" +CLI_FILTER="" + +BIN_PATH="" # --bin +BUILD_DIR="." # --build-dir +CWD="." # --cwd +TESTDATA_PATH="" # --testdata +EXTRA_ARGS="" # --args "..." +TIMEOUT_SECS="" # --timeout N +SUITE="all" # --suite accuracy|performance|all +LIST_ONLY=0 # --list +REPEAT="" # --repeat N +SHUFFLE=0 # --shuffle +SEED="" # --seed N +PERF_ARGS="" # --perf-args "" +PERF_TO_TESTS=0 # --perf-to-tests (apply PERF_ARGS to opencv_test_* too) + +print_usage() { + cat < Run a single OpenCV gtest/perf binary (overrides --suite) + --build-dir Root to search for binaries (default: .) + --suite accuracy | performance | all (default: all) + --filter gtest filter for all runs (default from env or "-tracking_GOTURN.GOTURN/*") + --repeat gtest repeat count + --shuffle Enable gtest shuffle + --seed gtest random seed + --cwd Working directory for test execution (default: .) + --testdata Export OPENCV_TEST_DATA_PATH to this dir + --args "" Extra args added to all test binaries + --timeout Kill a run if it exceeds (needs \`timeout\`) + --perf-args "" Extra args appended to all opencv_perf_* runs + --perf-to-tests Also append --perf-args to opencv_test_* runs + --list List tests (per-binary) and exit (treated as PASS) + -h|--help Show this help + +Env honored: + OPENCV_TEST_DATA_PATH, GTEST_FILTER, GTEST_FILTER_STRING +EOF +} + +while [ $# -gt 0 ]; do + case "$1" in + --bin) BIN_PATH="$2"; shift 2 ;; + --build-dir) BUILD_DIR="$2"; shift 2 ;; + --suite) SUITE="$2"; shift 2 ;; + --filter) CLI_FILTER="$2"; shift 2 ;; + --repeat) REPEAT="$2"; shift 2 ;; + --shuffle) SHUFFLE=1; shift 1 ;; + --seed) SEED="$2"; shift 2 ;; + --cwd) CWD="$2"; shift 2 ;; + --testdata) TESTDATA_PATH="$2"; shift 2 ;; + --args) EXTRA_ARGS="$2"; shift 2 ;; + --timeout) TIMEOUT_SECS="$2"; shift 2 ;; + --perf-args) PERF_ARGS="$2"; shift 2 ;; + --perf-to-tests) PERF_TO_TESTS=1; shift 1 ;; + --list) LIST_ONLY=1; shift 1 ;; + -h|--help) print_usage; exit 0 ;; + *) log_warn "Unknown arg: $1"; shift 1 ;; + esac +done + +# ----- PATH enrichment so check_dependencies can see build dir binaries ----- +case ":$PATH:" in + *":$BUILD_DIR/bin:"*) ;; + *) [ -d "$BUILD_DIR/bin" ] && PATH="$BUILD_DIR/bin:$PATH" ;; +esac +case ":$PATH:" in + *":$BUILD_DIR:"*) ;; + *) [ -d "$BUILD_DIR" ] && PATH="$BUILD_DIR:$PATH" ;; +esac +export PATH + +# ----- OPENCV_TEST_DATA_PATH (explicit or auto) ----- +if [ -n "$TESTDATA_PATH" ]; then + if [ -d "$TESTDATA_PATH" ]; then + export OPENCV_TEST_DATA_PATH="$TESTDATA_PATH" + else + log_warn "--testdata provided but not found: $TESTDATA_PATH (continuing)" + fi +elif [ -z "${OPENCV_TEST_DATA_PATH:-}" ]; then + for cand in \ + /var/testdata \ + /usr/share/opencv4/testdata \ + /usr/share/opencv/testdata \ + /opt/opencv_extra/testdata \ + "$SCRIPT_DIR/../../testdata" + do + if [ -d "$cand" ]; then export OPENCV_TEST_DATA_PATH="$cand"; break; fi + done +fi + +# ----- resolve final gtest filter with precedence ----- +FINAL_FILTER="$DEFAULT_FILTER" +[ -n "${GTEST_FILTER_STRING:-}" ] && FINAL_FILTER="$GTEST_FILTER_STRING" +[ -n "${GTEST_FILTER:-}" ] && FINAL_FILTER="$GTEST_FILTER" +[ -n "$CLI_FILTER" ] && FINAL_FILTER="$CLI_FILTER" + +# Export for children and also use internally +export GTEST_FILTER_STRING="$FINAL_FILTER" +GTEST_FILTER="$FINAL_FILTER" + +# ----- set default PERF_ARGS early so logs always show it ----- +if [ -z "$PERF_ARGS" ]; then + PERF_ARGS="--perf_impl=plain --perf_min_samples=10 --perf_force_samples=10 --perf_verify_sanity --skip_unstable=1" +fi + +# ----- timeout handling via run_with_timeout (from functestlib.sh) ----- +if [ -n "$TIMEOUT_SECS" ]; then + if ! command -v run_with_timeout >/dev/null 2>&1; then + log_warn "run_with_timeout() not available; ignoring --timeout" + TIMEOUT_SECS="" + fi +fi + +# ----- helpers ----- +resolve_bin() { + bn="$1" + if [ -x "$bn" ]; then printf "%s" "$bn"; return 0; fi + if [ -x "$BUILD_DIR/bin/$bn" ]; then printf "%s" "$BUILD_DIR/bin/$bn"; return 0; fi + if [ -x "$BUILD_DIR/$bn" ]; then printf "%s" "$BUILD_DIR/$bn"; return 0; fi + if command -v "$bn" >/dev/null 2>&1; then command -v "$bn"; return 0; fi + fnd="$(find "$BUILD_DIR" -maxdepth 3 -type f -name "$bn" -perm -111 2>/dev/null | head -n 1)" + [ -n "$fnd" ] && { printf "%s" "$fnd"; return 0; } + return 1 +} + +append_summary() { + # args: name status logpath + printf "%-32s %-4s %s\n" "$1" "$2" "$3" >> "$SUMMARY_FILE" +} + +parse_zero_tests_as_skip() { + # Return 0 (true) if log shows "Running 0 tests ..." (suites or cases wording) or "No tests to run" + logp="$1" + grep -Eq '^\[==========\] Running 0 tests from 0 test (suites|cases)\.' "$logp" \ + || grep -qi 'No tests to run' "$logp" +} + +run_one() { + bin_short="$1" + bin_extra="$2" + + bin_path="$(resolve_bin "$bin_short")" || { + log_skip "$bin_short : not found — SKIP" + echo "$bin_short SKIP" >> "$RESLIST_FILE" + append_summary "$bin_short" "SKIP" "-" + return 2 + } + + # Build argument list + ts="$(date +%Y%m%d-%H%M%S)" + gargs="--gtest_color=yes --gtest_filter=$GTEST_FILTER" + [ -n "$REPEAT" ] && gargs="$gargs --gtest_repeat=$REPEAT" + [ "$SHUFFLE" -eq 1 ] && gargs="$gargs --gtest_shuffle" + [ -n "$SEED" ] && gargs="$gargs --gtest_random_seed=$SEED" + [ "$LIST_ONLY" -eq 1 ] && gargs="$gargs --gtest_list_tests" + [ -n "$EXTRA_ARGS" ] && gargs="$gargs $EXTRA_ARGS" + [ -n "$bin_extra" ] && gargs="$gargs $bin_extra" + + perf_applied="no" + case "$bin_short" in + opencv_perf_*) + # If suite didn't pass PERF_ARGS as bin_extra (single-binary mode), apply them here. + if [ -z "$bin_extra" ] && [ -n "$PERF_ARGS" ]; then + gargs="$gargs $PERF_ARGS" + fi + perf_applied="yes" + ;; + opencv_test_*) + if [ "$PERF_TO_TESTS" -eq 1 ] && [ -n "$PERF_ARGS" ]; then + # If not already provided via bin_extra, add now. + case " $gargs " in + *" $PERF_ARGS "*) : ;; # already included + *) gargs="$gargs $PERF_ARGS" ;; + esac + perf_applied="yes" + fi + ;; + esac + + run_log="$LOG_DIR/${bin_short}_${ts}.log" + + now="$(date '+%Y-%m-%d %H:%M:%S')" + log_info "----- START $bin_short @ $now -----" + log_info "Running $bin_short" + log_info "Binary : $bin_path" + [ -n "${OPENCV_TEST_DATA_PATH:-}" ] && log_info "OPENCV_TEST_DATA_PATH=$OPENCV_TEST_DATA_PATH" + log_info "GTEST_FILTER_STRING: '$GTEST_FILTER_STRING'" + log_info "Args : $gargs" + if [ "$perf_applied" = "yes" ]; then + log_info "PerfArgs (applied): ${PERF_ARGS:-}" + else + log_info "PerfArgs (available, not applied): ${PERF_ARGS:-}" + fi + + cmd="$bin_path $gargs" + if [ -n "$TIMEOUT_SECS" ]; then + log_info "Cmd : $cmd (via run_with_timeout $TIMEOUT_SECS)" + else + log_info "Cmd : $cmd" + fi + log_info "Log : $run_log" + + ( + cd "$CWD" || exit 2 + if [ -n "$TIMEOUT_SECS" ]; then + run_with_timeout "$TIMEOUT_SECS" sh -c "$cmd" + else + # shellcheck disable=SC2086 + sh -c "$cmd" + fi + ) >"$run_log" 2>&1 + rc=$? + [ "$rc" -eq 124 ] && rc=1 # timeout => fail + + if [ "$rc" -eq 0 ] && parse_zero_tests_as_skip "$run_log"; then + log_skip "$bin_short : No tests executed — SKIP" + echo "$bin_short SKIP" >> "$RESLIST_FILE" + append_summary "$bin_short" "SKIP" "$run_log" + log_info "----- END $bin_short (rc=0, SKIP) @ $(date '+%Y-%m-%d %H:%M:%S') -----" + return 2 + fi + + if [ "$rc" -eq 0 ]; then + log_pass "$bin_short : PASS" + echo "$bin_short PASS" >> "$RESLIST_FILE" + append_summary "$bin_short" "PASS" "$run_log" + log_info "----- END $bin_short (rc=0, PASS) @ $(date '+%Y-%m-%d %H:%M:%S') -----" + return 0 + else + log_fail "$bin_short : FAIL (exit=$rc). See: $run_log" + echo "$bin_short FAIL" >> "$RESLIST_FILE" + append_summary "$bin_short" "FAIL" "$run_log" + log_info "----- END $bin_short (rc=$rc, FAIL) @ $(date '+%Y-%m-%d %H:%M:%S') -----" + return 1 + fi +} + +discover_bins() { + # Emit newline-separated base names for opencv_test_* and opencv_perf_* discovered + tmp_all="$(mktemp "/tmp/${TESTNAME}_bins.XXXXXX")" + : > "$tmp_all" + + # Search BUILD_DIR and BUILD_DIR/bin first + for root in "$BUILD_DIR" "$BUILD_DIR/bin"; do + [ -d "$root" ] || continue + find "$root" -maxdepth 1 -type f \( -name 'opencv_test_*' -o -name 'opencv_perf_*' \) -print 2>/dev/null \ + | while IFS= read -r p; do + [ -x "$p" ] && basename "$p" + done >> "$tmp_all" + done + + # Search PATH dirs + OLD_IFS="$IFS"; IFS=":" + for d in $PATH; do + [ -d "$d" ] || continue + find "$d" -maxdepth 1 -type f \( -name 'opencv_test_*' -o -name 'opencv_perf_*' \) -print 2>/dev/null \ + | while IFS= read -r p; do + [ -x "$p" ] && basename "$p" + done >> "$tmp_all" + done + IFS="$OLD_IFS" + + # Unique + classify + sort -u "$tmp_all" | while IFS= read -r b; do + case "$b" in + opencv_test_*) printf "ACC %s\n" "$b" ;; + opencv_perf_*) printf "PER %s\n" "$b" ;; + esac + done + rm -f "$tmp_all" +} + +log_info "========= Starting OpenCV Suite at $(date '+%Y-%m-%d %H:%M:%S') =========" +log_info "Suite settings: FILTER='$GTEST_FILTER_STRING' | PERF_TO_TESTS=$PERF_TO_TESTS | PERF_ARGS='${PERF_ARGS:-}''" + +# ----- single-binary mode: dependency check + run ----- +if [ -n "$BIN_PATH" ]; then + # Resolve; if not found => SKIP the suite + rb="$(resolve_bin "$(basename "$BIN_PATH")" 2>/dev/null || true)" + if [ -z "$rb" ]; then + log_skip "$(basename "$BIN_PATH") : not found — SKIP" + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 2 + fi + # Ensure check_dependencies sees it (by basename in PATH) + bname="$(basename "$rb")" + bdir="$(dirname "$rb")" + case ":$PATH:" in *":$bdir:"*) ;; *) PATH="$bdir:$PATH"; export PATH;; esac + check_dependencies "$bname" || log_warn "Dependency check failed for $bname — proceeding" + + BIN_SHORT="$bname" + run_one "$BIN_SHORT" "" + rc=$? + echo "" + echo "========= OpenCV Suite Summary =========" + printf "%-32s %-4s %s\n" "TEST" "RES" "LOG" + cat "$SUMMARY_FILE" + echo "========================================" + case "$rc" in + 0) echo "$TESTNAME PASS" > "$RES_FILE"; exit 0 ;; + 2) echo "$TESTNAME SKIP" > "$RES_FILE"; exit 2 ;; + *) echo "$TESTNAME FAIL" > "$RES_FILE"; exit 1 ;; + esac +fi + +# ----- build suite selection ----- +want_acc=0; want_per=0 +case "$SUITE" in + accuracy) want_acc=1 ;; + performance) want_per=1 ;; + all) want_acc=1; want_per=1 ;; + *) log_warn "Unknown suite '$SUITE', defaulting to 'all'"; want_acc=1; want_per=1 ;; +esac + +# ----- discover first (we will also use this list for dependency check) ----- +DISC_TMP="$(mktemp "/tmp/${TESTNAME}_discover.XXXXXX")" +discover_bins > "$DISC_TMP" + +if [ ! -s "$DISC_TMP" ]; then + log_skip "No OpenCV test/perf binaries discovered — SKIP" + echo "$TESTNAME SKIP" > "$RES_FILE" + rm -f "$DISC_TMP" + exit 2 +fi + +# ----- dependency check for opencv_test_core + discovered binaries (non-fatal) ----- +DEPS="opencv_test_core" +while IFS= read -r line; do + name="$(printf '%s' "$line" | awk '{print $2}')" + [ -n "$name" ] && DEPS="$DEPS $name" +done < "$DISC_TMP" + +# Non-fatal: just report availability; we still run and SKIP missing ones in run_one +# shellcheck disable=SC2086 +check_dependencies $DEPS || log_warn "Some OpenCV binaries not found in PATH; they may still be resolved via --build-dir" + +PASS=0 +FAIL=0 +SKIP=0 + +# ----- run accuracy ----- +if [ "$want_acc" -eq 1 ]; then + acc_list="$(awk '/^ACC /{print $2}' "$DISC_TMP" | tr '\n' ' ')" + for b in $acc_list; do + [ -z "$b" ] && continue + run_one "$b" "" + r=$? + if [ "$r" -eq 0 ]; then PASS=$((PASS+1)) + elif [ "$r" -eq 2 ]; then SKIP=$((SKIP+1)) + else FAIL=$((FAIL+1)) + fi + done +fi + +# ----- run performance ----- +if [ "$want_per" -eq 1 ]; then + per_list="$(awk '/^PER /{print $2}' "$DISC_TMP" | tr '\n' ' ')" + for b in $per_list; do + [ -z "$b" ] && continue + # We no longer need to pass PERF_ARGS here explicitly, run_one() will apply defaults for perf bins. + run_one "$b" "" + r=$? + if [ "$r" -eq 0 ]; then PASS=$((PASS+1)) + elif [ "$r" -eq 2 ]; then SKIP=$((SKIP+1)) + else FAIL=$((FAIL+1)) + fi + done +fi + +rm -f "$DISC_TMP" + +# ----- print + persist final summary ----- +echo "" >> "$SUMMARY_FILE" +echo "========= OpenCV Suite Summary =========" +printf "%-32s %-4s %s\n" "TEST" "RES" "LOG" +cat "$SUMMARY_FILE" +echo "----------------------------------------" +echo "Totals: PASS=$PASS FAIL=$FAIL SKIP=$SKIP" + +# ----- decide overall result ----- +if [ "$FAIL" -gt 0 ]; then + echo "$TESTNAME FAIL" > "$RES_FILE" + exit 1 +fi +if [ "$PASS" -gt 0 ] || [ "$SKIP" -gt 0 ]; then + # Treat "some ran (pass/skip) and no failures" as PASS overall + echo "$TESTNAME PASS" > "$RES_FILE" + exit 0 +fi +echo "$TESTNAME SKIP" > "$RES_FILE" +exit 2 diff --git a/Runner/suites/Multimedia/Video/README_Video.md b/Runner/suites/Multimedia/Video/README_Video.md deleted file mode 100644 index 28a458c2..00000000 --- a/Runner/suites/Multimedia/Video/README_Video.md +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. -# SPDX-License-Identifier: BSD-3-Clause-Clear - -# Iris V4L2 Video Test Scripts for Qualcomm Linux based platform (Yocto) - -## Overview - -Video scripts automates the validation of video encoding and decoding capabilities on the Qualcomm Linux based platform running a Yocto-based Linux system. It utilizes iri_v4l2_test test app which is publicly available @https://github.com/quic/v4l-video-test-app - -## Features - -- V4L2 driver level test -- Encoding YUV to H264 bitstream -- Decoding H264 bitstream to YUV -- Compatible with Yocto-based root filesystem - -## Prerequisites - -Ensure the following components are present in the target Yocto build: - -- `iris_v4l2_test` (available in /usr/bin/) - this test app can be compiled from https://github.com/quic/v4l-video-test-app -- input json file for iris_v4l2_test app -- input bitstream for decode script -- input YUV for encode script -- Write access to root filesystem (for environment setup) - -## Directory Structure - -```bash -Runner/ -├── suites/ -│ ├── Multimedia/ -│ │ ├── Video/ -│ │ │ ├── iris_v4l2_video_encode/ -│ │ │ │ ├── H264Encoder.json -│ │ │ │ ├── run.sh -│ │ │ ├── iris_v4l2_video_decode/ -│ │ │ │ ├── H264Decoder.json -│ │ │ │ ├── run.sh -``` - -## Usage - -1. Copy repo to Target Device: Use scp to transfer the scripts from the host to the target device. The scripts should be copied to any directory on the target device. - -2. Verify Transfer: Ensure that the repo have been successfully copied to any directory on the target device. - -3. Run Scripts: Navigate to the directory where these files are copied on the target device and execute the scripts as needed. - -Run a specific test using: ---- -Quick Example -``` -git clone -cd -scp -r Runner user@target_device_ip: -ssh user@target_device_ip -cd /Runner && ./run-test.sh iris_v4l2_video_encode -``` -Sample output: -``` -sh-5.2# cd /Runner && ./run-test.sh iris_v4l2_video_encode -[Executing test case: /Runner/suites/Multimedia/Video/iris_v4l2_video_encode] 1980-01-08 22:22:15 - -[INFO] 1980-01-08 22:22:15 - ----------------------------------------------------------------------------------------- -[INFO] 1980-01-08 22:22:15 - -------------------Starting iris_v4l2_video_encode Testcase---------------------------- -[INFO] 1980-01-08 22:22:15 - Checking if dependency binary is available -[PASS] 1980-01-08 22:22:15 - Test related dependencies are present. -... -[PASS] 1980-01-08 22:22:17 - iris_v4l2_video_encode : Test Passed -[INFO] 1980-01-08 22:22:17 - -------------------Completed iris_v4l2_video_encode Testcase---------------------------- -``` -3. Results will be available in the `Runner/suites/Multimedia/Video/` directory under each usecase folder. - -## Notes - -- The script does not take any arguments. -- It validates the presence of required libraries before executing tests. -- If any critical tool is missing, the script exits with an error message. \ No newline at end of file diff --git a/Runner/suites/Multimedia/Video/Video_V4L2_Runner/README_Video.md b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/README_Video.md new file mode 100644 index 00000000..aa53fd16 --- /dev/null +++ b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/README_Video.md @@ -0,0 +1,353 @@ +# Iris V4L2 Video Test Scripts for Qualcomm Linux (Yocto) + +**Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.** +**SPDX-License-Identifier: BSD-3-Clause-Clear** + +--- + +## Overview + +These scripts automate validation of video **encoding** and **decoding** on Qualcomm Linux platforms running a Yocto-based rootfs. +They drive the public `iris_v4l2_test` app: . + +The suite includes a **reboot-free video stack switcher** (upstream ↔ downstream), a **Kodiak (QCS6490/RB3gen2) firmware swap flow**, and robust **pre-flight checks** (rootfs size, network bootstrap, module sanity, device nodes). + +--- + +## What’s New (since 2025‑10‑03) + +- **Network stabilization delay (post-connect)** + After an interface comes up (DHCP/DNS), the runner now sleeps for a short grace period before the first TLS download to avoid immediate failures. + - Env knob: `NET_STABILIZE_SLEEP` (default **5** seconds). + +- **Downloader timeouts & retries (BusyBox‑friendly)** + Clip bundle downloads honor BusyBox wget timeouts/retries and perform a final TLS‑lenient attempt when the clock is not yet sane. + - Env knobs: `WGET_TIMEOUT_SECS` (default **120**), `WGET_TRIES` (default **2**). + +- **SKIP instead of FAIL when offline** + If the network is unreachable (or time is invalid for TLS) *and* required media clips are missing, **decode** cases are *SKIPPED* rather than failed. Encode cases continue to run. + +- **App launch & inter‑test pacing** + To reduce flakiness from back‑to‑back runs, the runner adds small sleeps **before** launching `iris_v4l2_test` and **between** tests. + - Env knobs: `VIDEO_APP_LAUNCH_SLEEP` (default **1** second), `VIDEO_INTER_TEST_SLEEP` (default **1** second). + +- **Module operations: gentle waits & retries** + Module unload/load and blacklist scrubbing paths include short sleeps and a retry pass (`modprobe -r` retry with a small delay, 1s delays around remoteproc/module reloads). No new CLI needed. + +- **CLI parity** + `--stack both` is supported to run the suite twice in one invocation (BASE/upstream pass then OVERLAY/downstream pass). + +- **NEW (opt‑in) custom module sources** + You can now point the runner at alternative module locations without disturbing the default flow. If you **do nothing**, behavior is unchanged. + - `--ko-dir DIR[:DIR2:...]` — search these dir(s) for `.ko*` files when resolving modules. + - `--ko-tree ROOT` — use `modprobe -d ROOT` (expects `ROOT/lib/modules/$(uname -r)`). + - `--ko-tar FILE.tar[.gz|.xz|.zst]` — unpack once under `/run/iris_mods/$KVER`; auto-derives a `--ko-tree` or `--ko-dir`. + - `--ko-prefer-custom` — prefer custom sources before the system tree. + - The loader now logs **path resolution** and **load method** lines, e.g.: + - `resolve-path: qcom_iris via KO_DIRS => /data/kos/qcom_iris.ko` + - `load-path: modprobe(system): qcom_iris` / `load-path: insmod: /tmp/qcom_iris.ko` + +--- + +## Features + +- Pure **V4L2** driver-level tests using `iris_v4l2_test` +- **Encode** (YUV → H.264/H.265) and **Decode** (H.264/H.265/VP9 → YUV) +- **Yocto**-friendly, POSIX shell with BusyBox-safe paths +- Parse & run multiple JSON configs, auto-detect **encode/decode** +- **Auto-fetch** missing input clips (retries, BusyBox `wget` compatible) +- **Rootfs size guard** (auto‑resize) **before** fetching assets +- **Network bootstrap** (Ethernet → Wi‑Fi via `nmcli`/`wpa_supplicant`) when needed for downloads +- Timeout, repeat, dry-run, JUnit XML, dmesg triage +- **Stack switcher**: upstream ↔ downstream without reboot +- **Kodiak firmware live swap** with backup/restore helpers +- **udev refresh + prune** of stale device nodes +- **Waits/retries/sleeps** integrated across networking, downloads, module ops, and app launches (see next section) +- **(Opt‑in)** custom module sources with **non-exported** CLI flags (`--ko-*`); defaults remain untouched + +--- + +## Stability waits, retries & timeouts (defaults & overrides) + +These are **environment variables** (not user‑visible CLI flags) so your LAVA job YAML can stay minimal. All are **optional**—defaults are sane. + +| Env Var | Default | Purpose | +|---|---:|---| +| `NET_STABILIZE_SLEEP` | `5` | Sleep (seconds) after link/IP assignment before first download. Applied also when already online, to debounce DNS/routes. | +| `WGET_TIMEOUT_SECS` | `120` | BusyBox wget timeout per attempt when fetching the clip bundle. | +| `WGET_TRIES` | `2` | BusyBox wget retry count for clip bundle. | +| `VIDEO_APP_LAUNCH_SLEEP` | `1` | Sleep (seconds) right before launching `iris_v4l2_test` for each case. | +| `VIDEO_INTER_TEST_SLEEP` | `1` | Sleep (seconds) between cases to allow device/udev to settle. | + +> Notes +> - If download **stalls** or the system clock is invalid for TLS, the runner re-checks network health and treats it as **offline** → decode cases **SKIP** (not FAIL). +> - Module management includes small internal waits (e.g., `modprobe -r` retry after 200ms, 1s delays around remoteproc/module reloads). These are built‑in, no extra env required. + +--- + +## Directory Layout + +```bash +Runner/ +├── suites/ +│ └── Multimedia/ +│ └── Video/ +│ ├── README_Video.md +│ └── Video_V4L2_Runner/ +│ ├── h264Decoder.json +│ ├── h265Decoder.json +│ ├── vp9Decoder.json +│ ├── h264Encoder.json +│ ├── h265Encoder.json +│ └── run.sh +└── utils/ + ├── functestlib.sh + └── lib_video.sh +``` + +--- + +## Quick Start + +```bash +git clone +cd + +# Copy to target +scp -r Runner user@: +ssh user@ + +cd /Runner +./run-test.sh Video_V4L2_Runner +``` + +> Results land under: `Runner/suites/Multimedia/Video/Video_V4L2_Runner/` + +--- + +## Runner CLI (run.sh) + +| Option | Description | +|---|---| +| `--config path.json` | Run a specific config file | +| `--dir DIR` | Directory to search for configs | +| `--pattern GLOB` | Filter configs by glob pattern | +| `--extract-input-clips true|false` | Auto-fetch missing clips (default: `true`) | +| `--timeout S` | Timeout per test (default: `60`) | +| `--strict` | Treat dmesg warnings as failures | +| `--no-dmesg` | Disable dmesg scanning | +| `--max N` | Run at most `N` tests | +| `--stop-on-fail` | Abort suite on first failure | +| `--loglevel N` | Log level passed to `iris_v4l2_test` | +| `--repeat N` | Repeat each test `N` times | +| `--repeat-delay S` | Delay between repeats | +| `--repeat-policy all|any` | PASS if all runs pass, or any run passes | +| `--junit FILE` | Write JUnit XML | +| `--dry-run` | Print commands only | +| `--verbose` | Verbose runner logs | +| `--app /path/to/iris_v4l2_test` | Override test app path | +| `--stack auto|upstream|downstream|base|overlay|up|down|both` | Select target stack (use `both` for BASE→OVERLAY two-pass) | +| `--platform lemans|monaco|kodiak` | Force platform (else auto-detect) | +| `--downstream-fw PATH` | **Kodiak**: path to DS firmware (e.g. `vpu20_1v.mbn`) | +| `--ko-dir DIR[:DIR2:...]` | *(Opt‑in)* Additional directories to search for `.ko*` files during resolution | +| `--ko-tree ROOT` | *(Opt‑in)* Use `modprobe -d ROOT` (expects `ROOT/lib/modules/$(uname -r)`) | +| `--ko-tar FILE.tar[.gz|.xz|.zst]` | *(Opt‑in)* Unpack once into `/run/iris_mods/$KVER`; auto-derives `--ko-tree` or `--ko-dir` | +| `--ko-prefer-custom` | *(Opt‑in)* Prefer custom module sources (KO_DIRS/KO_TREE) before system | + +> **Default remains unchanged.** If you omit all `--ko-*` flags, the runner uses the system module tree and `modinfo`/`modprobe` resolution only. + +--- + +## Pre‑Flight: Rootfs Size & Network + +### Auto‑resize before downloads +The runner calls `ensure_rootfs_min_size 2` **before** any download. If `/` is on `/dev/disk/by-partlabel/rootfs` and total size is below ~2 GiB, it executes: +```sh +resize2fs /dev/disk/by-partlabel/rootfs +``` + +### Network bootstrap (if offline) +If the target is offline when a clip bundle is needed: + +1. Tries Ethernet DHCP (safe retries) +2. If **Wi‑Fi creds** are available, tries: + - `nmcli dev wifi connect "" password "" ifname ` + If NetworkManager complains about missing key‑mgmt, it auto‑falls back to: + ```sh + nmcli con add type wifi ifname con-name auto- ssid "" \ + wifi-sec.key-mgmt wpa-psk wifi-sec.psk "" + nmcli con up auto- + ``` + - If still offline, uses `wpa_supplicant + udhcpc` + +After connectivity, a **debounce wait** is applied: +```sh +# Default 5s (override via NET_STABILIZE_SLEEP) +sleep "${NET_STABILIZE_SLEEP:-5}" +``` + +**Provide credentials via:** +```sh +export SSID="WIFI_SSID" +export PASSWORD="WIFI_PASSWORD" +# or create ./ssid_list.txt with: WIFI_SSID WIFI_PASSWORD +``` + +When network remains unreachable and clips are missing, **decode cases are SKIPPED** (not failed). + +--- + +## Stack Selection & Validation + +- **lemans/monaco** + - **Upstream**: `qcom_iris` + `iris_vpu` + - **Downstream**: `iris_vpu` only (and *no* `qcom_iris`) + +- **kodiak** + - **Upstream**: `venus_core`, `venus_dec`, `venus_enc` + - **Downstream**: `iris_vpu` + +The runner: +1. Prints **pre/post** module snapshots and any runtime/persistent modprobe blocks +2. Switches stacks without reboot (uses runtime blacklists under `/run/modprobe.d`) +3. **Refreshes** `/dev/video*` & `/dev/media*` with udev and **prunes** stale nodes +4. Applies small **waits/retries** around unload/load and de‑blacklist/blacklist paths + +--- + +## Kodiak Firmware Flows + +### Downstream (custom blob) +When `--stack downstream` and you pass `--downstream-fw /path/to/vpu20_1v.mbn`: +1. The blob is copied to: `/lib/firmware/qcom/vpu/vpu20_p1_gen2.mbn` +2. Previous image is backed up to: `/opt/video-fw-backups/vpu20_p1_gen2.mbn..bak` +3. Runner tries **remoteproc restart**, then **module reload**, then **unbind/bind** (with short waits between steps) + +### Upstream (restore a backup before switch) +When `--stack upstream` on **kodiak**, the runner tries to **restore a known‑good backup** to `/lib/firmware/qcom/vpu/vpu20_p1_gen2.mbn` **before** switching: +- Search order: + 1. `$VIDEO_FW_BACKUP_DIR` (default `/opt/video-fw-backups`), newest `vpu20_p1_gen2.mbn.*.bak` + 2. Legacy `/opt` patterns (e.g., `vpu20_p1_gen2.mbn.*.bak`) +- Then it attempts **remoteproc restart**; falls back as needed (with 1s waits). + +**Tip:** If you maintain backups under a custom path: +```sh +export VIDEO_FW_BACKUP_DIR=/opt +./run.sh --platform kodiak --stack upstream +``` + +--- + +## Examples + +### Minimal: run all configs with sane defaults +```sh +./run.sh +``` + +### Use `both` to run BASE then OVERLAY in one job +```sh +./run.sh --stack both +``` + +### Force downstream on lemans/monaco +```sh +./run.sh --stack downstream +``` + +### Force upstream on lemans/monaco +```sh +./run.sh --stack upstream +``` + +### Kodiak: downstream with custom firmware (live swap) +```sh +./run.sh --platform kodiak --stack downstream --downstream-fw /data/fw/vpu20_1v.mbn +``` + +### Kodiak: upstream with automatic backup restore +```sh +./run.sh --platform kodiak --stack upstream +# optionally pin the backup directory +VIDEO_FW_BACKUP_DIR=/opt ./run.sh --platform kodiak --stack upstream +``` + +### Ensure Wi‑Fi is used for downloads (if needed) +```sh +export SSID="WIFI_SSID" +export PASSWORD="WIFI_PASSWORD" +./run.sh --extract-input-clips true +``` + +### Use a specific app binary +```sh +./run.sh --app /data/vendor/iris_test_app/iris_v4l2_test --stack upstream +``` + +### Run only H.265 decode +```sh +./run.sh --pattern '*h265*Decoder.json' +``` + +### Override waits/timeouts (optional) +```sh +# Debounce network right after IP assignment +export NET_STABILIZE_SLEEP=8 + +# BusyBox wget tuning +export WGET_TIMEOUT_SECS=180 +export WGET_TRIES=3 + +# Pacing iris app & tests +export VIDEO_APP_LAUNCH_SLEEP=2 +export VIDEO_INTER_TEST_SLEEP=3 + +./run.sh --stack upstream +``` + +### (Opt‑in) Use custom module sources +**Default behavior is unchanged.** Only use these when you want to test modules from a non-system location. + +#### Use a prepared tree (modprobe -d) +```sh +./run.sh --ko-tree /opt/custom-kmods --stack upstream +``` + +#### Search one or more directories of loose .ko files +```sh +./run.sh --ko-dir /data/kos:/mnt/usb/venus_kos --stack downstream +``` + +#### Prefer custom before system +```sh +./run.sh --ko-dir /sdcard/kos --ko-prefer-custom --stack upstream +``` + +#### Unpack a tarball of modules and auto-wire paths +```sh +./run.sh --ko-tar /sdcard/iris_kmods_${KVER}.tar.xz --stack upstream +# The runner unpacks into /run/iris_mods/$KVER and derives --ko-tree or --ko-dir. +``` + +> While resolving and loading modules, the runner logs lines like: +> - `resolve-path: venus_core via KO_TREE => /run/iris_mods/6.9.0/lib/modules/6.9.0/venus_core.ko` +> - `load-path: insmod: /data/kos/qcom_iris.ko` or `load-path: modprobe(system): qcom_iris` + +--- + +## Troubleshooting + +- **“No backup firmware found” on kodiak upstream switch** + Ensure your backups exist under `/opt/video-fw-backups` **or** legacy `/opt` with a name like `vpu20_p1_gen2.mbn..bak`, or set `VIDEO_FW_BACKUP_DIR`. + +- **Upstream/Downstream mismatch** + Check the **pre/post module snapshots**; the runner will explicitly log which modules are present/absent. + +- **No `/dev/video*`** + The runner triggers udev and prunes stale nodes; verify udev is available and rules are active. + +- **Download fails** + Ensure time is sane (TLS), network is reachable, and provide Wi‑Fi creds via env or `ssid_list.txt`. The downloader uses BusyBox‑compatible flags with retries and a final TLS‑lenient attempt if needed. When the network remains unreachable, the runner **SKIPs** decode cases. + +--- + diff --git a/Runner/suites/Multimedia/Video/iris_v4l2_video_decode/h264Decoder.json b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h264Decoder.json similarity index 67% rename from Runner/suites/Multimedia/Video/iris_v4l2_video_decode/h264Decoder.json rename to Runner/suites/Multimedia/Video/Video_V4L2_Runner/h264Decoder.json index 2afb90f4..a2ff932b 100755 --- a/Runner/suites/Multimedia/Video/iris_v4l2_video_decode/h264Decoder.json +++ b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h264Decoder.json @@ -1,20 +1,20 @@ -{ - "ExecutionMode": "Sequential", - "TestCases": [ - { - "Name" : "Decoder Testcase", - "TestConfigs" : { - "Domain": "Decoder", - "InputPath": "./simple_AVC_720p_10fps_90frames.264", - "NumFrames": -1, - "CodecName": "AVC", - "PixelFormat": "NV12", - "Width": 1280, - "Height": 720, - "Outputpath": "./output_simple_nv12_720p_90frms.yuv", - "InputBufferCount": 16, - "OutputBufferCount": 16 - } - } - ] -} +{ + "ExecutionMode": "Sequential", + "TestCases": [ + { + "Name" : "H264 Decoder Testcase", + "TestConfigs" : { + "Domain": "Decoder", + "InputPath": "./720p_AVC.h264", + "NumFrames": -1, + "CodecName": "AVC", + "PixelFormat": "NV12", + "Width": 1280, + "Height": 720, + "Outputpath": "./720p_AVC.yuv", + "InputBufferCount": 16, + "OutputBufferCount": 16 + } + } + ] +} diff --git a/Runner/suites/Multimedia/Video/iris_v4l2_video_encode/h264Encoder.json b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h264Encoder.json similarity index 87% rename from Runner/suites/Multimedia/Video/iris_v4l2_video_encode/h264Encoder.json rename to Runner/suites/Multimedia/Video/Video_V4L2_Runner/h264Encoder.json index 4855b5bd..709b7bfe 100755 --- a/Runner/suites/Multimedia/Video/iris_v4l2_video_encode/h264Encoder.json +++ b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h264Encoder.json @@ -1,30 +1,30 @@ -{ - "ExecutionMode": "Sequential", - "TestCases": [ - { - "Name" : "Encoder Testcase", - "TestConfigs" : { - "Domain": "Encoder", - "InputPath": "./simple_nv12_720p_90frms.yuv", - "NumFrames": -1, - "CodecName": "AVC", - "PixelFormat": "NV12", - "Width": 1280, - "Height": 720, - "Outputpath": "./output_simple_AVC_720p_10fps.h264", - "InputBufferCount": 32, - "OutputBufferCount": 32, - "OperatingRate": 10, - "FrameRate": 10, - "StaticControls": [ - {"Id": "Profile", "Vtype": "String", "Value": "BASELINE"}, - {"Id": "Level", "Vtype": "String", "Value": "5.0"}, - {"Id": "FrameRC", "Vtype": "Int", "Value": 1}, - {"Id": "BitRate", "Vtype": "Int", "Value": 18000000}, - {"Id": "BitRateMode", "Vtype": "String", "Value": "CBR"}, - {"Id": "PrefixHeaderMode", "Vtype": "String", "Value": "JOINED"} - ] - } - } - ] -} +{ + "ExecutionMode": "Sequential", + "TestCases": [ + { + "Name" : "Encoder Testcase", + "TestConfigs" : { + "Domain": "Encoder", + "InputPath": "./90frames_yuv.yuv", + "NumFrames": -1, + "CodecName": "AVC", + "PixelFormat": "NV12", + "Width": 1280, + "Height": 720, + "Outputpath": "./Enc_AVC_720p.h264", + "InputBufferCount": 32, + "OutputBufferCount": 32, + "OperatingRate": 10, + "FrameRate": 10, + "StaticControls": [ + {"Id": "Profile", "Vtype": "String", "Value": "BASELINE"}, + {"Id": "Level", "Vtype": "String", "Value": "5.0"}, + {"Id": "FrameRC", "Vtype": "Int", "Value": 1}, + {"Id": "BitRate", "Vtype": "Int", "Value": 18000000}, + {"Id": "BitRateMode", "Vtype": "String", "Value": "CBR"}, + {"Id": "PrefixHeaderMode", "Vtype": "String", "Value": "JOINED"} + ] + } + } + ] +} diff --git a/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h265Decoder.json b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h265Decoder.json new file mode 100755 index 00000000..fa9bb581 --- /dev/null +++ b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h265Decoder.json @@ -0,0 +1,21 @@ +{ + "ExecutionMode": "Sequential", + "TestCases": [ + { + "Name": "HEVC Decoder TestCase", + "TestConfigs": { + "Domain": "Decoder", + "InputPath": "./720x1280_hevc.h265", + "NumFrames": -1, + "CodecName": "HEVC", + "PixelFormat": "NV12", + "Width": 1280, + "Height": 720, + "Outputpath": "./720x1280_hevc.yuv", + "InputBufferCount": 16, + "OutputBufferCount": 16, + "UseMinBufferCtrl": false + } + } + ] +} diff --git a/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h265Encoder.json b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h265Encoder.json new file mode 100755 index 00000000..846611c8 --- /dev/null +++ b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h265Encoder.json @@ -0,0 +1,67 @@ +{ + "ExecutionMode": "Sequential", + "TestCases": [ + { + "Name": "HEVC Encode TestCase", + "TestConfigs": { + "VideoDevice": "/dev/video1", + "Domain": "Encoder", + "InputPath": "./60frames_yuv.yuv", + "NumFrames": 60, + "CodecName": "HEVC", + "PixelFormat": "NV12", + "Width": 1280, + "Height": 720, + "Outputpath": "./Enc_HEVC__Main_1280_720.265", + "UseMinBufferCtrl": false, + "InputBufferCount": 32, + "OutputBufferCount": 32, + "OperatingRate": 30, + "FrameRate": 30, + "StaticControls": [ + { + "Id": "Profile", + "Vtype": "String", + "Value": "MAIN" + }, + { + "Id": "Level", + "Vtype": "String", + "Value": "5.0" + }, + { + "Id": "FrameRC", + "Vtype": "Int", + "Value": 1 + }, + { + "Id": "BitRate", + "Vtype": "Int", + "Value": 3662400 + }, + { + "Id": "BitRateMode", + "Vtype": "String", + "Value": "VBR" + }, + { + "Id": "PrefixHeaderMode", + "Vtype": "String", + "Value": "JOINED" + }, + { + "Id": "Tier", + "Vtype": "String", + "Value": "HIGH" + }, + { + "Id": "GOPSize", + "Vtype": "Int", + "Value": 59 + } + ], + "DynamicControls": [] + } + } + ] +} \ No newline at end of file diff --git a/Runner/suites/Multimedia/Video/Video_V4L2_Runner/run.sh b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/run.sh new file mode 100755 index 00000000..4681b7a2 --- /dev/null +++ b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/run.sh @@ -0,0 +1,1391 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause-Clear +# IRIS Video V4L2 runner with stack selection via utils/lib_video.sh + +# ---------- Repo env + helpers ---------- +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" +INIT_ENV="" +SEARCH="$SCRIPT_DIR" + +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +# Only source once (idempotent) +# NOTE: We intentionally **do not export** any new vars. They stay local to this shell. +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" +# shellcheck disable=SC1091 +. "$TOOLS/lib_video.sh" + +TESTNAME="Video_V4L2_Runner" +RES_FILE="./${TESTNAME}.res" + +if [ -z "${TAR_URL:-}" ]; then + TAR_URL="https://github.com/qualcomm-linux/qcom-linux-testkit/releases/download/IRIS-Video-Files-v1.0/video_clips_iris.tar.gz" +fi + +# --- Defaults / knobs --- +if [ -z "${TIMEOUT:-}" ]; then TIMEOUT="60"; fi +if [ -z "${STRICT:-}" ]; then STRICT="0"; fi +if [ -z "${DMESG_SCAN:-}" ]; then DMESG_SCAN="1"; fi +PATTERN="" +if [ -z "${MAX:-}" ]; then MAX="0"; fi +if [ -z "${STOP_ON_FAIL:-}" ]; then STOP_ON_FAIL="0"; fi +DRY="0" +if [ -z "${EXTRACT_INPUT_CLIPS:-}" ]; then EXTRACT_INPUT_CLIPS="true"; fi +if [ -z "${SUCCESS_RE:-}" ]; then SUCCESS_RE="SUCCESS"; fi +if [ -z "${LOGLEVEL:-}" ]; then LOGLEVEL="15"; fi +if [ -z "${REPEAT:-}" ]; then REPEAT="1"; fi +if [ -z "${REPEAT_DELAY:-}" ]; then REPEAT_DELAY="0"; fi +if [ -z "${REPEAT_POLICY:-}" ]; then REPEAT_POLICY="all"; fi +JUNIT_OUT="" +VERBOSE="0" + +# --- Stabilizers (opt-in) --- +RETRY_ON_FAIL="0" # extra attempts after a FAIL +POST_TEST_SLEEP="0" # settle time after each case + +# --- Custom module source (opt-in; default is untouched) --- +KO_DIRS="" # colon-separated list of dirs that contain .ko files +KO_TREE="" # alt root that has lib/modules/$KVER +KO_TARBALL="" # optional tarball that we unpack once +KO_PREFER_CUSTOM="0" # 1 = try custom first; default 0 = system first + +# --- Opt-in: custom media bundle tar (always honored even with --dir/--config) --- +CLIPS_TAR="" # /path/to/clips.tar[.gz|.xz|.zst|.bz2|.tgz|.tbz2|.zip] +CLIPS_DEST="" # optional extraction destination; defaults to cfg/dir root or testcase dir + +if [ -z "${VIDEO_STACK:-}" ]; then VIDEO_STACK="auto"; fi +if [ -z "${VIDEO_PLATFORM:-}" ]; then VIDEO_PLATFORM=""; fi +if [ -z "${VIDEO_FW_DS:-}" ]; then VIDEO_FW_DS=""; fi +if [ -z "${VIDEO_FW_BACKUP_DIR:-}" ]; then VIDEO_FW_BACKUP_DIR=""; fi +if [ -z "${VIDEO_NO_REBOOT:-}" ]; then VIDEO_NO_REBOOT="0"; fi +if [ -z "${VIDEO_FORCE:-}" ]; then VIDEO_FORCE="0"; fi +if [ -z "${VIDEO_APP:-}" ]; then VIDEO_APP="/usr/bin/iris_v4l2_test"; fi + +# --- Net/DL tunables (no-op if helpers ignore them) --- +if [ -z "${NET_STABILIZE_SLEEP:-}" ]; then NET_STABILIZE_SLEEP="5"; fi +if [ -z "${WGET_TIMEOUT_SECS:-}" ]; then WGET_TIMEOUT_SECS="120"; fi +if [ -z "${WGET_TRIES:-}" ]; then WGET_TRIES="2"; fi + +# --- Stability sleeps --- +if [ -z "${APP_LAUNCH_SLEEP:-}" ]; then APP_LAUNCH_SLEEP="1"; fi +if [ -z "${INTER_TEST_SLEEP:-}" ]; then INTER_TEST_SLEEP="2"; fi + +# --- New: log flavor for --stack both sub-runs --- +LOG_FLAVOR="" + +usage() { + cat </dev/null || true + if [ ! -x "$VIDEO_APP" ]; then + log_warn "App $VIDEO_APP is not executable and chmod failed; attempting to run anyway." + fi +fi + +# --- Optional: unpack a custom module tarball **once** (no env exports) --- +KVER="$(uname -r 2>/dev/null || printf '%s' unknown)" +if [ -n "$KO_TARBALL" ] && [ -f "$KO_TARBALL" ]; then + DEST="/run/iris_mods/$KVER" + if [ ! -d "$DEST" ]; then + mkdir -p "$DEST" 2>/dev/null || true + case "$KO_TARBALL" in + *.tar|*.tar.gz|*.tgz|*.tar.xz|*.txz|*.tar.zst) + if command -v tar >/dev/null 2>&1; then + # best-effort; keep extraction bounded to DEST + tar -xf "$KO_TARBALL" -C "$DEST" 2>/dev/null || true + fi + ;; + *) + : + ;; + esac + fi + if [ -d "$DEST/lib/modules/$KVER" ]; then + KO_TREE="$DEST" + else + first_ko_dir="$(find "$DEST" -type f -name '*.ko*' -maxdepth 3 2>/dev/null | head -n1 | xargs -r dirname)" + if [ -n "$first_ko_dir" ]; then + if [ -n "$KO_DIRS" ]; then + KO_DIRS="$first_ko_dir:$KO_DIRS" + else + KO_DIRS="$first_ko_dir" + fi + fi + fi + log_info "Custom module source prepared (tree='${KO_TREE:-none}', dirs='${KO_DIRS:-none}', prefer_custom=$KO_PREFER_CUSTOM)" +fi + +if [ -n "$VIDEO_APP" ] && [ -f "$VIDEO_APP" ] && [ ! -x "$VIDEO_APP" ]; then + chmod +x "$VIDEO_APP" 2>/dev/null || true + if [ ! -x "$VIDEO_APP" ]; then + log_warn "App $VIDEO_APP is not executable and chmod failed; attempting to run anyway." + fi +fi + +# ---- Default firmware path for Kodiak downstream if CLI not given ---- +if [ -z "${VIDEO_FW_DS:-}" ]; then + default_fw="/data/vendor/iris_test_app/firmware/vpu20_1v.mbn" + if [ -f "$default_fw" ]; then + VIDEO_FW_DS="$default_fw" + export VIDEO_FW_DS + log_info "Using default downstream firmware path: $VIDEO_FW_DS" + fi +fi + +# Decide final app path: if --app given, require it; otherwise search PATH, /usr/bin, /data/vendor/iris_test_app +final_app="" + +if [ -n "$VIDEO_APP" ] && [ -x "$VIDEO_APP" ]; then + final_app="$VIDEO_APP" +else + if command -v iris_v4l2_test >/dev/null 2>&1; then + final_app="$(command -v iris_v4l2_test)" + else + if [ -x "/usr/bin/iris_v4l2_test" ]; then + final_app="/usr/bin/iris_v4l2_test" + else + if [ -x "/data/vendor/iris_test_app/iris_v4l2_test" ]; then + final_app="/data/vendor/iris_test_app/iris_v4l2_test" + fi + fi + fi +fi + +if [ -z "$final_app" ]; then + log_skip "$TESTNAME SKIP - iris_v4l2_test not available (VIDEO_APP=$VIDEO_APP). Provide --app or install the binary." + printf '%s\n' "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + +VIDEO_APP="$final_app" +export VIDEO_APP + +# --- Resolve testcase path and cd so outputs land here --- +if ! check_dependencies grep sed awk find sort; then + log_skip "$TESTNAME SKIP - required tools missing" + printf '%s\n' "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + +test_path="$(find_test_case_by_name "$TESTNAME" 2>/dev/null || echo "$SCRIPT_DIR")" + +if ! cd "$test_path"; then + log_error "cd failed: $test_path" + printf '%s\n' "$TESTNAME FAIL" >"$RES_FILE" + exit 1 +fi + +# --- New: split logs by flavor, share bundle cache at root --- +LOG_ROOT="./logs_${TESTNAME}" +LOG_DIR="$LOG_ROOT" + +if [ -n "$LOG_FLAVOR" ]; then + LOG_DIR="$LOG_ROOT/$LOG_FLAVOR" +fi + +mkdir -p "$LOG_DIR" +export LOG_DIR +export LOG_ROOT + +# --- Detect top-level vs sub-run (when --stack both re-execs itself) --- +TOP_LEVEL_RUN="1" +if [ -n "$LOG_FLAVOR" ]; then + TOP_LEVEL_RUN="0" +fi + +# --- Opt-in local media bundle extraction (honored regardless of --config/--dir) --- +if [ -n "$CLIPS_TAR" ]; then + # destination resolution: explicit --clips-dest > cfg dir > --dir > testcase dir + clips_dest_resolved="$CLIPS_DEST" + if [ -z "$clips_dest_resolved" ]; then + if [ -n "$CFG" ] && [ -f "$CFG" ]; then + clips_dest_resolved="$(cd "$(dirname "$CFG")" 2>/dev/null && pwd)" + elif [ -n "$DIR" ] && [ -d "$DIR" ]; then + clips_dest_resolved="$DIR" + else + clips_dest_resolved="$test_path" + fi + fi + mkdir -p "$clips_dest_resolved" 2>/dev/null || true + video_step "" "Extract custom clips tar → $clips_dest_resolved" + case "$CLIPS_TAR" in + *.tar|*.tar.gz|*.tgz|*.tar.xz|*.txz|*.tar.zst|*.tar.bz2|*.tbz2) + if command -v tar >/dev/null 2>&1; then + tar -xf "$CLIPS_TAR" -C "$clips_dest_resolved" 2>/dev/null || true + else + log_warn "tar not available; cannot extract --clips-tar" + fi + ;; + *.zip) + if command -v unzip >/dev/null 2>&1; then + unzip -o "$CLIPS_TAR" -d "$clips_dest_resolved" >/dev/null 2>&1 || true + else + log_warn "unzip not available; cannot extract --clips-tar" + fi + ;; + *) + log_warn "Unrecognized archive type for --clips-tar: $CLIPS_TAR" + ;; + esac +fi + +# Ensure rootfs meets minimum size (2GiB) BEFORE any downloads — only once +if [ "$TOP_LEVEL_RUN" -eq 1 ]; then + ensure_rootfs_min_size 2 +else + log_info "Sub-run: skipping rootfs size check (already performed)." +fi + +# If we're going to fetch, ensure network is online first — only once +if [ "$TOP_LEVEL_RUN" -eq 1 ]; then + if [ "$EXTRACT_INPUT_CLIPS" = "true" ] && [ -z "$CFG" ] && [ -z "$DIR" ] && [ -z "$CLIPS_TAR" ]; then + net_rc=1 + + if command -v check_network_status_rc >/dev/null 2>&1; then + check_network_status_rc + net_rc=$? + elif command -v check_network_status >/dev/null 2>&1; then + check_network_status >/dev/null 2>&1 + net_rc=$? + fi + + if [ "$net_rc" -ne 0 ]; then + video_step "" "Bring network online (Wi-Fi credentials if provided)" + ensure_network_online || true + sleep "${NET_STABILIZE_SLEEP:-5}" + else + sleep "${NET_STABILIZE_SLEEP:-5}" + fi + fi +else + log_info "Sub-run: skipping initial network bring-up." +fi + +# --- Early guard: bail out BEFORE any download if Kodiak-downstream lacks --downstream-fw --- +early_plat="$VIDEO_PLATFORM" +if [ -z "$early_plat" ]; then + early_plat="$(video_detect_platform)" +fi + +early_stack="$(video_normalize_stack "$VIDEO_STACK")" + +if [ "$early_plat" = "kodiak" ] && [ "$early_stack" = "downstream" ] && [ -z "${VIDEO_FW_DS:-}" ]; then + log_skip "On Kodiak, downstream/overlay requires --downstream-fw ; skipping run." + printf '%s\n' "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + +# --- Optional early fetch of bundle (best-effort, ALWAYS in LOG_ROOT) — only once +if [ "$TOP_LEVEL_RUN" -eq 1 ]; then + if [ "$EXTRACT_INPUT_CLIPS" = "true" ] && [ -z "$CFG" ] && [ -z "$DIR" ]; then + if [ -n "$CLIPS_TAR" ]; then + log_info "Custom --clips-tar provided; skipping online early fetch." + else + video_step "" "Early bundle fetch (best-effort)" + + saved_log_dir="$LOG_DIR" + LOG_DIR="$LOG_ROOT" + export LOG_DIR + + if command -v check_network_status_rc >/dev/null 2>&1; then + if ! check_network_status_rc; then + log_info "Network unreachable; skipping early media bundle fetch." + else + extract_tar_from_url "$TAR_URL" || true + fi + else + extract_tar_from_url "$TAR_URL" || true + fi + + LOG_DIR="$saved_log_dir" + export LOG_DIR + fi + else + log_info "Skipping early bundle fetch (explicit --config/--dir provided or EXTRACT_INPUT_CLIPS=false)." + fi +else + log_info "Sub-run: skipping early bundle fetch." +fi + +# --- If user asked for both stacks, re-invoke ourselves for base and overlay --- +if [ "${VIDEO_STACK}" = "both" ]; then + build_reexec_args() { + args="" + + if [ -n "${CFG:-}" ]; then + esc_cfg="$(printf %s "$CFG" | sed "s/'/'\\\\''/g")" + args="$args --config '$esc_cfg'" + fi + + if [ -n "${DIR:-}" ]; then + esc_dir="$(printf %s "$DIR" | sed "s/'/'\\\\''/g")" + args="$args --dir '$esc_dir'" + fi + + if [ -n "${PATTERN:-}" ]; then + esc_pat="$(printf %s "$PATTERN" | sed "s/'/'\\\\''/g")" + args="$args --pattern '$esc_pat'" + fi + + if [ -n "${TIMEOUT:-}" ]; then + args="$args --timeout $(printf %s "$TIMEOUT")" + fi + + if [ "${STRICT:-0}" -eq 1 ]; then + args="$args --strict" + fi + + if [ "${DMESG_SCAN:-1}" -eq 0 ]; then + args="$args --no-dmesg" + fi + + if [ -n "${MAX:-}" ] && [ "$MAX" -gt 0 ] 2>/dev/null; then + args="$args --max $MAX" + fi + + if [ "${STOP_ON_FAIL:-0}" -eq 1 ]; then + args="$args --stop-on-fail" + fi + + if [ -n "${LOGLEVEL:-}" ]; then + args="$args --loglevel $(printf %s "$LOGLEVEL")" + fi + + if [ -n "${REPEAT:-}" ]; then + args="$args --repeat $(printf %s "$REPEAT")" + fi + + if [ -n "${REPEAT_DELAY:-}" ]; then + args="$args --repeat-delay $(printf %s "$REPEAT_DELAY")" + fi + + if [ -n "${REPEAT_POLICY:-}" ]; then + esc_pol="$(printf %s "$REPEAT_POLICY" | sed "s/'/'\\\\''/g")" + args="$args --repeat-policy '$esc_pol'" + fi + + if [ -n "${JUNIT_OUT:-}" ]; then + esc_junit="$(printf %s "$JUNIT_OUT" | sed "s/'/'\\\\''/g")" + args="$args --junit '$esc_junit'" + fi + + if [ "${DRY:-0}" -eq 1 ]; then + args="$args --dry-run" + fi + + if [ -n "${EXTRACT_INPUT_CLIPS:-}" ] && [ "$EXTRACT_INPUT_CLIPS" != "true" ]; then + args="$args --extract-input-clips $(printf %s "$EXTRACT_INPUT_CLIPS")" + fi + + if [ "${VERBOSE:-0}" -eq 1 ]; then + args="$args --verbose" + fi + + if [ -n "${VIDEO_PLATFORM:-}" ]; then + esc_plat="$(printf %s "$VIDEO_PLATFORM" | sed "s/'/'\\\\''/g")" + args="$args --platform '$esc_plat'" + fi + + if [ -n "${VIDEO_FW_DS:-}" ]; then + esc_fw="$(printf %s "$VIDEO_FW_DS" | sed "s/'/'\\\\''/g")" + args="$args --downstream-fw '$esc_fw'" + fi + + if [ "${VIDEO_FORCE:-0}" -eq 1 ]; then + args="$args --force" + fi + + if [ -n "${VIDEO_APP:-}" ]; then + esc_app="$(printf %s "$VIDEO_APP" | sed "s/'/'\\\\''/g")" + args="$args --app '$esc_app'" + fi + + if [ -n "${SSID:-}" ]; then + esc_ssid="$(printf %s "$SSID" | sed "s/'/'\\\\''/g")" + args="$args --ssid '$esc_ssid'" + fi + + if [ -n "${PASSWORD:-}" ]; then + esc_pwd="$(printf %s "$PASSWORD" | sed "s/'/'\\\\''/g")" + args="$args --password '$esc_pwd'" + fi + + if [ -n "${APP_LAUNCH_SLEEP:-}" ]; then + args="$args --app-launch-sleep $(printf %s "$APP_LAUNCH_SLEEP")" + fi + + if [ -n "${INTER_TEST_SLEEP:-}" ]; then + args="$args --inter-test-sleep $(printf %s "$INTER_TEST_SLEEP")" + fi + + # --- Stabilizers passthrough --- + if [ -n "${RETRY_ON_FAIL:-}" ]; then + args="$args --retry-on-fail $(printf %s "$RETRY_ON_FAIL")" + fi + if [ -n "${POST_TEST_SLEEP:-}" ]; then + args="$args --post-test-sleep $(printf %s "$POST_TEST_SLEEP")" + fi + + # --- Media bundle passthrough --- + if [ -n "${CLIPS_TAR:-}" ]; then + esc_tar="$(printf %s "$CLIPS_TAR" | sed "s/'/'\\\\''/g")" + args="$args --clips-tar '$esc_tar'" + fi + if [ -n "${CLIPS_DEST:-}" ]; then + esc_dst="$(printf %s "$CLIPS_DEST" | sed "s/'/'\\\\''/g")" + args="$args --clips-dest '$esc_dst'" + fi + + printf "%s" "$args" + } + + reexec_args="$(build_reexec_args)" + + log_info "[both] starting BASE (upstream) pass" + # shellcheck disable=SC2086 + sh -c "'$0' --stack base --log-flavor upstream $reexec_args" + rc_base=$? + + base_res_line="" + if [ -f "$RES_FILE" ]; then + base_res_line="$(cat "$RES_FILE" 2>/dev/null || true)" + fi + + log_info "[both] starting OVERLAY (downstream) pass" + # shellcheck disable=SC2086 + sh -c "'$0' --stack overlay --log-flavor downstream $reexec_args" + rc_overlay=$? + + overlay_res_line="" + if [ -f "$RES_FILE" ]; then + overlay_res_line="$(cat "$RES_FILE" 2>/dev/null || true)" + fi + + base_status="$(printf '%s\n' "$base_res_line" | awk '{print $2}')" + overlay_status="$(printf '%s\n' "$overlay_res_line" | awk '{print $2}')" + + overlay_reason="" + plat_for_reason="$VIDEO_PLATFORM" + if [ -z "$plat_for_reason" ]; then + plat_for_reason="$(video_detect_platform)" + fi + if [ "$overlay_status" = "SKIP" ] && [ "$plat_for_reason" = "kodiak" ] && [ -z "${VIDEO_FW_DS:-}" ]; then + overlay_reason="missing --downstream-fw" + fi + + if [ "$rc_base" -eq 0 ] && [ "$rc_overlay" -eq 0 ] ; then + if [ "$base_status" = "PASS" ] && [ "$overlay_status" = "SKIP" ]; then + if [ -n "$overlay_reason" ]; then + log_info "[both] upstream/base executed and PASS; downstream/overlay SKIP ($overlay_reason). Overall PASS." + else + log_info "[both] upstream/base executed and PASS; downstream/overlay SKIP. Overall PASS." + fi + elif [ "$base_status" = "SKIP" ] && [ "$overlay_status" = "PASS" ]; then + log_info "[both] downstream/overlay executed and PASS; upstream/base SKIP. Overall PASS." + else + log_pass "[both] both passes succeeded" + fi + + printf '%s\n' "$TESTNAME PASS" > "$RES_FILE" + exit 0 + else + log_fail "[both] one or more passes failed (base rc=$rc_base, overlay rc=$rc_overlay; base=$base_status overlay=$overlay_status)" + printf '%s\n' "$TESTNAME FAIL" >"$RES_FILE" + exit 1 + fi +fi + +log_info "----------------------------------------------------------------------" +log_info "---------------------- Starting $TESTNAME (modular) -------------------" +log_info "STACK=$VIDEO_STACK PLATFORM=${VIDEO_PLATFORM:-auto} STRICT=$STRICT DMESG_SCAN=$DMESG_SCAN" +log_info "TIMEOUT=${TIMEOUT}s LOGLEVEL=$LOGLEVEL REPEAT=$REPEAT REPEAT_POLICY=$REPEAT_POLICY" +log_info "APP=$VIDEO_APP" +if [ -n "$VIDEO_FW_DS" ]; then + log_info "Downstream FW override: $VIDEO_FW_DS" +fi +if [ -n "$KO_TREE$KO_DIRS$KO_TARBALL" ]; then + if [ -n "$KO_TREE" ]; then + log_info "Custom module tree (modprobe -d): $KO_TREE" + fi + if [ -n "$KO_DIRS" ]; then + log_info "Custom ko dir(s): $KO_DIRS (prefer_custom=$KO_PREFER_CUSTOM)" + fi +fi +if [ -n "$VIDEO_FW_BACKUP_DIR" ]; then + log_info "FW backup override: $VIDEO_FW_BACKUP_DIR" +fi +if [ "$VERBOSE" -eq 1 ]; then + log_info "CWD=$(pwd) | SCRIPT_DIR=$SCRIPT_DIR | test_path=$test_path" +fi +log_info "SLEEPS: app-launch=${APP_LAUNCH_SLEEP}s, inter-test=${INTER_TEST_SLEEP}s" + +# Warn if not root (module/blacklist ops may fail) +video_warn_if_not_root + +# --- Ensure desired video stack (hot switch best-effort) --- +plat="$VIDEO_PLATFORM" +if [ -z "$plat" ]; then + plat=$(video_detect_platform) +fi +log_info "Detected platform: $plat" + +VIDEO_STACK="$(video_normalize_stack "$VIDEO_STACK")" +pre_stack="$(video_stack_status "$plat")" +log_info "Current video stack (pre): $pre_stack" + +# Kodiak + upstream → install backup firmware to /lib/firmware before switching +if [ "$plat" = "kodiak" ]; then + case "$VIDEO_STACK" in + upstream|up|base) + video_step "" "Kodiak upstream firmware install" + video_kodiak_install_firmware || true + ;; + esac +fi + +# ---- Enforce --downstream-fw on Kodiak when requesting downstream/overlay (SKIP if unmet) ---- +if [ "$plat" = "kodiak" ]; then + case "$VIDEO_STACK" in + downstream|overlay|down) + if [ -z "$VIDEO_FW_DS" ] || [ ! -f "$VIDEO_FW_DS" ]; then + log_skip "On Kodiak, downstream/overlay requires --downstream-fw ; skipping run." + printf '%s\n' "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + ;; + esac +fi + +# --- Optional cleanup: robust capture + normalization of post-stack value --- +video_dump_stack_state "pre" + +# --- Custom .ko staging (only if user provided --ko-dir) --- +if [ -n "${KO_DIRS:-}" ]; then + case "$(video_normalize_stack "$VIDEO_STACK")" in + downstream|overlay|down) + KVER="$(uname -r 2>/dev/null || printf '%s' unknown)" + + if command -v video_find_module_file >/dev/null 2>&1; then + modpath="$(video_find_module_file iris_vpu "$KO_DIRS" 2>/dev/null | tail -n1 | tr -d '\r')" + else + modpath="" + fi + + if [ -n "$modpath" ] && [ -f "$modpath" ]; then + log_info "Using custom iris_vpu candidate: $modpath" + if command -v video_ensure_moddir_install >/dev/null 2>&1; then + video_ensure_moddir_install "$modpath" "$KVER" >/dev/null 2>&1 || true + fi + if command -v depmod >/dev/null 2>&1; then + depmod -a "$KVER" >/dev/null 2>&1 || true + fi + else + log_warn "KO_DIRS set, but iris_vpu.ko not found under: $KO_DIRS" + fi + ;; + esac +fi + +video_step "" "Apply desired stack = $VIDEO_STACK" + +stack_tmp="$LOG_DIR/.ensure_stack.$$.out" +: > "$stack_tmp" + +video_ensure_stack "$VIDEO_STACK" "$plat" >"$stack_tmp" 2>&1 || true + +if [ -s "$stack_tmp" ]; then + total_lines="$(wc -l < "$stack_tmp" 2>/dev/null | tr -d ' ')" + if [ -n "$total_lines" ] && [ "$total_lines" -gt 1 ] 2>/dev/null; then + head -n $((total_lines - 1)) "$stack_tmp" + fi + post_stack="$(tail -n 1 "$stack_tmp" | tr -d '\r')" +else + post_stack="" +fi + +rm -f "$stack_tmp" 2>/dev/null || true + +if [ -z "$post_stack" ] || [ "$post_stack" = "unknown" ]; then + log_warn "Could not fully switch to requested stack=$VIDEO_STACK (platform=$plat). Blacklist updated; reboot may be required." + post_stack="$(video_stack_status "$plat")" +fi + +log_info "Video stack (post): $post_stack" + +video_dump_stack_state "post" + +# --- Custom .ko load assist (only if user provided --ko-dir) --- +if [ -n "${KO_DIRS:-}" ]; then + case "$(video_normalize_stack "$VIDEO_STACK")" in + downstream|overlay|down) + if ! video_has_module_loaded iris_vpu 2>/dev/null; then + if command -v video_find_module_file >/dev/null 2>&1; then + modpath2="$(video_find_module_file iris_vpu "$KO_DIRS" 2>/dev/null | tail -n1 | tr -d '\r')" + else + modpath2="" + fi + + if [ "$KO_PREFER_CUSTOM" = "1" ] && [ -n "$modpath2" ] && [ -f "$modpath2" ]; then + if command -v video_insmod_with_deps >/dev/null 2>&1; then + log_info "Prefer custom: insmod with deps: $modpath2" + video_insmod_with_deps "$modpath2" >/dev/null 2>&1 || true + fi + fi + fi + ;; + esac +fi + +# Always refresh/prune device nodes (even if no switch occurred) +video_step "" "Refresh V4L device nodes (udev trigger + prune stale)" +video_clean_and_refresh_v4l || true + +# --- Hard gate: if requested stack not in effect, abort immediately (platform-aware) +case "$VIDEO_STACK" in + upstream|up|base) + if ! video_validate_upstream_loaded "$plat"; then + case "$plat" in + lemans|monaco) + msg="qcom_iris not both present" + ;; + kodiak) + msg="venus_core/dec/enc not all present" + ;; + *) + msg="required upstream modules not present for platform $plat" + ;; + esac + log_fail "[STACK] Upstream requested but $msg; aborting." + printf '%s\n' "$TESTNAME FAIL" >"$RES_FILE" + exit 1 + fi + ;; + downstream|overlay|down) + if ! video_validate_downstream_loaded "$plat"; then + case "$plat" in + lemans|monaco) + msg="iris_vpu missing or qcom_iris still loaded" + ;; + kodiak) + msg="iris_vpu missing or venus_core still loaded" + ;; + *) + msg="required downstream modules not present for platform $plat" + ;; + esac + log_fail "[STACK] Downstream requested but $msg; aborting." + printf '%s\n' "$TESTNAME FAIL" >"$RES_FILE" + exit 1 + fi + ;; +esac + +# Per-platform module validation (informational) +case "$plat" in + lemans|monaco) + if [ "$post_stack" = "upstream" ]; then + if video_has_module_loaded qcom_iris && video_has_module_loaded iris_vpu; then + log_pass "Upstream validated: qcom_iris + iris_vpu present" + elif video_has_module_loaded qcom_iris && ! video_has_module_loaded iris_vpu; then + log_pass "Upstream validated: qcom_iris present (pure upstream build)" + else + log_warn "Upstream expected but qcom_iris not present" + fi + elif [ "$post_stack" = "downstream" ]; then + if video_has_module_loaded iris_vpu && ! video_has_module_loaded qcom_iris; then + log_pass "Downstream validated: only iris_vpu present" + else + log_warn "Downstream expected but qcom_iris still loaded or iris_vpu missing" + fi + fi + ;; + kodiak) + if [ "$post_stack" = "upstream" ]; then + if video_has_module_loaded venus_core && video_has_module_loaded venus_dec && video_has_module_loaded venus_enc; then + log_pass "Upstream validated: venus_core/dec/enc present" + elif video_has_module_loaded qcom_iris && ! video_has_module_loaded iris_vpu; then + log_pass "Upstream validated: qcom_iris present (pure upstream build on Kodiak)" + else + log_warn "Upstream expected but neither Venus trio nor pure qcom_iris path validated" + fi + elif [ "$post_stack" = "downstream" ]; then + if video_has_module_loaded iris_vpu; then + log_pass "Downstream validated: iris_vpu present (Kodiak)" + else + log_warn "Downstream expected but iris_vpu not present (Kodiak)" + fi + fi + ;; + *) + log_warn "Unknown platform; skipping strict module validation" + ;; +esac + +# Validate numeric loglevel +case "$LOGLEVEL" in + ''|*[!0-9]* ) + log_warn "Non-numeric --loglevel '$LOGLEVEL'; using 15" + LOGLEVEL=15 + ;; +esac + +# --- Discover config list --- +CFG_LIST="$LOG_DIR/.cfgs" +: > "$CFG_LIST" + +if [ -n "$CFG" ] && [ -d "$CFG" ]; then + DIR="$CFG" + CFG="" +fi + +if [ -z "$CFG" ]; then + if [ -n "$DIR" ]; then + base_dir="$DIR" + if [ -n "$PATTERN" ]; then + find "$base_dir" -type f -name "$PATTERN" 2>/dev/null | sort > "$CFG_LIST" + else + find "$base_dir" -type f -name "*.json" 2>/dev/null | sort > "$CFG_LIST" + fi + log_info "Using custom config directory: $base_dir" + else + log_info "No --config passed, searching for JSON under testcase dir: $test_path" + find "$test_path" -type f -name "*.json" 2>/dev/null | sort > "$CFG_LIST" + fi +else + printf '%s\n' "$CFG" > "$CFG_LIST" +fi + +if [ ! -s "$CFG_LIST" ]; then + log_skip "$TESTNAME SKIP - no JSON configs found" + printf '%s\n' "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + +cfg_count="$(wc -l < "$CFG_LIST" 2>/dev/null | tr -d ' ')" +log_info "Discovered $cfg_count JSON config(s) to run" + +# --- JUnit prep / results files --- +JUNIT_TMP="$LOG_DIR/.junit_cases.xml" +: > "$JUNIT_TMP" + +printf '%s\n' "mode,id,result,name,elapsed,pass_runs,fail_runs" > "$LOG_DIR/results.csv" +: > "$LOG_DIR/summary.txt" + +# --- Suite loop --- +total="0" +pass="0" +fail="0" +skip="0" +suite_rc="0" +first_case="1" + +while IFS= read -r cfg; do + if [ -z "$cfg" ]; then + continue + fi + + # Inter-test pause (skip before the very first case) + if [ "$first_case" -eq 0 ] 2>/dev/null; then + case "$INTER_TEST_SLEEP" in + ''|*[!0-9]* ) + : + ;; + 0) + : + ;; + *) + log_info "Inter-test sleep ${INTER_TEST_SLEEP}s" + sleep "$INTER_TEST_SLEEP" + ;; + esac + fi + first_case="0" + + total=$((total + 1)) + + if video_is_decode_cfg "$cfg"; then + mode="decode" + else + mode="encode" + fi + + name_and_id="$(video_pretty_name_from_cfg "$cfg")" + pretty="$(printf '%s' "$name_and_id" | cut -d'|' -f1)" + raw_codec="$(video_guess_codec_from_cfg "$cfg")" + codec="$(video_canon_codec "$raw_codec")" + safe_codec="$(printf '%s' "$codec" | tr ' /' '__')" + base_noext="$(basename "$cfg" .json)" + id="${mode}-${safe_codec}-${base_noext}" + + log_info "----------------------------------------------------------------------" + log_info "[$id] START — mode=$mode codec=$codec name=\"$pretty\" cfg=\"$cfg\"" + + video_step "$id" "Check /dev/video* presence" + if ! video_devices_present; then + log_skip "[$id] SKIP - no /dev/video* nodes" + printf '%s\n' "$id SKIP $pretty" >> "$LOG_DIR/summary.txt" + printf '%s\n' "$mode,$id,SKIP,$pretty,0,0,0" >> "$LOG_DIR/results.csv" + skip=$((skip + 1)) + continue + fi + + # Fetch only when not explicitly provided a config/dir and feature enabled + if [ "$EXTRACT_INPUT_CLIPS" = "true" ] && [ -z "$CFG" ] && [ -z "$DIR" ]; then + if [ -n "$CLIPS_TAR" ]; then + log_info "[$id] Custom --clips-tar provided; skipping online per-test fetch." + ce=0 + else + video_step "$id" "Ensure clips present or fetch" + + saved_log_dir_case="$LOG_DIR" + LOG_DIR="$LOG_ROOT" + export LOG_DIR + + video_ensure_clips_present_or_fetch "$cfg" "$TAR_URL" + ce=$? + + LOG_DIR="$saved_log_dir_case" + export LOG_DIR + + # Map generic download errors to "offline" if link just flapped + if [ "$ce" -eq 1 ] 2>/dev/null; then + sleep "${NET_STABILIZE_SLEEP:-5}" + + if command -v check_network_status_rc >/dev/null 2>&1; then + if ! check_network_status_rc; then + ce=2 + fi + elif command -v check_network_status >/dev/null 2>&1; then + if ! check_network_status >/dev/null 2>&1; then + ce=2 + fi + fi + fi + + if [ "$ce" -eq 2 ] 2>/dev/null; then + if [ "$mode" = "decode" ]; then + log_skip "[$id] SKIP - offline and clips missing (decode case)" + printf '%s\n' "$id SKIP $pretty" >> "$LOG_DIR/summary.txt" + printf '%s\n' "$mode,$id,SKIP,$pretty,0,0,0" >> "$LOG_DIR/results.csv" + skip=$((skip + 1)) + continue + fi + elif [ "$ce" -eq 1 ] 2>/dev/null; then + log_fail "[$id] FAIL - fetch/extract failed while online" + printf '%s\n' "$id FAIL $pretty" >> "$LOG_DIR/summary.txt" + printf '%s\n' "$mode,$id,FAIL,$pretty,0,0,0" >> "$LOG_DIR/results.csv" + fail=$((fail + 1)) + suite_rc=1 + + if [ "$STOP_ON_FAIL" -eq 1 ]; then + break + fi + + continue + fi + fi + else + log_info "[$id] Fetch disabled (explicit --config/--dir)." + fi + + # Strict clip existence check after optional fetch + video_step "$id" "Verify required clips exist" + missing_case="0" + clips_file="$LOG_DIR/.clips.$$" + + video_extract_input_clips "$cfg" > "$clips_file" + + if [ -s "$clips_file" ]; then + while IFS= read -r pth; do + if [ -z "$pth" ]; then + continue + fi + + case "$pth" in + /*) + abs="$pth" + ;; + *) + abs="$(cd "$(dirname "$cfg")" 2>/dev/null && pwd)/$pth" + ;; + esac + + if [ ! -f "$abs" ]; then + missing_case=1 + fi + done < "$clips_file" + fi + + rm -f "$clips_file" 2>/dev/null || true + + if [ "$missing_case" -eq 1 ] 2>/dev/null; then + log_fail "[$id] Required input clip(s) not present — $pretty" + printf '%s\n' "$id FAIL $pretty" >> "$LOG_DIR/summary.txt" + printf '%s\n' "$mode,$id,FAIL,$pretty,$elapsed,0,0" >> "$LOG_DIR/results.csv" + fail=$((fail + 1)) + suite_rc=1 + + if [ "$STOP_ON_FAIL" -eq 1 ]; then + break + fi + + continue + fi + + if [ "$DRY" -eq 1 ]; then + video_step "$id" "DRY RUN - print command" + log_info "[dry] [$id] $VIDEO_APP --config \"$cfg\" --loglevel $LOGLEVEL — $pretty" + printf '%s\n' "$id DRY-RUN $pretty" >> "$LOG_DIR/summary.txt" + printf '%s\n' "$mode,$id,DRY-RUN,$pretty,0,0,0" >> "$LOG_DIR/results.csv" + continue + fi + + pass_runs="0" + fail_runs="0" + rep="1" + start_case="$(date +%s 2>/dev/null || printf '%s' 0)" + logf="$LOG_DIR/${id}.log" + + while [ "$rep" -le "$REPEAT" ]; do + if [ "$REPEAT" -gt 1 ]; then + log_info "[$id] repeat $rep/$REPEAT — $pretty" + fi + + video_step "$id" "Execute app" + log_info "[$id] CMD: $VIDEO_APP --config \"$cfg\" --loglevel $LOGLEVEL" + + case "$APP_LAUNCH_SLEEP" in + ''|*[!0-9]* ) + : + ;; + 0) + : + ;; + *) + log_info "[$id] pre-launch sleep ${APP_LAUNCH_SLEEP}s" + sleep "$APP_LAUNCH_SLEEP" + ;; + esac + + if video_run_once "$cfg" "$logf" "$TIMEOUT" "$SUCCESS_RE" "$LOGLEVEL"; then + pass_runs=$((pass_runs + 1)) + else + rc_val="$(awk -F'=' '/^END-RUN rc=/{print $2}' "$logf" 2>/dev/null | tail -n1 | tr -d ' ')" + if [ -n "$rc_val" ] 2>/dev/null; then + case "$rc_val" in + 139) log_warn "[$id] App exited rc=139 (SIGSEGV)." ;; + 134) log_warn "[$id] App exited rc=134 (SIGABRT)." ;; + 137) log_warn "[$id] App exited rc=137 (SIGKILL/OOM?)." ;; + *) : ;; + esac + fi + fail_runs=$((fail_runs + 1)) + fi + + if [ "$rep" -lt "$REPEAT" ] && [ "$REPEAT_DELAY" -gt 0 ]; then + sleep "$REPEAT_DELAY" + fi + + rep=$((rep + 1)) + done + + end_case="$(date +%s 2>/dev/null || printf '%s' 0)" + elapsed=$((end_case - start_case)) + if [ "$elapsed" -lt 0 ] 2>/dev/null; then + elapsed=0 + fi + + final="FAIL" + case "$REPEAT_POLICY" in + any) + if [ "$pass_runs" -ge 1 ]; then + final="PASS" + fi + ;; + all|*) + if [ "$fail_runs" -eq 0 ]; then + final="PASS" + fi + ;; + esac + + video_step "$id" "DMESG triage" + video_scan_dmesg_if_enabled "$DMESG_SCAN" "$LOG_DIR" + dmesg_rc=$? + + if [ "$dmesg_rc" -eq 0 ]; then + log_warn "[$id] dmesg reported errors (STRICT=$STRICT)" + if [ "$STRICT" -eq 1 ]; then + final="FAIL" + fi + fi + + # (2) Retry on final failure (extra attempts outside REPEAT loop, before recording results) + if [ "$final" = "FAIL" ] && [ "$RETRY_ON_FAIL" -gt 0 ] 2>/dev/null; then + r=1 + log_info "[$id] RETRY_ON_FAIL: up to $RETRY_ON_FAIL additional attempt(s)" + while [ "$r" -le "$RETRY_ON_FAIL" ]; do + if [ "$REPEAT_DELAY" -gt 0 ] 2>/dev/null; then + sleep "$REPEAT_DELAY" + fi + + log_info "[$id] retry attempt $r/$RETRY_ON_FAIL" + if video_run_once "$cfg" "$logf" "$TIMEOUT" "$SUCCESS_RE" "$LOGLEVEL"; then + pass_runs=$((pass_runs + 1)) + final="PASS" + log_pass "[$id] RETRY succeeded — marking PASS" + break + else + rc_val="$(awk -F'=' '/^END-RUN rc=/{print $2}' "$logf" 2>/dev/null | tail -n1 | tr -d ' ')" + if [ -n "$rc_val" ]; then + case "$rc_val" in + 139) log_warn "[$id] Retry exited rc=139 (SIGSEGV)." ;; + 134) log_warn "[$id] Retry exited rc=134 (SIGABORT)." ;; + 137) log_warn "[$id] Retry exited rc=137 (SIGKILL/OOM?)." ;; + *) : ;; + esac + fi + fi + r=$((r + 1)) + done + fi + + { + printf 'RESULT id=%s mode=%s pretty="%s" final=%s pass_runs=%s fail_runs=%s elapsed=%s\n' \ + "$id" "$mode" "$pretty" "$final" "$pass_runs" "$fail_runs" "$elapsed" + } >> "$logf" 2>&1 + + video_junit_append_case "$JUNIT_TMP" "Video.$mode" "$pretty" "$elapsed" "$final" "$logf" + + case "$final" in + PASS) + log_pass "[$id] PASS ($pass_runs/$REPEAT ok) — $pretty" + ;; + FAIL) + log_fail "[$id] FAIL (pass=$pass_runs fail=$fail_runs) — $pretty" + ;; + SKIP) + log_skip "[$id] SKIP — $pretty" + ;; + esac + + printf '%s\n' "$id $final $pretty" >> "$LOG_DIR/summary.txt" + printf '%s\n' "$mode,$id,$final,$pretty,$elapsed,$pass_runs,$fail_runs" >> "$LOG_DIR/results.csv" + + if [ "$final" = "PASS" ]; then + pass=$((pass + 1)) + else + fail=$((fail + 1)) + suite_rc=1 + + if [ "$STOP_ON_FAIL" -eq 1 ]; then + break + fi + fi + + case "$POST_TEST_SLEEP" in + ''|*[!0-9]* ) + : + ;; + 0) : ;; + *) log_info "Post-test sleep ${POST_TEST_SLEEP}s"; sleep "$POST_TEST_SLEEP" ;; + esac + + if [ "$MAX" -gt 0 ] && [ "$total" -ge "$MAX" ]; then + log_info "Reached MAX=$MAX tests; stopping" + break + fi +done < "$CFG_LIST" + +log_info "Summary: total=$total pass=$pass fail=$fail skip=$skip" + +# --- End-of-run detailed per-test results --- +if [ -s "$LOG_DIR/summary.txt" ]; then + log_info "----------------------------------------------------------------------" + log_info "Per-test results (id result):" + while IFS= read -r line; do + id_field=$(printf '%s\n' "$line" | awk '{print $1}') + res_field=$(printf '%s\n' "$line" | awk '{print $2}') + if [ -n "$id_field" ] && [ -n "$res_field" ]; then + log_info "$id_field $res_field" + fi + done < "$LOG_DIR/summary.txt" +fi + +# --- Aggregate breakdown by mode/codec --- +if [ -s "$LOG_DIR/results.csv" ]; then + log_info "----------------------------------------------------------------------" + log_info "Mode/codec breakdown (total/pass/fail/skip):" + awk -F',' 'NR>1 { + id=$2; res=$3; + split(id,a,"-"); mode=a[1]; codec=a[2]; + key=mode "-" codec; + total[key]++ + if (res=="PASS") pass[key]++ + else if (res=="FAIL") fail[key]++ + else if (res=="SKIP") skip[key]++ + } + END { + for (k in total) { + printf " %s: total=%d pass=%d fail=%d skip=%d\n", k, total[k], pass[k]+0, fail[k]+0, skip[k]+0 + } + }' "$LOG_DIR/results.csv" | while IFS= read -r ln; do + if [ -n "$ln" ]; then + log_info "$ln" + fi + done +fi + +# --- JUnit finalize --- +if [ -n "$JUNIT_OUT" ]; then + tests=$((pass + fail + skip)) + failures="$fail" + skipped="$skip" + + { + printf '\n' "$TESTNAME" "$tests" "$failures" "$skipped" + cat "$JUNIT_TMP" + printf '\n' + } > "$JUNIT_OUT" + + log_info "Wrote JUnit: $JUNIT_OUT" +fi + +# Overall suite result (single-stack path): +# If ALL testcases were skipped (pass=0, fail=0, skip>0) => overall SKIP. +# Otherwise: suite_rc==0 -> PASS, else FAIL. +if [ "$pass" -eq 0 ] && [ "$fail" -eq 0 ] && [ "$skip" -gt 0 ]; then + log_skip "$TESTNAME: SKIP (all $skip test(s) skipped)" + printf '%s\n' "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + +if [ "$suite_rc" -eq 0 ] 2>/dev/null; then + log_pass "$TESTNAME: PASS" + printf '%s\n' "$TESTNAME PASS" >"$RES_FILE" + exit 0 +else + log_fail "$TESTNAME: FAIL" + printf '%s\n' "$TESTNAME FAIL" >"$RES_FILE" + exit 1 +fi + +exit "$suite_rc" diff --git a/Runner/suites/Multimedia/Video/Video_V4L2_Runner/vp9Decoder.json b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/vp9Decoder.json new file mode 100755 index 00000000..e38d4f95 --- /dev/null +++ b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/vp9Decoder.json @@ -0,0 +1,21 @@ +{ + "ExecutionMode": "Sequential", + "TestCases": [ + { + "Name": "VP9 Decode TestCase", + "TestConfigs": { + "Domain": "Decoder", + "InputPath": "./720x1280_vp9.ivf", + "NumFrames": -1, + "CodecName": "VP9", + "PixelFormat": "NV12", + "Width": 1280, + "Height": 720, + "Outputpath": "./720x1280_vp9.yuv", + "InputBufferCount": 16, + "OutputBufferCount": 16, + "UseMinBufferCtrl": false + } + } + ] +} diff --git a/Runner/suites/Multimedia/Video/iris_v4l2_video_decode/run.sh b/Runner/suites/Multimedia/Video/iris_v4l2_video_decode/run.sh deleted file mode 100755 index 88e6f7bc..00000000 --- a/Runner/suites/Multimedia/Video/iris_v4l2_video_decode/run.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/sh - -# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. -# SPDX-License-Identifier: BSD-3-Clause-Clear - -# Robustly find and source init_env -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -INIT_ENV="" -SEARCH="$SCRIPT_DIR" -while [ "$SEARCH" != "/" ]; do - if [ -f "$SEARCH/init_env" ]; then - INIT_ENV="$SEARCH/init_env" - break - fi - SEARCH=$(dirname "$SEARCH") -done - -if [ -z "$INIT_ENV" ]; then - echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 - exit 1 -fi - -# Only source if not already loaded (idempotent) -if [ -z "$__INIT_ENV_LOADED" ]; then - # shellcheck disable=SC1090 - . "$INIT_ENV" -fi -# Always source functestlib.sh, using $TOOLS exported by init_env -# shellcheck disable=SC1090,SC1091 -. "$TOOLS/functestlib.sh" - -TESTNAME="iris_v4l2_video_decode" -test_path=$(find_test_case_by_name "$TESTNAME") -cd "$test_path" || exit 1 -# shellcheck disable=SC2034 -res_file="./$TESTNAME.res" -TAR_URL="https://github.com/qualcomm-linux/qcom-linux-testkit/releases/download/IRIS-Video-Files-v1.0/video_clips_iris.tar.gz" - -log_info "-----------------------------------------------------------------------------------------" -log_info "-------------------Starting $TESTNAME Testcase----------------------------" -log_info "=== Test Initialization ===" - -log_info "Checking if dependency binary is available" -check_dependencies iris_v4l2_test -extract_tar_from_url "$TAR_URL" - -# Run the first test -iris_v4l2_test --config "${test_path}/h264Decoder.json" --loglevel 15 >> "${test_path}/video_dec.txt" - -if grep -q "SUCCESS" "${test_path}/video_dec.txt"; then - log_pass "$TESTNAME : Test Passed" - echo "$TESTNAME PASS" > "$test_path/$TESTNAME.res" - exit 0 -else - log_fail "$TESTNAME : Test Failed" - echo "$TESTNAME FAIL" > "$test_path/$TESTNAME.res" - exit 1 -fi - -log_info "-------------------Completed $TESTNAME Testcase----------------------------" diff --git a/Runner/suites/Multimedia/Video/iris_v4l2_video_encode/run.sh b/Runner/suites/Multimedia/Video/iris_v4l2_video_encode/run.sh deleted file mode 100755 index 6de21775..00000000 --- a/Runner/suites/Multimedia/Video/iris_v4l2_video_encode/run.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/sh - -# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. -# SPDX-License-Identifier: BSD-3-Clause-Clear - -# Robustly find and source init_env -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -INIT_ENV="" -SEARCH="$SCRIPT_DIR" -while [ "$SEARCH" != "/" ]; do - if [ -f "$SEARCH/init_env" ]; then - INIT_ENV="$SEARCH/init_env" - break - fi - SEARCH=$(dirname "$SEARCH") -done - -if [ -z "$INIT_ENV" ]; then - echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 - exit 1 -fi - -# Only source if not already loaded (idempotent) -if [ -z "$__INIT_ENV_LOADED" ]; then - # shellcheck disable=SC1090 - . "$INIT_ENV" -fi -# Always source functestlib.sh, using $TOOLS exported by init_env -# shellcheck disable=SC1090,SC1091 -. "$TOOLS/functestlib.sh" - -TESTNAME="iris_v4l2_video_encode" -test_path=$(find_test_case_by_name "$TESTNAME") -cd "$test_path" || exit 1 -# shellcheck disable=SC2034 -res_file="./$TESTNAME.res" -TAR_URL="https://github.com/qualcomm-linux/qcom-linux-testkit/releases/download/IRIS-Video-Files-v1.0/video_clips_iris.tar.gz" - -log_info "-----------------------------------------------------------------------------------------" -log_info "-------------------Starting $TESTNAME Testcase----------------------------" -log_info "=== Test Initialization ===" - -log_info "Checking if dependency binary is available" -check_dependencies iris_v4l2_test -extract_tar_from_url "$TAR_URL" - -# Run the first test -iris_v4l2_test --config "${test_path}/h264Encoder.json" --loglevel 15 >> "${test_path}/video_enc.txt" - -if grep -q "SUCCESS" "${test_path}/video_enc.txt"; then - log_pass "$TESTNAME : Test Passed" - echo "$TESTNAME PASS" > "$test_path/$TESTNAME.res" - exit 0 -else - log_fail "$TESTNAME : Test Failed" - echo "$TESTNAME FAIL" > "$test_path/$TESTNAME.res" - exit 1 -fi - -log_info "-------------------Completed $TESTNAME Testcase----------------------------" diff --git a/Runner/suites/Performance/resource-tuner/README.md b/Runner/suites/Performance/resource-tuner/README.md new file mode 100644 index 00000000..5f34f03d --- /dev/null +++ b/Runner/suites/Performance/resource-tuner/README.md @@ -0,0 +1,165 @@ +# `resource-tuner` Test Runner (`run.sh`) + +A pinned **whitelist** test runner for `resource-tuner` that produces per-suite logs and an overall gating result for CI. + +--- + +## What this runs + +Only these binaries are executed, in this order (anything else is ignored): +``` +/usr/bin/ClientDataManagerTests +/usr/bin/ResourceProcessorTests +/usr/bin/MemoryPoolTests +/usr/bin/SignalConfigProcessorTests +/usr/bin/DeviceInfoTests +/usr/bin/ThreadPoolTests +/usr/bin/MiscTests +/usr/bin/SignalParsingTests +/usr/bin/SafeOpsTests +/usr/bin/ExtensionIntfTests +/usr/bin/RateLimiterTests +/usr/bin/SysConfigAPITests +/usr/bin/ExtFeaturesParsingTests +/usr/bin/RequestMapTests +/usr/bin/TargetConfigProcessorTests +/usr/bin/InitConfigParsingTests +/usr/bin/RequestQueueTests +/usr/bin/CocoTableTests +/usr/bin/ResourceParsingTests +/usr/bin/TimerTests +/usr/bin/resource_tuner_tests +``` + +--- + +## Gating policy + +* **Service check (early gate):** If `resource-tuner.service` is **not active**, the test **SKIPs overall** and exits. +* **Per‑suite SKIP conditions (neutral):** + * Missing binary → **SKIP that suite**, continue. + * Missing base configs → **SKIP that suite**, continue. + * Missing test nodes for `resource_tuner_tests` → **SKIP that suite**, continue. +* **Final result:** + * If **any** suite **FAILS** → **overall FAIL**. + * Else if **≥1** suite **PASS** → **overall PASS**. + * Else (**everything SKIPPED**) → **overall SKIP**. + +> Skips are **neutral**: they never convert a passing run into a failure. + +--- + +## Pre‑checks + +### 1) Service +The runner uses the repo helper `check_systemd_services()` to verify **`resource-tuner.service`** is active. +- On failure: overall **SKIP** (ends early). +- Override service name: `SERVICE_NAME=your.service ./run.sh` + +### 2) Config presence +Suites that parse configs require **at least one** of these base config trees: + +- `common/` (required files): + - `InitConfig.yaml`, `PropertiesConfig.yaml`, `ResourcesConfig.yaml`, `SignalsConfig.yaml` + +- `custom/` (required files): + - `InitConfig.yaml`, `PropertiesConfig.yaml`, `ResourcesConfig.yaml`, `SignalsConfig.yaml`, `TargetConfig.yaml`, `ExtFeaturesConfig.yaml` + +If **both** trees are missing required files/dirs, config‑parsing suites are **SKIP** only (neutral). + +> Override required file lists without editing the script: +```bash +export RT_REQUIRE_COMMON_FILES="InitConfig.yaml PropertiesConfig.yaml ResourcesConfig.yaml SignalsConfig.yaml" +export RT_REQUIRE_CUSTOM_FILES="InitConfig.yaml PropertiesConfig.yaml ResourcesConfig.yaml SignalsConfig.yaml TargetConfig.yaml ExtFeaturesConfig.yaml" +``` + +### 3) Test ResourceSysFsNodes +`/etc/resource-tuner/tests/Configs/ResourceSysFsNodes` must exist and be non‑empty for **`/usr/bin/resource_tuner_tests`**. If missing/empty → **SKIP only that suite**. + +### 4) Base tools +Requires: `awk`, `grep`, `date`, `printf`. If missing → **overall SKIP**. + +--- + +## CLI + +``` +Usage: ./run.sh [--all] [--bin ] [--list] [--timeout SECS] +``` + +- `--all` (default): run all approved suites. +- `--bin NAME|PATH`: run a single approved suite. +- `--list`: print approved list and presence coverage, then exit. +- `--timeout SECS`: default per‑binary timeout **if** `run_with_timeout()` helper exists (else ignored). + +Per‑suite default timeouts (if helper is present): +- `ThreadPoolTests`, `RateLimiterTests`: **1800s** +- `resource_tuner_tests`: **2400s** +- others: **1200s** (default) + +--- + +## Output layout + +- **Overall status file:** `./resource-tuner.res` → `PASS` / `FAIL` / `SKIP` +- **Logs directory:** `./logs/resource-tuner-YYYYMMDD-HHMMSS/` + - Per‑suite logs: `SUITE.log` + - Per‑suite result markers: `SUITE.res` (`PASS`/`FAIL`/`SKIP`) + - Coverage summaries: `coverage.txt`, `missing_bins.txt`, `coverage_counts.env` + - System snapshot: `dmesg_snapshot.log` +- **Symlink to latest:** `./logs/resource-tuner-latest` + +**Parsing heuristics:** a suite is considered PASS if the binary exits 0 **or** its log contains +`Run Successful`, `executed successfully`, or `Ran Successfully`. Strings like `Assertion failed`, `Terminating Suite`, `Segmentation fault`, `Backtrace`, or `fail/failed` mark **FAIL**. + +--- + +## Environment overrides + +- `SERVICE_NAME`: systemd unit to check (default: `resource-tuner.service`) +- `RT_CONFIG_DIR`: root of config tree (default: `/etc/resource-tuner`) +- `RT_REQUIRE_COMMON_FILES`, `RT_REQUIRE_CUSTOM_FILES`: *space‑separated* filenames that must exist in `common/` / `custom/` respectively to treat that tree as present. + +--- + +## Examples + +Run all (normal CI mode): +```bash +./run.sh +``` + +Run a single suite by basename: +```bash +./run.sh --bin ResourceParsingTests +``` + +List suites and presence coverage: +```bash +./run.sh --list +``` + +Use a different config root: +```bash +RT_CONFIG_DIR=/opt/rt/etc ./run.sh +``` + +--- + +## Exit status + +The script writes the overall result to `resource-tuner.res`. The **process exit code is 0** in all cases in the current version (soft gating). If you want hard CI gating via non‑zero exit on FAIL, that can be added easily on request. + +--- + +## Troubleshooting + +- **Overall SKIP immediately** → service inactive. Check `systemctl status resource-tuner.service`. +- **Suite SKIP (config)** → confirm required files exist under `common/` or `custom/` (see lists above). +- **Suite SKIP (missing bin)** → verify the binary is installed and executable under `/usr/bin`. +- **Suite FAIL** → inspect `logs/.../SUITE.log` for the first failure pattern or assertion. +- **Very long runs** → a `run_with_timeout` helper (if available in your repo toolchain) will be used automatically. + +## License +- SPDX-License-Identifier: BSD-3-Clause-Clear +- (C) Qualcomm Technologies, Inc. and/or its subsidiaries. diff --git a/Runner/suites/Performance/resource-tuner/run.sh b/Runner/suites/Performance/resource-tuner/run.sh new file mode 100755 index 00000000..8dd5bdcf --- /dev/null +++ b/Runner/suites/Performance/resource-tuner/run.sh @@ -0,0 +1,518 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause-Clear +# resource-tuner test runner (pinned whitelist) + +# ---------- Repo env + helpers ---------- +SCRIPT_DIR="$( + cd "$(dirname "$0")" && pwd +)" +INIT_ENV="" +SEARCH="$SCRIPT_DIR" +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi +# Only source once (idempotent) +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi +# shellcheck disable=SC1090,SC1091 +. "$TOOLS/functestlib.sh" + +# ---------- Stable env ---------- +umask 022 +export LC_ALL=C +export PATH="/usr/sbin:/sbin:/usr/bin:/bin:${PATH}" +# Try best-effort core dumps; ignore on strict POSIX shells. +# shellcheck disable=SC3045 +( ulimit -c unlimited ) >/dev/null 2>&1 || true + +TESTNAME="resource-tuner" +test_path="$(find_test_case_by_name "$TESTNAME")" +cd "$test_path" || exit 1 +RES_FILE="./${TESTNAME}.res" + +# ---------- Lock (avoid concurrent runs on same host) ---------- +LOCKFILE="/tmp/${TESTNAME}.lock" +if command -v flock >/dev/null 2>&1; then + exec 9>"$LOCKFILE" + if ! flock -n 9; then + log_warn "Another ${TESTNAME} run is active; skipping" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi +else + if ! mkdir "$LOCKFILE" 2>/dev/null; then + log_warn "Another ${TESTNAME} run is active; skipping" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + trap 'rmdir "$LOCKFILE" 2>/dev/null || true' EXIT INT TERM +fi + +# ---------- Approved list (pinned whitelist) ---------- +APPROVED_TESTS=" +/usr/bin/ClientDataManagerTests +/usr/bin/ResourceProcessorTests +/usr/bin/MemoryPoolTests +/usr/bin/SignalConfigProcessorTests +/usr/bin/DeviceInfoTests +/usr/bin/ThreadPoolTests +/usr/bin/MiscTests +/usr/bin/SignalParsingTests +/usr/bin/SafeOpsTests +/usr/bin/ExtensionIntfTests +/usr/bin/RateLimiterTests +/usr/bin/SysConfigAPITests +/usr/bin/ExtFeaturesParsingTests +/usr/bin/RequestMapTests +/usr/bin/TargetConfigProcessorTests +/usr/bin/InitConfigParsingTests +/usr/bin/RequestQueueTests +/usr/bin/CocoTableTests +/usr/bin/ResourceParsingTests +/usr/bin/TimerTests +/usr/bin/resource_tuner_tests +" + +# Suites that need base configs (accept either common/ OR custom/) +SUITES_REQUIRE_BASE_CFGS="ResourceProcessorTests SignalConfigProcessorTests SysConfigAPITests \ +ExtFeaturesParsingTests TargetConfigProcessorTests InitConfigParsingTests \ +ResourceParsingTests ExtensionIntfTests resource_tuner_tests" + +# ---------- CLI ---------- +print_usage() { + cat <] [--list] [--timeout SECS] +Policy: + - Service INACTIVE => overall SKIP (end early) + - Base configs: suites require common/ OR custom/ (skip only if BOTH missing) + - resource_tuner_tests additionally needs tests/Configs/ResourceSysFsNodes + - Any test FAIL => overall FAIL + - No FAIL & PASS>0 => overall PASS + - No FAIL & PASS=0 => overall SKIP (everything skipped) + +Options: + --all Run all approved tests (default) + --bin NAME|PATH Run only one approved test + --list Print approved set and coverage and exit + --timeout SECS Per-binary timeout if run_with_timeout() exists (default: 1200) +EOF +} +RUN_MODE="all" +ONE_BIN="" +TIMEOUT_SECS=1200 +while [ $# -gt 0 ]; do + case "$1" in + --all) + RUN_MODE="all" + ;; + --bin) + shift + ONE_BIN="$1" + RUN_MODE="one" + ;; + --list) + RUN_MODE="list" + ;; + --timeout) + shift + TIMEOUT_SECS="${1:-1200}" + ;; + --help|-h) + print_usage + exit 0 + ;; + *) + log_error "Unknown argument: $1" + print_usage + exit 1 + ;; + esac + shift +done + +# ---------- Helpers ---------- +approved_tests() { + printf '%s\n' "$APPROVED_TESTS" | awk 'NF' +} +is_approved() { + cand="$1" + cbase="$(basename "$cand")" + for t in $(approved_tests); do + if [ "$cand" = "$t" ]; then + return 0 + fi + if [ "$cbase" = "$(basename "$t")" ]; then + return 0 + fi + done + return 1 +} +suite_requires_base_cfgs() { + name="$1" + for s in $SUITES_REQUIRE_BASE_CFGS; do + if [ "$name" = "$s" ]; then + return 0 + fi + done + return 1 +} +parse_and_score_log() { + logf="$1" + if grep -Eiq '(^|[^a-z])fail(ed)?([^a-z]|$)|Assertion failed|Terminating Suite|Segmentation fault|Backtrace' "$logf"; then + return 1 + fi + if grep -Eiq 'Run Successful|executed successfully|Ran Successfully' "$logf"; then + return 0 + fi + return 2 +} +per_suite_timeout() { + case "$1" in + ThreadPoolTests|RateLimiterTests) + echo 1800 + ;; + resource_tuner_tests) + echo 2400 + ;; + *) + echo "$TIMEOUT_SECS" + ;; + esac +} +run_cmd_maybe_timeout() { + bin="$1" + shift + secs="$(per_suite_timeout "$(basename "$bin")")" + if command -v run_with_timeout >/dev/null 2>&1; then + run_with_timeout "$secs" "$bin" "$@" + else + "$bin" "$@" + fi +} + +# ---------- Banner & deps ---------- +log_info "----------------------------------------------------------------------" +log_info "------------------- Starting ${TESTNAME} Testcase ----------------------" +log_info "=== Test Initialization ===" +if ! check_dependencies awk grep date printf; then + log_skip "$TESTNAME SKIP – base tools missing" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + +# ---------- Logs ---------- +TS="$(date +%Y%m%d-%H%M%S)" +LOGDIR="./logs/${TESTNAME}-${TS}" +mkdir -p "$LOGDIR" +(dmesg 2>/dev/null || true) > "$LOGDIR/dmesg_snapshot.log" +ln -sfn "$LOGDIR" "./logs/${TESTNAME}-latest" 2>/dev/null || true + +# ---------- SoC / Platform info (via functestlib) ---------- +if command -v log_soc_info >/dev/null 2>&1; then + log_soc_info +fi + +# ---------- Service gate (use repo helper) ---------- +SERVICE_NAME="${SERVICE_NAME:-resource-tuner.service}" +log_info "[SERVICE] Checking $SERVICE_NAME via check_systemd_services()" +if check_systemd_services "$SERVICE_NAME"; then + log_pass "[SERVICE] $SERVICE_NAME is active" +else + log_warn "[SERVICE] $SERVICE_NAME not active — attempting enable/start" + + if command -v systemctl >/dev/null 2>&1; then + systemctl enable resource-tuner >/dev/null 2>&1 || true + systemctl daemon-reload >/dev/null 2>&1 || true + systemctl start resource-tuner >/dev/null 2>&1 || true + systemctl status resource-tuner --no-pager -l >/dev/null 2>&1 || true + else + log_warn "[SERVICE] systemctl not available; cannot auto-start $SERVICE_NAME" + fi + + if check_systemd_services "$SERVICE_NAME"; then + log_pass "[SERVICE] $SERVICE_NAME is active after start attempt" + else + log_skip "[SERVICE] $SERVICE_NAME not active — overall SKIP" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi +fi + +# ---------- Config preflight (check both common/ and custom/) ---------- +RT_CONFIG_DIR="${RT_CONFIG_DIR:-/etc/resource-tuner}" +COMMON_DIR="$RT_CONFIG_DIR/common" +CUSTOM_DIR="$RT_CONFIG_DIR/custom" +TEST_NODES_DIR="$RT_CONFIG_DIR/tests/Configs/ResourceSysFsNodes" + +COMMON_OK=1 +CUSTOM_OK=1 +NODES_OK=1 + +REQ_COMMON_FILES="${RT_REQUIRE_COMMON_FILES:-InitConfig.yaml PropertiesConfig.yaml ResourcesConfig.yaml SignalsConfig.yaml}" +REQ_CUSTOM_FILES="${RT_REQUIRE_CUSTOM_FILES:-InitConfig.yaml PropertiesConfig.yaml ResourcesConfig.yaml SignalsConfig.yaml TargetConfig.yaml ExtFeaturesConfig.yaml}" + +# common/ +if [ ! -d "$COMMON_DIR" ]; then + log_warn "[CFG] Missing dir: $COMMON_DIR" + COMMON_OK=0 +else + for f in $REQ_COMMON_FILES; do + if [ ! -f "$COMMON_DIR/$f" ]; then + log_warn "[CFG] Missing file: $COMMON_DIR/$f" + COMMON_OK=0 + fi + done +fi + +# custom/ +if [ ! -d "$CUSTOM_DIR" ]; then + log_warn "[CFG] Missing dir: $CUSTOM_DIR" + CUSTOM_OK=0 +else + for f in $REQ_CUSTOM_FILES; do + if [ ! -f "$CUSTOM_DIR/$f" ]; then + log_warn "[CFG] Missing file: $CUSTOM_DIR/$f" + CUSTOM_OK=0 + fi + done + cn="$( + find "$CUSTOM_DIR/ResourceSysFsNodes" -mindepth 1 -maxdepth 1 -type f -print 2>/dev/null \ + | wc -l | awk '{print $1}' + )" + if [ -n "$cn" ]; then + log_info "[CFG] custom/ResourceSysFsNodes entries: $cn" + fi +fi + +# tests nodes (hard requirement for resource_tuner_tests) +if [ ! -d "$TEST_NODES_DIR" ]; then + log_warn "[CFG] Missing dir: $TEST_NODES_DIR" + NODES_OK=0 +else + count_nodes="$( + find "$TEST_NODES_DIR" -mindepth 1 -maxdepth 1 -type f -print 2>/dev/null \ + | wc -l | awk '{print $1}' + )" + if [ "${count_nodes:-0}" -le 0 ]; then + log_warn "[CFG] $TEST_NODES_DIR is empty" + NODES_OK=0 + fi +fi + +# ---------- Preflight whitelist coverage ---------- +: >"$LOGDIR/summary.txt" +preflight_bins() { + : >"$LOGDIR/coverage.txt" + : >"$LOGDIR/missing_bins.txt" + total=0 + present=0 + missing=0 + for t in $(approved_tests); do + total=$((total+1)) + base="$(basename "$t")" + resolved="$t" + if [ ! -x "$resolved" ]; then + resolved="$(command -v "$base" 2>/dev/null || true)" + fi + if [ -x "$resolved" ]; then + echo "[PRESENT] $base -> $resolved" >>"$LOGDIR/coverage.txt" + present=$((present+1)) + else + echo "[MISSING] $base" >>"$LOGDIR/missing_bins.txt" + echo "SKIP" >"$LOGDIR/${base}.res" + echo "[SKIP] $base – not found" >>"$LOGDIR/summary.txt" + missing=$((missing+1)) + fi + done + { + echo "total=$total" + echo "present=$present" + echo "missing=$missing" + } > "$LOGDIR/coverage_counts.env" + if [ $missing -gt 0 ]; then + log_warn "Whitelist coverage: $present/$total present, $missing missing" + fi +} +preflight_bins +if [ -r "$LOGDIR/coverage_counts.env" ]; then + # shellcheck disable=SC1091 + . "$LOGDIR/coverage_counts.env" +else + total=0 + present=0 + missing=0 +fi + +# ---------- List mode ---------- +if [ "$RUN_MODE" = "list" ]; then + log_info "Approved tests:" + approved_tests | sed 's/^/ - /' + log_info "Coverage:" + sed 's/^/ - /' "$LOGDIR/coverage.txt" 2>/dev/null || true + if [ -s "$LOGDIR/missing_bins.txt" ]; then + log_info "Missing:" + sed 's/^/ - /' "$LOGDIR/missing_bins.txt" + fi + exit 0 +fi + +# ---------- Build run list ---------- +if [ "$RUN_MODE" = "one" ]; then + TESTS="$ONE_BIN" +else + TESTS="$(approved_tests)" +fi +if [ -z "$TESTS" ]; then + log_skip "$TESTNAME SKIP – approved list empty" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + +# ---------- Execute ---------- +PASS=0 +FAIL=0 +SKIP=0 + +run_one() { + bin="$1" + name="$(basename "$bin")" + tlog="$LOGDIR/${name}.log" + tres="$LOGDIR/${name}.res" + + # whitelist enforcement + if ! is_approved "$bin"; then + log_skip "[TEST] $name not in approved set – skipping" + echo "SKIP" >"$tres" + echo "[SKIP] $name – not approved" >>"$LOGDIR/summary.txt" + return 2 + fi + + # base config requirement: accept common OR custom; skip only if BOTH missing + if suite_requires_base_cfgs "$name"; then + if [ $COMMON_OK -eq 0 ] && [ $CUSTOM_OK -eq 0 ]; then + log_skip "[CFG] Base configs missing (common/ AND custom/) — skipping $name" + echo "SKIP" >"$tres" + echo "[SKIP] $name – base configs missing" >>"$LOGDIR/summary.txt" + return 2 + fi + fi + + # resource_tuner_tests also needs test nodes + if [ "$name" = "resource_tuner_tests" ]; then + if [ $NODES_OK -eq 0 ]; then + log_skip "[CFG] Test ResourceSysFsNodes missing/empty — skipping $name" + echo "SKIP" >"$tres" + echo "[SKIP] $name – test nodes missing" >>"$LOGDIR/summary.txt" + return 2 + fi + fi + + # resolve binary + if [ ! -x "$bin" ] && command -v "$bin" >/dev/null 2>&1; then + bin="$(command -v "$bin")" + fi + if [ ! -x "$bin" ]; then + log_skip "[TEST] $name missing – skipping" + echo "SKIP" >"$tres" + echo "[SKIP] $name – not found" >>"$LOGDIR/summary.txt" + return 2 + fi + + log_info "--- Running $bin ---" + log_info "[CI] Logging to $tlog" + run_cmd_maybe_timeout "$bin" >"$tlog" 2>&1 + rc=$? + + parse_and_score_log "$tlog" + score=$? + if [ $score -eq 2 ] && [ $rc -eq 0 ]; then + score=0 + fi + + case $score in + 0) + log_pass "[TEST] $name PASS" + echo "PASS" >"$tres" + echo "[PASS] $name" >>"$LOGDIR/summary.txt" + return 0 + ;; + 1) + log_fail "[TEST] $name FAIL" + echo "FAIL" >"$tres" + echo "[FAIL] $name (rc=$rc)" >>"$LOGDIR/summary.txt" + return 1 + ;; + 2) + log_skip "[TEST] $name SKIP" + echo "SKIP" >"$tres" + echo "[SKIP] $name (rc=$rc)" >>"$LOGDIR/summary.txt" + return 2 + ;; + esac +} + +for t in $TESTS; do + run_one "$t" + rc=$? + case $rc in + 0) + PASS=$((PASS+1)) + ;; + 1) + FAIL=$((FAIL+1)) + ;; + 2) + SKIP=$((SKIP+1)) + ;; + esac +done + +# ---------- Summaries & gating ---------- +log_info "--------------------------------------------------" +log_info "Per-test summary:" +sed -n 'p' "$LOGDIR/summary.txt" | while IFS= read -r L; do + if [ -n "$L" ]; then + log_info " $L" + fi +done + +if [ -r "$LOGDIR/coverage_counts.env" ]; then + # shellcheck disable=SC1091 + . "$LOGDIR/coverage_counts.env" +else + total=${total:-0} + present=${present:-0} + missing=${missing:-0} +fi + +log_info "Coverage: ${present:-0}/${total:-0} present" +log_info "Overall counts: PASS=$PASS FAIL=$FAIL SKIP=$SKIP" + +# Final policy (skips are neutral): +# - Any FAIL -> overall FAIL +# - Else if PASS>0 -> overall PASS +# - Else -> overall SKIP (everything skipped) +if [ "$FAIL" -gt 0 ]; then + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 1 +fi +if [ "$PASS" -gt 0 ]; then + echo "$TESTNAME PASS" >"$RES_FILE" + exit 0 +fi + +echo "$TESTNAME SKIP" >"$RES_FILE" +exit 0 diff --git a/Runner/utils/audio_common.sh b/Runner/utils/audio_common.sh index 815f0d6c..b94118d4 100755 --- a/Runner/utils/audio_common.sh +++ b/Runner/utils/audio_common.sh @@ -37,14 +37,64 @@ resolve_clip() { esac } -ensure_playback_clip() { - clip="$1" - [ -f "$clip" ] && return 0 - if [ -n "$AUDIO_TAR_URL" ]; then - log_info "Audio clip missing, extracting from $AUDIO_TAR_URL" - extract_tar_from_url "$AUDIO_TAR_URL" || return 1 - fi - [ -f "$clip" ] +# audio_download_with_any +audio_download_with_any() { + url="$1"; out="$2" + if command -v wget >/dev/null 2>&1; then + wget -O "$out" "$url" + elif command -v curl >/dev/null 2>&1; then + curl -L --fail -o "$out" "$url" + else + log_error "No downloader (wget/curl) available to fetch $url" + return 1 + fi +} +# audio_fetch_assets_from_url +# Prefer functestlib's extract_tar_from_url; otherwise download + extract. +audio_fetch_assets_from_url() { + url="$1" + if command -v extract_tar_from_url >/dev/null 2>&1; then + extract_tar_from_url "$url" + return $? + fi + fname="$(basename "$url")" + log_info "Fetching assets: $url" + if ! audio_download_with_any "$url" "$fname"; then + log_warn "Download failed: $url" + return 1 + fi + tar -xzf "$fname" >/dev/null 2>&1 || tar -xf "$fname" >/dev/null 2>&1 || { + log_warn "Extraction failed: $fname" + return 1 + } + return 0 +} +# audio_ensure_clip_ready [tarball-url] +# Return codes: +# 0 = clip exists/ready +# 2 = network unavailable after attempts (caller should SKIP) +# 1 = fetch/extract/downloader error (caller will also SKIP per your policy) +audio_ensure_clip_ready() { + clip="$1" + url="${2:-${AUDIO_TAR_URL:-}}" + [ -f "$clip" ] && return 0 + # Try once without forcing network (tarball may already be present) + if [ -n "$url" ]; then + audio_fetch_assets_from_url "$url" >/dev/null 2>&1 || true + [ -f "$clip" ] && return 0 + fi + # Bring network up and retry once + if ! ensure_network_online; then + log_warn "Network unavailable; cannot fetch audio assets for $clip" + return 2 + fi + if [ -n "$url" ]; then + if audio_fetch_assets_from_url "$url" >/dev/null 2>&1; then + [ -f "$clip" ] && return 0 + fi + fi + log_warn "Clip fetch/extract failed for $clip" + return 1 } # ---------- dmesg + mixer dumps ---------- @@ -212,7 +262,11 @@ pa_default_source() { printf '%s\n' "$s" } -pa_set_default_source() { [ -n "$1" ] && pactl set-default-source "$1" >/dev/null 2>&1 || true; } +pa_set_default_source() { + if [ -n "$1" ]; then + pactl set-default-source "$1" >/dev/null 2>&1 || true + fi +} pa_source_name() { id="$1"; [ -n "$id" ] || return 1 @@ -546,4 +600,64 @@ audio_exec_with_timeout() { # no timeout "$@" } - + +# -------------------------------------------------------------------- +# Backend chain + minimal ALSA capture picker (for fallback in run.sh) +# -------------------------------------------------------------------- + +# Prefer: currently selected (or detected) backend, then pipewire, pulseaudio, alsa. +# We keep it simple: we don't filter by daemon state here; the caller tries each. +build_backend_chain() { + preferred="${AUDIO_BACKEND:-$(detect_audio_backend)}" + chain="" + add_unique() { + case " $chain " in + *" $1 "*) : ;; + *) chain="${chain:+$chain }$1" ;; + esac + } + [ -n "$preferred" ] && add_unique "$preferred" + for b in pipewire pulseaudio alsa; do + add_unique "$b" + done + printf '%s\n' "$chain" +} + +# Pick a plausible ALSA capture device. +# Returns something like hw:0,0 if available, else "default". +alsa_pick_capture() { + line="$(arecord -l 2>/dev/null | sed -n 's/^card \([0-9][0-9]*\):.*device \([0-9][0-9]*\):.*/\1 \2/p' | head -n1)" + if [ -n "$line" ]; then + set -- "$line" + printf 'hw:%s,%s\n' "$1" "$2" + return 0 + fi + printf '%s\n' "default" + return 0 +} + +alsa_pick_capture() { + command -v arecord >/dev/null 2>&1 || return 1 + # Prefer the first real capture device from `arecord -l` + arecord -l 2>/dev/null | awk ' + /card [0-9]+: .*device [0-9]+:/ { + if (match($0, /card ([0-9]+):/, c) && match($0, /device ([0-9]+):/, d)) { + printf("hw:%s,%s\n", c[1], d[1]); + exit 0; + } + } + ' +} + +# Prefer virtual capture PCMs (PipeWire/Pulse) over raw hw: when a sound server is present +alsa_pick_virtual_pcm() { + command -v arecord >/dev/null 2>&1 || return 1 + pcs="$(arecord -L 2>/dev/null | sed -n 's/^[[:space:]]*\([[:alnum:]_]\+\)[[:space:]]*$/\1/p')" + for pcm in pipewire pulse default; do + if printf '%s\n' "$pcs" | grep -m1 -x "$pcm" >/dev/null 2>&1; then + printf '%s\n' "$pcm" + return 0 + fi + done + return 1 +} diff --git a/Runner/utils/camera/lib_camera.sh b/Runner/utils/camera/lib_camera.sh new file mode 100755 index 00000000..29bc3306 --- /dev/null +++ b/Runner/utils/camera/lib_camera.sh @@ -0,0 +1,418 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause-Clear +# Shared libcamera helpers +# ---------- Sensor & index helpers ---------- + +# Return the number of sensors visible in `cam -l` +libcam_list_sensors_count() { + out="$(cam -l 2>&1 || true)" + printf '%s\n' "$out" | awk ' + BEGIN { c=0; inlist=0 } + /^Available cameras:/ { inlist=1; next } + /^[[:space:]]*[0-9]+:[[:space:]]/ { if (inlist) c++ } + END { print c+0 } + ' +} + +# List all camera indices seen in `cam -l` (space-separated) +libcam_list_indices() { + command -v cam >/dev/null 2>&1 || { printf '\n'; return 1; } + cam -l 2>/dev/null \ + | sed -n -E 's/^[[:space:]]*\[([0-9]+)\].*$/\1/p; s/^[[:space:]]*([0-9]+):.*/\1/p' +} + +# Resolve requested indices: +# - "auto" (default): first index from cam -l +# - "all": all indices space-separated +# - "0,2,5": comma-separated list (validated against cam -l) +# Outputs space-separated indices to stdout. +libcam_resolve_indices() { + want="${1:-auto}" + all="$(libcam_list_indices | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g;s/^ //;s/ $//')" + [ -n "$all" ] || { printf '\n'; return 1; } + + case "$want" in + ""|"auto") + printf '%s\n' "$(printf '%s' "$all" | awk '{print $1}')" + ;; + "all") + printf '%s\n' "$all" + ;; + *) + good="" + IFS=','; set -- "$want"; IFS=' ' + for idx in "$@"; do + if printf '%s\n' "$all" | grep -qx "$idx"; then + good="$good $idx" + fi + done + printf '%s\n' "$(printf '%s' "$good" | sed 's/^ //')" + ;; + esac +} + +# Pick first camera index (helper used by older paths) +libcam_pick_cam_index() { + want="${1:-auto}" + if [ "$want" != "auto" ]; then + printf '%s\n' "$want" + return 0 + fi + libcam_list_indices | head -n1 +} + +# ---------- Log parsing ---------- + +# Extract sequences from a cam run log +# Usage: libcam_log_seqs "" +libcam_log_seqs() { + sed -n 's/.*seq:[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$1" +} + +# Extract bytesused from a cam run log +# Usage: libcam_log_bytesused "" +libcam_log_bytesused() { + sed -n 's/.*bytesused:[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$1" +} + +# Check contiguous sequence numbers on stdin; prints a summary and sets exit code +# Usage: libcam_check_contiguous +libcam_check_contiguous() { + awk ' + { + v=$1+0 + a[v]=1 + if(n==0 || vmax) max=v + n++ + } + END{ + if(n==0){ print "EMPTY"; exit 1 } + missing=0 + for(i=min;i<=max;i++){ if(!(i in a)) missing++ } + printf "N=%d MIN=%06d MAX=%06d MISSING=%d\n", n, min, max, missing + exit (missing==0 ? 0 : 2) + }' +} + +# List captured files and their inferred sequence (from filename) +# Usage: libcam_file_list_and_seq "" +libcam_file_list_and_seq() { + find "$1" -maxdepth 1 -type f \( -name 'frame-*.bin' -o -name 'frame-*.ppm' \) -print \ + | awk ' + { + f=$0; seq=-1 + if (match(f, /[^0-9]([0-9]+)\.(bin|ppm)$/, m)) seq=m[1] + print f, seq + }' | sort +} + +# Sample the first N bytes and count unique values (to detect constant content) +# Usage: libcam_sample_uniques "" "" +libcam_sample_uniques() { + f="$1"; n="$2" + + # Prefer BusyBox-compatible od -b + if command -v od >/dev/null 2>&1; then + dd if="$f" bs=1 count="$n" status=none 2>/dev/null \ + | od -v -b 2>/dev/null \ + | awk ' + { + for (i=2;i<=NF;i++) { + if ($i ~ /^[0-7]{3}$/) { + v = strtonum("0"$i) + if (!(v in seen)) { seen[v]=1; cnt++ } + } + } + } + END { print (cnt+0) } + ' + return + fi + + # Fallback: hexdump (some BusyBox builds lack -e) + if command -v hexdump >/dev/null 2>&1; then + dd if="$f" bs=1 count="$n" status=none 2>/dev/null \ + | hexdump -v -C 2>/dev/null \ + | awk ' + { + # Parse canonical dump: hex bytes in columns 2..17 + for (i=2;i<=17;i++) { + h = $i + if (h ~ /^[0-9A-Fa-f]{2}$/) { + v = strtonum("0x"h) + if (!(v in seen)) { seen[v]=1; cnt++ } + } + } + } + END { print (cnt+0) } + ' + return + fi + + # Last-chance optimistic fallback + echo 256 +} + +# Return a hashing command if available (sha256sum preferred) +# Usage: cmd="$(libcam_hash_command)" +libcam_hash_command() { + if command -v sha256sum >/dev/null 2>&1; then + echo sha256sum + elif command -v md5sum >/dev/null 2>&1; then + echo md5sum + else + echo "" + fi +} + +# ---------- Files & sequence mapping ---------- + +# Build file→seq map and (optionally) check contiguous sequences across files. +# Usage: libcam_files_and_seq "" "" +# Side-effects: writes "$out_dir/.file_seq_map.txt" +# Returns: 0 = OK, 1 = failed strict check +libcam_files_and_seq() { + dir="$1"; strict="$2" + MAP="$dir/.file_seq_map.txt" + : > "$MAP" + + for f in "$dir"/frame-*.bin "$dir"/frame-*.ppm; do + [ -e "$f" ] || continue + base="${f##*/}" + seq="${base%.*}" + seq="${seq##*-}" + printf '%s %s\n' "$f" "$seq" >> "$MAP" + done + + if [ "$strict" = "yes" ]; then + awk '{print $2+0}' "$MAP" | sort -n | libcam_check_contiguous >/dev/null 2>&1 || { + log_warn "non-contiguous sequences in files" + return 1 + } + fi + return 0 +} + +# ---------- Content validation (PPM & BIN) ---------- + +# Validate PPM frames under OUT_DIR. +# Usage: libcam_validate_ppm "" "" +# Returns: 0 = OK, 1 = problems found +libcam_validate_ppm() { + out_dir="$1" + ppm_bytes="$2" + count=$(find "$out_dir" -maxdepth 1 -type f -name 'frame-*.ppm' | wc -l | tr -d ' ') + [ "$count" -gt 0 ] || return 0 + + BAD_PPM=0 + ppm_list="$out_dir/.ppm_list.txt" + find "$out_dir" -maxdepth 1 -type f -name 'frame-*.ppm' -print > "$ppm_list" + + while IFS= read -r f; do + [ -f "$f" ] || continue + + magic="$(LC_ALL=C head -c 2 "$f" 2>/dev/null || true)" + if [ "$magic" != "P6" ]; then + log_warn "PPM magic not P6: $f" + BAD_PPM=$((BAD_PPM+1)) + continue + fi + + hdr="$(dd if="$f" bs=1 count=256 status=none 2>/dev/null)" + hdr_clean="$(printf '%s' "$hdr" \ + | sed 's/#.*$//g' \ + | tr '\n' ' ' \ + | tr -s ' ' \ + | sed 's/^P6 *//')" + + IFS=' ' read -r W H M _ </dev/null || stat -f %z "$f" 2>/dev/null || echo 0) + if [ "$fsz" -lt "$datasz" ]; then + log_warn "PPM smaller than payload ($fsz < $datasz): $f" + BAD_PPM=$((BAD_PPM+1)) + continue + fi + + u=$(libcam_sample_uniques "$f" "$ppm_bytes") + if [ "${u:-0}" -le 4 ]; then + log_warn "PPM looks constant/near-constant (uniques=$u): $f" + BAD_PPM=$((BAD_PPM+1)) + fi + done < "$ppm_list" + + [ "${BAD_PPM:-0}" -eq 0 ] +} + +# Validate BIN frames under OUT_DIR using RUN_LOG for bytesused correlation. +# Usage: libcam_validate_bin "" "" "" "" "" +# Returns: 0 = OK, 1 = problems found +libcam_validate_bin() { + dir="$1"; run_log="$2"; bin_sample_bytes="$3"; BIN_TOL_PCT="$4"; DUP_MAX_RATIO="$5" + + BAD=0 + + # Extract bytesused lines from the run log (may be 0 lines; that's fine) + BU_TXT="$dir/.bytesused.txt" + sed -n 's/.*bytesused:[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$run_log" > "$BU_TXT" 2>/dev/null || : + + # List BIN files & sizes (BusyBox-compatible: no -printf) + SIZES_TXT="$dir/.bin_sizes.txt" + : > "$SIZES_TXT" + find "$dir" -maxdepth 1 -type f -name 'frame-*.bin' -print 2>/dev/null \ + | while IFS= read -r f; do + sz="$(stat -c %s "$f" 2>/dev/null || stat -f %z "$f" 2>/dev/null || wc -c <"$f")" + printf '%s %s\n' "$f" "$sz" >> "$SIZES_TXT" + done + + # Size tolerance check vs. closest bytesused + if [ -s "$BU_TXT" ] && [ -s "$SIZES_TXT" ]; then + while IFS= read -r line; do + f=$(printf '%s\n' "$line" | cut -d' ' -f1) + sz=$(printf '%s\n' "$line" | cut -d' ' -f2) + [ -n "$sz" ] || continue + + target=$( + sort -n "$BU_TXT" 2>/dev/null \ + | awk -v S="$sz" ' + BEGIN{best=-1; diff=1e99} + { d=$1-S; if(d<0)d=-d; if(d/dev/null 2>&1; then + u="$(dd if="$f" bs=1 count="$bin_sample_bytes" status=none 2>/dev/null \ + | od -An -tu1 -v 2>/dev/null \ + | tr -s ' ' ' ' | tr ' ' '\n' | sed '/^$/d' \ + | sort -n | uniq | wc -l | tr -d ' ' )" + elif command -v hexdump >/dev/null 2>&1; then + u="$(dd if="$f" bs=1 count="$bin_sample_bytes" status=none 2>/dev/null \ + | hexdump -v -e '1/1 "%u\n"' 2>/dev/null \ + | sort -n | uniq | wc -l | tr -d ' ' )" + fi + # Warn only; do NOT increment BAD. + if [ "${u:-0}" -le 4 ] 2>/dev/null; then + log_warn "BIN looks constant/near-constant (uniques=${u:-0}): $f" + fi + done < "$SIZES_TXT" + fi + + # Duplicate detection by hash (optional, best-effort) + if [ -s "$SIZES_TXT" ]; then + hash_cmd="" + if command -v sha256sum >/dev/null 2>&1; then hash_cmd="sha256sum" + elif command -v md5sum >/dev/null 2>&1; then hash_cmd="md5sum" + fi + + if [ -n "$hash_cmd" ]; then + DUPS_TXT="$dir/.hashes.txt" + : > "$DUPS_TXT" + while IFS= read -r f; do + [ -f "$f" ] || continue + $hash_cmd "$f" 2>/dev/null | awk '{print $1}' >> "$DUPS_TXT" + done < "$SORTED" 2>/dev/null || cp "$DUPS_TXT" "$SORTED" + + total=$(wc -l <"$SORTED" 2>/dev/null | tr -d ' ') + maxdup=$(awk ' + { cnt[$1]++ } + END { + m=0; for (k in cnt) if (cnt[k]>m) m=cnt[k]; + print m+0 + }' "$SORTED") + + if [ "${total:-0}" -gt 0 ] && [ "${maxdup:-0}" -gt 0 ]; then + ratio=$(awk -v m="$maxdup" -v t="$total" 'BEGIN{ if(t==0) print "0"; else printf "%.3f", m/t }') + if awk -v r="$ratio" -v lim="$DUP_MAX_RATIO" 'BEGIN{ exit !(r>lim) }'; then + log_warn "High duplicate ratio in BIN frames (max bucket $maxdup / $total = $ratio > $DUP_MAX_RATIO)" + BAD=$((BAD+1)) + fi + fi + fi + fi + fi + + [ "$BAD" -eq 0 ] +} + +# Orchestrate both content checks +# Usage: libcam_validate_content "" "" "" "" "" "" +# Returns: 0 = OK, 1 = problems found +libcam_validate_content() { + out_dir="$1"; run_log="$2"; ppm_bytes="$3"; bin_bytes="$4"; bin_tol_pct="$5"; dup_max_ratio="$6" + ok=0 + if ! libcam_validate_ppm "$out_dir" "$ppm_bytes"; then ok=1; fi + if ! libcam_validate_bin "$out_dir" "$run_log" "$bin_bytes" "$bin_tol_pct" "$dup_max_ratio"; then ok=1; fi + [ $ok -eq 0 ] +} + +# ---------- Error scanning ---------- + +# Scan run log for serious errors if strict; return non-zero if any found. +# Usage: libcam_scan_errors "" "" +libcam_scan_errors() { + run_log="$1" + strict="$2" + + [ "$strict" = "yes" ] || { log_info "[scan] ERR_STRICT=no; skipping fatal scan"; return 0; } + + # Build a filtered view that removes known-benign noise we see on imx577 / simple pipeline. + # We keep this BusyBox/grep-basic-friendly (no PCRE features). + tmpf="$(mktemp)" || return 1 + # Lines to ignore (noisy but benign for listing/capture): + # - CameraSensor legacy WARN/ERROR geometry queries + # - IPAProxy config yaml warnings for imx577/simple + # - Software ISP / SimplePipeline warnings + # - Generic "WARN" lines + grep -viE \ + 'CameraSensor|PixelArray|ActiveAreas|crop rectangle|Rotation control|No sensor delays|CameraSensorProperties|IPAProxy|configuration file.*yaml|SoftwareIsp|IPASoft|SimplePipeline' \ + "$run_log" >"$tmpf" || true + + # Fatal patterns: keep simple & portable; focus on truly bad states. + # (No generic "error" catch-all here.) + if grep -Eiq \ + 'segmentation fault|assert|device[^[:alpha:]]*not[^[:alpha:]]*found|failed to open camera|cannot open camera|stream[^[:alpha:]]*configuration[^[:alpha:]]*failed|request[^[:alpha:]]*timeout|EPIPE|I/O error' \ + "$tmpf"; then + log_warn "Serious error keywords found in cam run log (fatal)" + rm -f "$tmpf" + return 1 + fi + + log_info "[scan] No fatal errors after noise suppression" + rm -f "$tmpf" + return 0 +} diff --git a/Runner/utils/functestlib.sh b/Runner/utils/functestlib.sh index ae6c690a..adcd2a54 100755 --- a/Runner/utils/functestlib.sh +++ b/Runner/utils/functestlib.sh @@ -1,5 +1,4 @@ #!/bin/sh - # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. # SPDX-License-Identifier: BSD-3-Clause-Clear @@ -289,85 +288,416 @@ check_network_status() { fi } +# --- Make sure system time is sane (TLS needs a sane clock) --- +ensure_reasonable_clock() { + now="$(date +%s 2>/dev/null || echo 0)" + cutoff="$(date -d '2020-01-01 UTC' +%s 2>/dev/null || echo 1577836800)" + [ -z "$cutoff" ] && cutoff=1577836800 + [ "$now" -ge "$cutoff" ] 2>/dev/null && return 0 + + log_warn "System clock looks invalid (epoch=$now). Attempting quick time sync..." + if command -v timedatectl >/dev/null 2>&1; then + timedatectl set-ntp true 2>/dev/null || true + fi + grace=25 + start="$(date +%s 2>/dev/null || echo 0)" + end=$((start + grace)) + while :; do + cur="$(date +%s 2>/dev/null || echo 0)" + if [ "$cur" -ge "$cutoff" ] 2>/dev/null; then + log_pass "Clock synchronized." + return 0 + fi + if [ "$cur" -ge "$end" ] 2>/dev/null; then + break + fi + sleep 1 + done + + log_warn "Clock still invalid; TLS downloads may fail. Treating as limited network." + return 1 +} + # If the tar file already exists,then function exit. Otherwise function to check the network connectivity and it will download tar from internet. extract_tar_from_url() { - url=$1 - filename=$(basename "$url") - - check_tar_file "$url" - status=$? + url="$1" + outdir="${LOG_DIR:-.}" + mkdir -p "$outdir" 2>/dev/null || true + + case "$url" in + /*) + tarfile="$url" + ;; + file://*) + tarfile="${url#file://}" + ;; + *) + tarfile="$outdir/$(basename "$url")" + ;; + esac + markfile="${tarfile}.extracted" + skip_sentinel="${outdir}/.asset_fetch_skipped" + + # If a previous run already marked "assets unavailable", honor it and SKIP. + if [ -f "$skip_sentinel" ]; then + log_info "Previous run marked assets unavailable on this system (${skip_sentinel}); skipping download." + return 2 + fi + + tar_already_extracted() { + tf="$1" + od="$2" + if [ -f "${tf}.extracted" ]; then + return 0 + fi + tmp_list="${od}/.tar_ls.$$" + if tar -tf "$tf" 2>/dev/null | head -n 20 > "$tmp_list"; then + total=0 + present=0 + while IFS= read -r ent; do + [ -z "$ent" ] && continue + total=$((total + 1)) + ent="${ent%/}" + if [ -e "$od/$ent" ] || [ -e "$od/$(basename "$ent")" ]; then + present=$((present + 1)) + fi + done < "$tmp_list" + rm -f "$tmp_list" 2>/dev/null || true + if [ "$present" -ge 3 ]; then + return 0 + fi + if [ "$total" -gt 0 ] && [ $((present * 100 / total)) -ge 50 ]; then + return 0 + fi + fi + return 1 + } + + if command -v check_tar_file >/dev/null 2>&1; then + check_tar_file "$url" + status=$? + else + if [ -f "$tarfile" ]; then + if tar_already_extracted "$tarfile" "$outdir"; then + status=0 + else + status=2 + fi + else + status=1 + fi + fi + + ensure_reasonable_clock || { + log_warn "Proceeding in limited-network mode." + limited_net=1 + } + + is_busybox_wget() { + if command -v wget >/dev/null 2>&1; then + if wget --help 2>&1 | head -n 1 | grep -qi busybox; then + return 0 + fi + fi + return 1 + } + + tls_capable_fetcher_available() { + scheme_https=0 + case "$url" in + https://*) + scheme_https=1 + ;; + esac + if [ "$scheme_https" -eq 0 ]; then + return 0 + fi + if command -v curl >/dev/null 2>&1; then + if curl -V 2>/dev/null | grep -qiE 'ssl|tls'; then + return 0 + fi + fi + if command -v aria2c >/dev/null 2>&1; then + return 0 + fi + if command -v wget >/dev/null 2>&1; then + if ! is_busybox_wget; then + return 0 + fi + if command -v openssl >/dev/null 2>&1; then + return 0 + fi + fi + return 1 + } + + try_download() { + src="$1" + dst="$2" + part="${dst}.part.$$" + ca="" + + for cand in \ + /etc/ssl/certs/ca-certificates.crt \ + /etc/ssl/cert.pem \ + /system/etc/security/cacerts/ca-certificates.crt + do + if [ -r "$cand" ]; then + ca="$cand" + break + fi + done + + if command -v curl >/dev/null 2>&1; then + if [ -n "$ca" ]; then + curl -4 -L --fail --retry 3 --retry-delay 2 --connect-timeout 10 \ + -o "$part" --cacert "$ca" "$src" + else + curl -4 -L --fail --retry 3 --retry-delay 2 --connect-timeout 10 \ + -o "$part" "$src" + fi + rc=$? + if [ $rc -eq 0 ]; then + mv -f "$part" "$dst" 2>/dev/null || true + return 0 + fi + rm -f "$part" 2>/dev/null || true + case "$rc" in + 60|35|22) + return 60 + ;; + esac + fi + + if command -v aria2c >/dev/null 2>&1; then + aria2c -x4 -s4 -m3 --connect-timeout=10 \ + -o "$(basename "$part")" --dir="$(dirname "$part")" "$src" + rc=$? + if [ $rc -eq 0 ]; then + mv -f "$part" "$dst" 2>/dev/null || true + return 0 + fi + rm -f "$part" 2>/dev/null || true + fi + + if command -v wget >/dev/null 2>&1; then + if is_busybox_wget; then + wget -O "$part" -T 15 "$src" + rc=$? + if [ $rc -ne 0 ]; then + log_warn "BusyBox wget failed (rc=$rc); final attempt with --no-check-certificate." + wget -O "$part" -T 15 --no-check-certificate "$src" + rc=$? + fi + if [ $rc -eq 0 ]; then + mv -f "$part" "$dst" 2>/dev/null || true + return 0 + fi + rm -f "$part" 2>/dev/null || true + return 60 + else + if [ -n "$ca" ]; then + wget -4 --timeout=15 --tries=3 --ca-certificate="$ca" -O "$part" "$src" + rc=$? + else + wget -4 --timeout=15 --tries=3 -O "$part" "$src" + rc=$? + fi + if [ $rc -ne 0 ]; then + log_warn "wget failed (rc=$rc); final attempt with --no-check-certificate." + wget -4 --timeout=15 --tries=1 --no-check-certificate -O "$part" "$src" + rc=$? + fi + if [ $rc -eq 0 ]; then + mv -f "$part" "$dst" 2>/dev/null || true + return 0 + fi + rm -f "$part" 2>/dev/null || true + if [ $rc -eq 5 ]; then + return 60 + fi + return $rc + fi + fi + + return 127 + } + if [ "$status" -eq 0 ]; then log_info "Already extracted. Skipping download." return 0 - elif [ "$status" -eq 1 ]; then - log_info "File missing or invalid. Will download and extract." - check_network_status || return 1 - log_info "Downloading $url..." - wget -O "$filename" "$url" || { - log_fail "Failed to download $filename" - return 1 - } - log_info "Extracting $filename..." - tar -xvf "$filename" || { - log_fail "Failed to extract $filename" - return 1 - } - elif [ "$status" -eq 2 ]; then + fi + + if [ "$status" -eq 2 ]; then log_info "File exists and is valid, but not yet extracted. Proceeding to extract." - tar -xvf "$filename" || { - log_fail "Failed to extract $filename" - return 1 - } + else + case "$url" in + /*|file://*) + if [ ! -f "$tarfile" ]; then + log_fail "Local tar file not found: $tarfile" + return 1 + fi + ;; + *) + if [ ! -f "$tarfile" ] || [ ! -s "$tarfile" ]; then + prestage_dirs="" + if [ -n "${ASSET_DIR:-}" ]; then prestage_dirs="$prestage_dirs $ASSET_DIR"; fi + if [ -n "${VIDEO_ASSET_DIR:-}" ]; then prestage_dirs="$prestage_dirs $VIDEO_ASSET_DIR"; fi + if [ -n "${AUDIO_ASSET_DIR:-}" ]; then prestage_dirs="$prestage_dirs $AUDIO_ASSET_DIR"; fi + prestage_dirs="$prestage_dirs . $outdir ${ROOT_DIR:-} ${ROOT_DIR:-}/cache /var/Runner /var/Runner/cache" + + for d in $prestage_dirs; do + if [ -d "$d" ] && [ -f "$d/$(basename "$tarfile")" ]; then + log_info "Using pre-staged tarball: $d/$(basename "$tarfile")" + cp -f "$d/$(basename "$tarfile")" "$tarfile" 2>/dev/null || true + break + fi + done + + if [ ! -s "$tarfile" ]; then + for top in /mnt /media; do + if [ -d "$top" ]; then + for d in "$top"/*; do + if [ -d "$d" ] && [ -f "$d/$(basename "$tarfile")" ]; then + log_info "Using pre-staged tarball: $d/$(basename "$tarfile")" + cp -f "$d/$(basename "$tarfile")" "$tarfile" 2>/dev/null || true + break 2 + fi + done + fi + done + fi + fi + + if [ ! -s "$tarfile" ]; then + if [ -n "$limited_net" ]; then + log_warn "Limited network, cannot fetch media bundle. Marking SKIP for callers." + : > "$skip_sentinel" 2>/dev/null || true + return 2 + fi + + if ! tls_capable_fetcher_available; then + log_warn "No TLS-capable downloader available on this minimal build, cannot fetch: $url" + log_warn "Pre-stage $(basename "$url") locally or use a file:// URL." + : > "$skip_sentinel" 2>/dev/null || true + return 2 + fi + + log_info "Downloading $url -> $tarfile" + if ! try_download "$url" "$tarfile"; then + rc=$? + if [ $rc -eq 60 ]; then + log_warn "TLS/handshake problem while downloading (cert/clock/firewall or minimal wget). Marking SKIP." + : > "$skip_sentinel" 2>/dev/null || true + return 2 + fi + log_fail "Failed to download $(basename "$url")" + return 1 + fi + fi + ;; + esac fi - - # Optionally, check that extraction succeeded - first_entry=$(tar -tf "$filename" 2>/dev/null | head -n1 | cut -d/ -f1) - if [ -n "$first_entry" ] && [ -e "$first_entry" ]; then - log_pass "Files extracted successfully ($first_entry exists)." + + log_info "Extracting $(basename "$tarfile")..." + if tar -xvf "$tarfile"; then + : > "$markfile" 2>/dev/null || true + # Clear the minimal/offline sentinel only if it exists (SC2015-safe) + if [ -f "$skip_sentinel" ]; then + rm -f "$skip_sentinel" 2>/dev/null || true + fi + + first_entry="$(tar -tf "$tarfile" 2>/dev/null | head -n 1 | sed 's#/$##')" + if [ -n "$first_entry" ]; then + if [ -e "$first_entry" ] || [ -e "$outdir/$first_entry" ]; then + log_pass "Files extracted successfully ($(basename "$first_entry") present)." + return 0 + fi + fi + log_warn "Extraction finished but couldn't verify entries. Assuming success." return 0 - else - log_fail "Extraction did not create expected entry: $first_entry" - return 1 fi + + log_fail "Failed to extract $(basename "$tarfile")" + return 1 } + # Function to check if a tar file exists check_tar_file() { - url=$1 - filename=$(basename "$url") - - # 1. Check file exists - if [ ! -f "$filename" ]; then - log_error "File $filename does not exist." + url="$1" + outdir="${LOG_DIR:-.}" + mkdir -p "$outdir" 2>/dev/null || true + + case "$url" in + /*) tarfile="$url" ;; + file://*) tarfile="${url#file://}" ;; + *) tarfile="$outdir/$(basename "$url")" ;; + esac + markfile="${tarfile}.extracted" + + # 1) Existence & basic validity + if [ ! -f "$tarfile" ]; then + log_info "File $(basename "$tarfile") does not exist in $outdir." return 1 fi - - # 2. Check file is non-empty - if [ ! -s "$filename" ]; then - log_error "File $filename exists but is empty." + if [ ! -s "$tarfile" ]; then + log_warn "File $(basename "$tarfile") exists but is empty." return 1 fi - - # 3. Check file is a valid tar archive - if ! tar -tf "$filename" >/dev/null 2>&1; then - log_error "File $filename is not a valid tar archive." + if ! tar -tf "$tarfile" >/dev/null 2>&1; then + log_warn "File $(basename "$tarfile") is not a valid tar archive." return 1 fi - - # 4. Check if already extracted: does the first entry in the tar exist? - first_entry=$(tar -tf "$filename" 2>/dev/null | head -n1 | cut -d/ -f1) - if [ -n "$first_entry" ] && [ -e "$first_entry" ]; then - log_pass "$filename has already been extracted ($first_entry exists)." + + # 2) Already extracted? (marker first) + if [ -f "$markfile" ]; then + log_pass "$(basename "$tarfile") has already been extracted (marker present)." return 0 fi - - log_info "$filename exists and is valid, but not yet extracted." + + # 3) Heuristic: check multiple entries from the tar exist on disk + tmp_list="${outdir}/.tar_ls.$$" + if tar -tf "$tarfile" 2>/dev/null | head -n 20 >"$tmp_list"; then + total=0; present=0 + while IFS= read -r ent; do + [ -z "$ent" ] && continue + total=$((total + 1)) + ent="${ent%/}" + # check exact relative path and also basename (covers archives with a top-level dir) + if [ -e "$outdir/$ent" ] || [ -e "$outdir/$(basename "$ent")" ]; then + present=$((present + 1)) + fi + done < "$tmp_list" + rm -f "$tmp_list" 2>/dev/null || true + + # If we find a reasonable portion of entries, assume it's extracted + if [ "$present" -ge 3 ] || { [ "$total" -gt 0 ] && [ $((present * 100 / total)) -ge 50 ]; }; then + log_pass "$(basename "$tarfile") already extracted ($present/$total entries found)." + return 0 + fi + fi + + # 4) Exists and valid, but not yet extracted + log_info "$(basename "$tarfile") exists and is valid, but not yet extracted." return 2 } -# Check if weston is running +# Return space-separated PIDs for 'weston' (BusyBox friendly). +weston_pids() { + pids="" + if command -v pgrep >/dev/null 2>&1; then + pids="$(pgrep -x weston 2>/dev/null || true)" + fi + if [ -z "$pids" ]; then + pids="$(ps -eo pid,comm 2>/dev/null | awk '$2=="weston"{print $1}')" + fi + echo "$pids" +} + +# Is Weston running? weston_is_running() { - pgrep -x weston >/dev/null 2>&1 + [ -n "$(weston_pids)" ] } # Stop all Weston processes @@ -393,118 +723,798 @@ weston_stop() { # Start weston with correct env if not running weston_start() { - export XDG_RUNTIME_DIR="/dev/socket/weston" - mkdir -p "$XDG_RUNTIME_DIR" - - # Remove stale Weston socket if it exists - if [ -S "$XDG_RUNTIME_DIR/weston" ]; then - log_info "Removing stale Weston socket." - rm -f "$XDG_RUNTIME_DIR/weston" - fi - if weston_is_running; then log_info "Weston already running." return 0 fi - # Clean up stale sockets for wayland-0 (optional) - [ -S "$XDG_RUNTIME_DIR/wayland-1" ] && rm -f "$XDG_RUNTIME_DIR/wayland-1" - nohup weston --continue-without-input --idle-time=0 > weston.log 2>&1 & - sleep 3 - - if weston_is_running; then - log_info "Weston started." - return 0 - else - log_error "Failed to start Weston." + + if command -v systemctl >/dev/null 2>&1; then + log_info "Attempting to start via systemd: weston.service" + systemctl start weston.service >/dev/null 2>&1 || true + sleep 1 + if weston_is_running; then + log_info "Weston started via systemd (weston.service)." + return 0 + fi + + log_info "Attempting to start via systemd: weston@.service" + systemctl start weston@.service >/dev/null 2>&1 || true + sleep 1 + if weston_is_running; then + log_info "Weston started via systemd (weston@.service)." + return 0 + fi + + log_warn "systemd start did not bring Weston up; will try direct spawn." + fi + + # Minimal-friendly direct spawn (no headless module guesses here). + ensure_xdg_runtime_dir + + if ! command -v weston >/dev/null 2>&1; then + log_fail "weston binary not found in PATH." return 1 fi -} - -# Returns true (0) if interface is administratively and physically up -is_interface_up() { - iface="$1" - if [ -f "/sys/class/net/$iface/operstate" ]; then - [ "$(cat "/sys/class/net/$iface/operstate")" = "up" ] - elif command -v ip >/dev/null 2>&1; then - ip link show "$iface" 2>/dev/null | grep -qw "state UP" - elif command -v ifconfig >/dev/null 2>&1; then - ifconfig "$iface" 2>/dev/null | grep -qw "UP" + + log_info "Attempting to spawn Weston (no backend override). Log: /tmp/weston.self.log" + ( nohup weston --log=/tmp/weston.self.log >/dev/null 2>&1 & ) || true + + tries=0 + while [ $tries -lt 5 ]; do + if weston_is_running; then + log_info "Weston is now running (PID(s): $(weston_pids))." + return 0 + fi + if [ -n "$(find_wayland_sockets | head -n1)" ]; then + log_info "A Wayland socket appeared after spawn." + return 0 + fi + sleep 1 + tries=$((tries+1)) + done + + if [ -f /tmp/weston.self.log ]; then + log_warn "Weston spawn failed; last log lines:" + tail -n 20 /tmp/weston.self.log 2>/dev/null | sed 's/^/[weston.log] /' || true else - return 1 + log_warn "Weston spawn failed; no log file present." fi + return 1 } -# Returns true (0) if physical link/carrier is detected (cable plugged in) -is_link_up() { - iface="$1" - [ -f "/sys/class/net/$iface/carrier" ] && [ "$(cat "/sys/class/net/$iface/carrier")" = "1" ] +# Choose a socket (or try to start), adopt env, and echo chosen path. +wayland_choose_or_start() { + wayland_debug_snapshot "pre-choose" + sock="$(wayland_pick_socket || true)" + if [ -z "$sock" ]; then + log_info "No Wayland socket found; attempting to start Weston…" + weston_start || log_warn "weston_start() did not succeed." + # Re-scan a few times + n=0 + while [ $n -lt 5 ] && [ -z "$sock" ]; do + sock="$(wayland_pick_socket || true)" + [ -n "$sock" ] && break + sleep 1 + n=$((n+1)) + done + fi + if [ -n "$sock" ]; then + adopt_wayland_env_from_socket "$sock" + wayland_debug_snapshot "post-choose" + echo "$sock" + return 0 + fi + wayland_debug_snapshot "no-socket" + return 1 } +# Ensure we have a writable XDG_RUNTIME_DIR for the current user. +# Prefers /run/user/, falls back to /tmp/xdg-runtime-. +ensure_xdg_runtime_dir() { + uid="$(id -u 2>/dev/null || echo 0)" + cand="/run/user/$uid" -# Returns true (0) if interface is Ethernet type (type 1 in sysfs) -is_ethernet_interface() { - iface="$1" - [ -f "/sys/class/net/$iface/type" ] && [ "$(cat "/sys/class/net/$iface/type")" = "1" ] -} + if [ ! -d "$cand" ]; then + mkdir -p "$cand" 2>/dev/null || cand="/tmp/xdg-runtime-$uid" + fi -# Get all Ethernet interfaces (excluding common virtual types) -get_ethernet_interfaces() { - for path in /sys/class/net/*; do - iface=$(basename "$path") - case "$iface" in - lo|docker*|br-*|veth*|virbr*|tap*|tun*|wl*) continue ;; - esac - if is_ethernet_interface "$iface"; then - echo "$iface" - fi - done -} + mkdir -p "$cand" 2>/dev/null || true + chmod 700 "$cand" 2>/dev/null || true + export XDG_RUNTIME_DIR="$cand" -# Bring up interface with retries (down before up). -bringup_interface() { - iface="$1"; retries="${2:-3}"; sleep_sec="${3:-2}"; i=0 - while [ $i -lt "$retries" ]; do - if command -v ip >/dev/null 2>&1; then - ip link set "$iface" down - sleep 1 - ip link set "$iface" up - sleep "$sleep_sec" - ip link show "$iface" | grep -q "state UP" && return 0 - elif command -v ifconfig >/dev/null 2>&1; then - ifconfig "$iface" down - sleep 1 - ifconfig "$iface" up - sleep "$sleep_sec" - ifconfig "$iface" | grep -q "UP" && return 0 - fi - i=$((i + 1)) - done - return 1 + log_info "XDG_RUNTIME_DIR ensured: $XDG_RUNTIME_DIR" } -# Wait for a valid IPv4 address on the given interface, up to a timeout (default 30s) -wait_for_ip_address() { - iface="$1" - timeout="${2:-30}" - elapsed=0 - while [ "$elapsed" -lt "$timeout" ]; do - ip_addr=$(get_ip_address "$iface") - if [ -n "$ip_addr" ]; then - if echo "$ip_addr" | grep -q '^169\.254'; then - echo "$ip_addr" - return 2 - fi - echo "$ip_addr" - return 0 +# Choose newest socket (by mtime); logs candidates for debugging. +wayland_pick_socket() { + best="" + best_mtime=0 + + log_info "Wayland sockets found (candidate list):" + for s in $(find_wayland_sockets | sort -u); do + mt="$(stat -c %Y "$s" 2>/dev/null || echo 0)" + log_info " - $s (mtime=$mt)" + if [ "$mt" -gt "$best_mtime" ]; then + best="$s" + best_mtime="$mt" fi - sleep 1 - elapsed=$((elapsed + 1)) done + + if [ -n "$best" ]; then + log_info "Picked Wayland socket (newest): $best" + echo "$best" + return 0 + fi return 1 } -# Get the IPv4 address for a given interface. -get_ip_address() { - iface="$1" +# ---- Wayland/Weston helpers ----------------------- +# Ensure a private XDG runtime directory exists and is usable (0700). +weston_start() { + # Already up? + if weston_is_running; then + log_info "Weston already running." + return 0 + fi + + # 1) Try systemd user/system units if present + if command -v systemctl >/dev/null 2>&1; then + for unit in weston.service weston@.service; do + log_info "Attempting to start via systemd: $unit" + systemctl start "$unit" >/dev/null 2>&1 || true + sleep 1 + if weston_is_running; then + log_info "Weston started via $unit." + return 0 + fi + done + log_warn "systemd start did not bring Weston up; will try direct spawn." + fi + + # Helper: attempt spawn for a given uid (empty => current user) + # Tries multiple backend names (to cover distro/plugin differences) + # Returns 0 if a weston process + socket appears, else non-zero. + spawn_weston_try() { + target_uid="$1" # "" or numeric uid + backends="${WESTON_BACKENDS:-headless headless-backend.so}" + + # Prepare runtime dir + if [ -n "$target_uid" ]; then + run_dir="/run/user/$target_uid" + mkdir -p "$run_dir" 2>/dev/null || true + chown "$target_uid:$target_uid" "$run_dir" 2>/dev/null || true + else + ensure_xdg_runtime_dir + run_dir="$XDG_RUNTIME_DIR" + fi + chmod 700 "$run_dir" 2>/dev/null || true + + # Where to log + log_file="/tmp/weston.${target_uid:-self}.log" + rm -f "$log_file" 2>/dev/null || true + + for be in $backends; do + log_info "Spawning weston (uid=${target_uid:-$(id -u)}) with backend='$be' …" + if ! command -v weston >/dev/null 2>&1; then + log_fail "weston binary not found in PATH." + return 1 + fi + + # Build the command: avoid optional modules that may not exist on minimal builds + cmd="XDG_RUNTIME_DIR='$run_dir' weston --backend='$be' --log='$log_file'" + + if [ -n "$target_uid" ]; then + # Run as that uid if we can + if command -v su >/dev/null 2>&1; then + su -s /bin/sh -c "$cmd >/dev/null 2>&1 &" "#$target_uid" || true + elif command -v runuser >/dev/null 2>&1; then + runuser -u "#$target_uid" -- sh -c "$cmd >/dev/null 2>&1 &" || true + else + log_warn "No su/runuser available to switch uid=$target_uid; skipping this mode." + continue + fi + else + # Current user + ( nohup sh -c "$cmd" >/dev/null 2>&1 & ) || true + fi + + # Wait up to ~5s for process + a socket to appear + tries=0 + while [ $tries -lt 5 ]; do + if weston_is_running; then + # See if a fresh socket is visible + sock="$(wayland_pick_socket)" + if [ -n "$sock" ]; then + log_info "Weston up (backend=$be). Socket: $sock" + return 0 + fi + fi + sleep 1 + tries=$((tries+1)) + done + + # Show weston log tail to aid debugging + if [ -r "$log_file" ]; then + log_warn "Weston did not come up with backend '$be'. Last log lines:" + tail -n 20 "$log_file" | sed 's/^/[weston.log] /' + else + log_warn "Weston did not come up with backend '$be' and no log file present ($log_file)." + fi + done + + return 1 + } + + # 2) Try as current user + if spawn_weston_try ""; then + return 0 + fi + + # 3) Try as 'weston' user (common on embedded images) + weston_uid="" + if command -v getent >/dev/null 2>&1; then + weston_uid="$(getent passwd weston 2>/dev/null | awk -F: '{print $3}')" + fi + [ -z "$weston_uid" ] && weston_uid="$(id -u weston 2>/dev/null || true)" + + if [ -n "$weston_uid" ]; then + log_info "Attempting to spawn Weston as uid=$weston_uid (user 'weston')." + if spawn_weston_try "$weston_uid"; then + return 0 + fi + else + log_info "No 'weston' user found; skipping user-switch spawn." + fi + + log_warn "All weston spawn attempts failed." + return 1 +} + +# Return first Wayland socket under a base dir (prints path or fails). +find_wayland_socket_in() { + base="$1" + [ -d "$base" ] || return 1 + for s in "$base"/wayland-*; do + [ -S "$s" ] || continue + printf '%s\n' "$s" + return 0 + done + return 1 +} + +# Best-effort discovery of a usable Wayland socket anywhere. +discover_wayland_socket_anywhere() { + uid="$(id -u 2>/dev/null || echo 0)" + bases="" + [ -n "$XDG_RUNTIME_DIR" ] && bases="$bases $XDG_RUNTIME_DIR" + bases="$bases /dev/socket/weston /run/user/$uid /tmp/wayland-$uid /dev/shm" + for b in $bases; do + ensure_private_runtime_dir "$b" >/dev/null 2>&1 || true + if s="$(find_wayland_socket_in "$b")"; then + printf '%s\n' "$s" + return 0 + fi + done + return 1 +} + +# Adopt env from a Wayland socket path like /run/user/0/wayland-0 +# Sets XDG_RUNTIME_DIR and WAYLAND_DISPLAY. Returns 0 on success. +adopt_wayland_env_from_socket() { + s="$1" + if [ -z "$s" ] || [ ! -S "$s" ]; then + log_warn "adopt_wayland_env_from_socket: invalid socket: ${s:-}" + return 1 + fi + XDG_RUNTIME_DIR="$(dirname "$s")" + WAYLAND_DISPLAY="$(basename "$s")" + export XDG_RUNTIME_DIR WAYLAND_DISPLAY + # Best-effort perms fix for minimal systems + chmod 700 "$XDG_RUNTIME_DIR" 2>/dev/null || true + log_info "Adopting Wayland environment from socket: $s" + log_info "Adopted Wayland env: XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR WAYLAND_DISPLAY=$WAYLAND_DISPLAY" + log_info "Reproduce with:" + log_info " export XDG_RUNTIME_DIR='$XDG_RUNTIME_DIR'" + log_info " export WAYLAND_DISPLAY='$WAYLAND_DISPLAY'" +} + +# Try to connect to Wayland. Returns 0 on OK. +wayland_can_connect() { + if command -v weston-info >/dev/null 2>&1; then + weston-info >/dev/null 2>&1 + return $? + fi + # fallback: quick client probe + ( env -i XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" WAYLAND_DISPLAY="$WAYLAND_DISPLAY" true ) >/dev/null 2>&1 + return $? +} + +# Ensure a Weston socket exists; if not, stop+start Weston and adopt helper socket. +weston_pick_env_or_start() { + sock="$(discover_wayland_socket_anywhere 2>/dev/null || true)" + if [ -n "$sock" ]; then + adopt_wayland_env_from_socket "$sock" + log_info "Selected Wayland socket: $sock" + return 0 + fi + + if weston_is_running; then + log_info "Stopping Weston..." + weston_stop + i=0; while weston_is_running && [ "$i" -lt 5 ]; do i=$((i+1)); sleep 1; done + fi + + log_info "Starting Weston..." + weston_start + i=0; sock="" + while [ "$i" -lt 6 ]; do + sock="$(find_wayland_socket_in /dev/socket/weston 2>/dev/null || true)" + [ -n "$sock" ] && break + sleep 1; i=$((i+1)) + done + if [ -z "$sock" ]; then + log_fail "Could not find Wayland socket after starting Weston." + return 1 + fi + adopt_wayland_env_from_socket "$sock" + log_info "Weston started; socket: $sock" + return 0 +} + +# Find candidate Wayland sockets in common locations. +# Prints absolute socket paths, one per line, most-preferred first. +find_wayland_sockets() { + # Enumerate plausible Wayland sockets (one per line) + uid="$(id -u 2>/dev/null || echo 0)" + + # Current env first (if valid) + if [ -n "${XDG_RUNTIME_DIR:-}" ] && [ -n "${WAYLAND_DISPLAY:-}" ] && + [ -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]; then + echo "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" + fi + + # Current uid + for f in "/run/user/$uid/wayland-0" "/run/user/$uid/wayland-1" "/run/user/$uid/wayland-2"; do + [ -S "$f" ] && echo "$f" + done + for f in /run/user/"$uid"/wayland-*; do + [ -S "$f" ] && echo "$f" + done 2>/dev/null + + # Any user under /run/user (root can traverse) — covers weston running as uid 1000 + for d in /run/user/*; do + [ -d "$d" ] || continue + for f in "$d"/wayland-*; do + [ -S "$f" ] && echo "$f" + done + done 2>/dev/null + + # weston-launch sockets + for f in /dev/socket/weston/wayland-*; do + [ -S "$f" ] && echo "$f" + done 2>/dev/null + + # Last resort + for f in /tmp/wayland-*; do + [ -S "$f" ] && echo "$f" + done 2>/dev/null +} + +# Ensure XDG_RUNTIME_DIR has owner=current-user and mode 0700. +# Returns 0 if OK (or fixed), non-zero if still not compliant. +ensure_wayland_runtime_dir_perms() { + dir="$1" + [ -n "$dir" ] && [ -d "$dir" ] || return 1 + + cur_uid="$(id -u 2>/dev/null || echo 0)" + cur_gid="$(id -g 2>/dev/null || echo 0)" + + # Best-effort fixups first (don’t error if chown/chmod fail) + chown "$cur_uid:$cur_gid" "$dir" 2>/dev/null || true + chmod 0700 "$dir" 2>/dev/null || true + + # Verify using stat (GNU first, then BSD). If stat is unavailable, + # we can’t verify—assume OK to avoid SC2012 (ls) usage. + if command -v stat >/dev/null 2>&1; then + # Mode: GNU: %a ; BSD: %Lp + mode="$(stat -c '%a' "$dir" 2>/dev/null || stat -f '%Lp' "$dir" 2>/dev/null || echo '')" + # Owner uid: GNU: %u ; BSD: %u + uid="$(stat -c '%u' "$dir" 2>/dev/null || stat -f '%u' "$dir" 2>/dev/null || echo '')" + + [ "$mode" = "700" ] && [ "$uid" = "$cur_uid" ] && return 0 + return 1 + fi + + # No stat available: directory exists and we attempted to fix perms/owner. + # Treat as success so clients can try; avoids SC2012 warnings. + return 0 +} + +# Quick Wayland handshake check. +# Prefers `wayland-info` with a short timeout; otherwise validates socket presence. +# Also enforces/fixes XDG_RUNTIME_DIR permissions so clients won’t reject it. +wayland_connection_ok() { + if command -v wayland-info >/dev/null 2>&1; then + log_info "Probing Wayland with: wayland-info" + wayland-info >/dev/null 2>&1 && return 0 + return 1 + fi + if command -v weston-info >/dev/null 2>&1; then + log_info "Probing Wayland with: weston-info" + weston-info >/dev/null 2>&1 && return 0 + return 1 + fi + if command -v weston-simple-egl >/dev/null 2>&1; then + log_info "Probing Wayland by briefly starting weston-simple-egl" + ( weston-simple-egl >/dev/null 2>&1 & echo $! >"/tmp/.wsegl.$$" ) + pid="$(cat "/tmp/.wsegl.$$" 2>/dev/null || echo)" + rm -f "/tmp/.wsegl.$$" 2>/dev/null || true + i=0 + while [ $i -lt 2 ]; do + sleep 1 + i=$((i+1)) + done + if [ -n "$pid" ]; then + kill "$pid" 2>/dev/null || true + fi + # If it started at all, consider the connection OK (best effort). + return 0 + fi + if [ -n "$XDG_RUNTIME_DIR" ] && [ -n "$WAYLAND_DISPLAY" ] && [ -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]; then + log_info "No probe tools present; accepting socket existence as OK." + return 0 + fi + return 1 +} +# Very verbose snapshot for debugging (processes, sockets, env, perms). +wayland_debug_snapshot() { + label="$1" + [ -n "$label" ] || label="snapshot" + log_info "----- Wayland/Weston debug snapshot: $label -----" + + # Processes + wpids="$(weston_pids)" + if [ -n "$wpids" ]; then + log_info "weston PIDs: $wpids" + for p in $wpids; do + if command -v ps >/dev/null 2>&1; then + ps -o pid,user,group,cmd -p "$p" 2>/dev/null | sed 's/^/[ps] /' || true + fi + if [ -r "/proc/$p/cmdline" ]; then + tr '\0' ' ' <"/proc/$p/cmdline" 2>/dev/null | sed 's/^/[cmdline] /' || true + fi + done + else + log_info "weston PIDs: (none)" + fi + + # Sockets (meta) — use stat instead of ls (SC2012) + for s in $(find_wayland_sockets | sort -u); do + log_info "socket: $s" + stat -c '[stat] %n -> owner=%U:%G mode=%A size=%s mtime=%y' "$s" 2>/dev/null || true + done + + # Current env + log_info "Env now: XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-} WAYLAND_DISPLAY=${WAYLAND_DISPLAY:-}" + if [ -n "${XDG_RUNTIME_DIR:-}" ]; then + stat -c '[stat] %n -> owner=%U:%G mode=%A size=%s mtime=%y' "$XDG_RUNTIME_DIR" 2>/dev/null || true + fi + + log_info "Suggested export (current env):" + log_info " export XDG_RUNTIME_DIR='${XDG_RUNTIME_DIR:-}'" + log_info " export WAYLAND_DISPLAY='${WAYLAND_DISPLAY:-}'" + + log_info "----- End snapshot: $label -----" +} + +# Print concise metadata for a path (portable). +# Prefers stat(1) (GNU or BSD); falls back to ls(1) only if needed. +# Usage: print_path_meta "/some/path" +print_path_meta() { + p=$1 + if [ -z "$p" ]; then + return 1 + fi + # GNU stat + if stat -c '%A %U %G %a %n' "$p" >/dev/null 2>&1; then + stat -c '%A %U %G %a %n' "$p" + return 0 + fi + # BSD/Mac stat + if stat -f '%Sp %Su %Sg %OLp %N' "$p" >/dev/null 2>&1; then + stat -f '%Sp %Su %Sg %OLp %N' "$p" + return 0 + fi + # shellcheck disable=SC2012 + ls -ld -- "$p" 2>/dev/null +} + +############################################################################### +# DRM / Display helpers (portable, minimal-build friendly) +############################################################################### + +# Echo lines: "\t\t\t\t" +# Example: "card0-HDMI-A-1 connected HDMI-A 9 1920x1080" +display_list_connectors() { + found=0 + for d in /sys/class/drm/*-*; do + [ -e "$d" ] || continue + [ -f "$d/status" ] || continue + name="$(basename "$d")" + status="$(tr -d '\r\n' <"$d/status" 2>/dev/null)" + + # Derive connector type from name: cardX--N + # Strip "cardN-" prefix and trailing "-N" index. + typ="$(printf '%s' "$name" \ + | sed -n 's/^card[0-9]\+-\([A-Za-z0-9+]\+\(-[A-Za-z0-9+]\+\)*\)-[0-9]\+/\1/p')" + [ -z "$typ" ] && typ="unknown" + + # Modes + modes_file="$d/modes" + if [ -f "$modes_file" ]; then + # wc output can have spaces on BusyBox; trim + mc="$(wc -l <"$modes_file" 2>/dev/null | tr -d '[:space:]')" + [ -z "$mc" ] && mc=0 + fm="$(head -n 1 "$modes_file" 2>/dev/null | tr -d '\r\n')" + else + mc=0 + fm="" + fi + + printf '%s\t%s\t%s\t%s\t%s\n' "$name" "$status" "$typ" "$mc" "$fm" + found=1 + done + [ "$found" -eq 1 ] || return 1 + return 0 +} + +# Return 0 if any connector is connected; else 1 +display_any_attached() { + for d in /sys/class/drm/*-*; do + [ -f "$d/status" ] || continue + st="$(tr -d '\r\n' <"$d/status" 2>/dev/null)" + if [ "$st" = "connected" ]; then + return 0 + fi + done + return 1 +} + +# Print one compact human line summarizing connected outputs +display_connected_summary() { + have=0 + line="" + # shellcheck disable=SC2039 + while IFS="$(printf '\t')" read -r name status typ mc fm; do + [ "$status" = "connected" ] || continue + have=1 + if [ -n "$fm" ]; then + seg="${name}(${typ},${fm})" + else + seg="${name}(${typ})" + fi + if [ -z "$line" ]; then line="$seg"; else line="$line, $seg"; fi + done </dev/null || true) +EOF + if [ "$have" -eq 1 ]; then + echo "$line" + return 0 + fi + echo "none" + return 1 +} + +# Best-effort "primary" guess: first connected with a mode; else first connected; echoes name +display_primary_guess() { + best="" + # Prefer one with modes + # shellcheck disable=SC2039 + while IFS="$(printf '\t')" read -r name status typ mc fm; do + [ "$status" = "connected" ] || continue + if [ -n "$fm" ]; then echo "$name"; return 0; fi + [ -z "$best" ] && best="$name" + done </dev/null || true) +EOF + [ -n "$best" ] && { echo "$best"; return 0; } + return 1 +} + +# Optional enrichment via weston-info (if available) +# Prints lines: "weston: model= make= phys=mm" +display_weston_outputs() { + if ! command -v weston-info >/dev/null 2>&1; then + return 0 + fi + # Very light parse; tolerate different locales + weston-info 2>/dev/null \ + | awk ' + $1=="output" && $2~/^[0-9]+:$/ {out=$2; sub(":","",out)} + /make:/ {make=$2} + /model:/ {model=$2} + /physical size:/ {w=$3; h=$5; sub("mm","",h)} + /scale:/ { + if (out!="") { + printf("weston: %s make=%s model=%s phys=%sx%sm\n", out, make, model, w, h); + out=""; make=""; model=""; w=""; h=""; + } + } + ' + return 0 +} + +# One-stop debug snapshot +display_debug_snapshot() { + ctx="$1" + [ -z "$ctx" ] && ctx="display-snapshot" + log_info "----- Display snapshot: $ctx -----" + + # DRM nodes (no ls; iterate) + nodes="" + for f in /dev/dri/card* /dev/dri/renderD*; do + if [ -e "$f" ]; then + if [ -z "$nodes" ]; then nodes="$f"; else nodes="$nodes $f"; fi + fi + done + if [ -n "$nodes" ]; then + log_info "DRM nodes: $nodes" + else + log_warn "No /dev/dri/* nodes found." + fi + + # Connectors + have=0 + # shellcheck disable=SC2039 + while IFS="$(printf '\t')" read -r name status typ mc fm; do + have=1 + if [ -n "$fm" ]; then + log_info "DRM: ${name} status=${status} type=${typ} modes=${mc} first=${fm}" + else + log_info "DRM: ${name} status=${status} type=${typ} modes=${mc}" + fi + done </dev/null || true) +EOF + [ "$have" -eq 1 ] || log_warn "No DRM connectors in /sys/class/drm." + + # Summary + weston outputs (if any) + sum="$(display_connected_summary 2>/dev/null || echo none)" + log_info "Connected summary: $sum" + display_weston_outputs | while IFS= read -r l; do + [ -n "$l" ] && log_info "$l" + done + + log_info "----- End display snapshot: $ctx -----" +} + +display_debug_snapshot() { + ctx="$1" + [ -z "$ctx" ] && ctx="display-snapshot" + log_info "----- Display snapshot: $ctx -----" + + # DRM nodes + nodes="" + for f in /dev/dri/card* /dev/dri/renderD*; do + [ -e "$f" ] && nodes="${nodes:+$nodes }$f" + done + if [ -n "$nodes" ]; then + log_info "DRM nodes: $nodes" + else + log_warn "No /dev/dri/* nodes found." + fi + + # Sysfs connectors (expects display_list_connectors to print tab-separated fields) + have=0 + while IFS="$(printf '\t')" read -r name status typ mc fm; do + [ -n "$name" ] || continue + have=1 + if [ -n "$fm" ]; then + log_info "DRM: ${name} status=${status} type=${typ} modes=${mc} first=${fm}" + else + log_info "DRM: ${name} status=${status} type=${typ} modes=${mc}" + fi + done </dev/null || true) +EOF + [ "$have" -eq 1 ] || log_warn "No DRM connectors in /sys/class/drm." + + # Connected summary (sysfs) + sum="$(display_connected_summary 2>/dev/null || echo none)" + log_info "Connected summary (sysfs): $sum" + + # Optional weston outputs (existing helper) + display_weston_outputs | while IFS= read -r l; do + [ -n "$l" ] && log_info "$l" + done + + log_info "----- End display snapshot: $ctx -----" +} + +# Returns true (0) if interface is administratively and physically up +is_interface_up() { + iface="$1" + if [ -f "/sys/class/net/$iface/operstate" ]; then + [ "$(cat "/sys/class/net/$iface/operstate")" = "up" ] + elif command -v ip >/dev/null 2>&1; then + ip link show "$iface" 2>/dev/null | grep -qw "state UP" + elif command -v ifconfig >/dev/null 2>&1; then + ifconfig "$iface" 2>/dev/null | grep -qw "UP" + else + return 1 + fi +} + +# Returns true (0) if physical link/carrier is detected (cable plugged in) +is_link_up() { + iface="$1" + [ -f "/sys/class/net/$iface/carrier" ] && [ "$(cat "/sys/class/net/$iface/carrier")" = "1" ] +} + +# Returns true (0) if interface is Ethernet type (type 1 in sysfs) +is_ethernet_interface() { + iface="$1" + [ -f "/sys/class/net/$iface/type" ] && [ "$(cat "/sys/class/net/$iface/type")" = "1" ] +} + +# Get all Ethernet interfaces (excluding common virtual types) +get_ethernet_interfaces() { + for path in /sys/class/net/*; do + iface=$(basename "$path") + case "$iface" in + lo|docker*|br-*|veth*|virbr*|tap*|tun*|wl*) continue ;; + esac + if is_ethernet_interface "$iface"; then + echo "$iface" + fi + done +} + +# Bring up interface with retries (down before up). +bringup_interface() { + iface="$1"; retries="${2:-3}"; sleep_sec="${3:-2}"; i=0 + while [ $i -lt "$retries" ]; do + if command -v ip >/dev/null 2>&1; then + ip link set "$iface" down + sleep 1 + ip link set "$iface" up + sleep "$sleep_sec" + ip link show "$iface" | grep -q "state UP" && return 0 + elif command -v ifconfig >/dev/null 2>&1; then + ifconfig "$iface" down + sleep 1 + ifconfig "$iface" up + sleep "$sleep_sec" + ifconfig "$iface" | grep -q "UP" && return 0 + fi + i=$((i + 1)) + done + return 1 +} + +# Wait for a valid IPv4 address on the given interface, up to a timeout (default 30s) +wait_for_ip_address() { + iface="$1" + timeout="${2:-30}" + elapsed=0 + while [ "$elapsed" -lt "$timeout" ]; do + ip_addr=$(get_ip_address "$iface") + if [ -n "$ip_addr" ]; then + if echo "$ip_addr" | grep -q '^169\.254'; then + echo "$ip_addr" + return 2 + fi + echo "$ip_addr" + return 0 + fi + sleep 1 + elapsed=$((elapsed + 1)) + done + return 1 +} + +# Get the IPv4 address for a given interface. +get_ip_address() { + iface="$1" if command -v ip >/dev/null 2>&1; then ip -4 -o addr show "$iface" | awk '{print $4}' | cut -d/ -f1 | head -n1 elif command -v ifconfig >/dev/null 2>&1; then @@ -515,25 +1525,33 @@ get_ip_address() { # Run a command with a timeout (in seconds) run_with_timeout() { timeout="$1"; shift - [ -z "$timeout" ] && { "$@"; return $?; } - - if command -v timeout >/dev/null 2>&1; then - timeout "$timeout" "$@" - return $? - fi + ( "$@" ) & + pid=$! + ( sleep "$timeout"; kill "$pid" 2>/dev/null ) & + watcher=$! + wait $pid 2>/dev/null + status=$? + kill $watcher 2>/dev/null + return $status +} + +# Only apply a timeout if TIMEOUT is set; prefer `timeout`; avoid functestlib here +runWithTimeoutIfSet() { + # Normalize TIMEOUT: treat empty or non-numeric as 0 + t="${TIMEOUT:-}" + case "$t" in + ''|*[!0-9]*) + t=0 + ;; + esac - # fallback if coreutils timeout is missing - ( - "$@" & - pid=$! - ( sleep "$timeout"; kill "$pid" 2>/dev/null ) & - watcher=$! - wait $pid 2>/dev/null - status=$? - kill $watcher 2>/dev/null - exit $status - ) - return $? + if [ "$t" -gt 0 ] && command -v run_with_timeout >/dev/null 2>&1; then + # Correct signature: run_with_timeout [args...] + run_with_timeout "$t" "$@" + else + # No timeout -> run command directly + "$@" + fi } # DHCP client logic (dhclient and udhcpc with timeouts) @@ -1061,6 +2079,19 @@ dt_has_remoteproc_fw() { return 1 } +# Find the remoteproc path for a given firmware substring (e.g., "adsp", "cdsp", "gdsp"). +get_remoteproc_path_by_firmware() { + name="$1" + idx path + # List all remoteproc firmware nodes, match name, and return the remoteproc path + idx=$(cat /sys/class/remoteproc/remoteproc*/firmware 2>/dev/null | grep -n "$name" | cut -d: -f1 | head -n1) + [ -z "$idx" ] && return 1 + idx=$((idx - 1)) + path="/sys/class/remoteproc/remoteproc${idx}" + [ -d "$path" ] && echo "$path" && return 0 + return 1 +} + # Get remoteproc state get_remoteproc_state() { rp="$1" @@ -1144,45 +2175,41 @@ start_remoteproc() { printf 'start\n' >"$statef" 2>/dev/null || return 1 wait_remoteproc_state "$path" running 6 } - -# Returns 0 if every rproc using $1 firmware is running; non-zero otherwise. +# Validate remoteproc running state with retries and logging validate_remoteproc_running() { - fw="$1" - - # Fast skip: if DT doesn't advertise this firmware, there may be nothing to validate - if command -v dt_has_remoteproc_fw >/dev/null 2>&1; then - if ! dt_has_remoteproc_fw "$fw"; then - log_skip "DT does not list '$fw' remoteproc; skipping rproc state check" - return 0 - fi - fi - - # functestlib helper prints "path|state|firmware|name" (one line per instance) - entries="$(get_remoteproc_by_firmware "$fw" "" all 2>/dev/null)" || entries="" - - if [ -z "$entries" ]; then - log_fail "No /sys/class/remoteproc entry found for firmware '$fw'" + fw_name="$1" + log_file="${2:-/dev/null}" + max_wait_secs="${3:-10}" + delay_per_try_secs="${4:-1}" + + rproc_path=$(get_remoteproc_path_by_firmware "$fw_name") + if [ -z "$rproc_path" ]; then + echo "[ERROR] Remoteproc for '$fw_name' not found" >> "$log_file" + { + echo "---- Last 20 remoteproc dmesg logs ----" + dmesg | grep -i "remoteproc" | tail -n 20 + echo "----------------------------------------" + } >> "$log_file" return 1 fi - - fail=0 - while IFS='|' read -r rpath rstate rfirm rname; do - [ -n "$rpath" ] || continue - inst="$(basename "$rpath")" - log_info "remoteproc entry: path=$rpath state=$rstate firmware=$rfirm name=$rname" - if [ "$rstate" = "running" ]; then - log_pass "$inst: running" - else - # If you prefer to re-query, uncomment next line: - # rstate="$(get_remoteproc_state "$rpath" 2>/dev/null || echo "$rstate")" - log_fail "$inst: not running (state=$rstate)" - fail=$((fail + 1)) + + total_waited=0 + while [ "$total_waited" -lt "$max_wait_secs" ]; do + state=$(get_remoteproc_state "$rproc_path") + if [ "$state" = "running" ]; then + return 0 fi - done <<__RPROC_LIST__ -$entries -__RPROC_LIST__ - - [ "$fail" -eq 0 ] + sleep "$delay_per_try_secs" + total_waited=$((total_waited + delay_per_try_secs)) + done + + echo "[ERROR] $fw_name remoteproc did not reach 'running' state within ${max_wait_secs}s (last state: $state)" >> "$log_file" + { + echo "---- Last 20 remoteproc dmesg logs ----" + dmesg | grep -i "remoteproc" | tail -n 20 + echo "----------------------------------------" + } >> "$log_file" + return 1 } # acquire_test_lock @@ -1406,17 +2433,82 @@ retry_command() { return 1 } -# Connect using nmcli with retries (returns 0 on success) +# Connect to Wi-Fi using nmcli, with fallback when key-mgmt is required wifi_connect_nmcli() { iface="$1" ssid="$2" pass="$3" - if command -v nmcli >/dev/null 2>&1; then - log_info "Trying to connect using nmcli..." - retry_command "nmcli dev wifi connect \"$ssid\" password \"$pass\" ifname \"$iface\" 2>&1 | tee nmcli.log" 3 3 - return $? + + if ! command -v nmcli >/dev/null 2>&1; then + return 1 fi - return 1 + + log_info "Trying to connect using nmcli..." + mkdir -p "${LOG_DIR:-.}" 2>/dev/null || true + nm_log="${LOG_DIR:-.}/nmcli_${iface}_$(printf '%s' "$ssid" | tr ' /' '__').log" + + # First try the simple connect path (what you already had) + if [ -n "$pass" ]; then + retry_command "nmcli dev wifi connect \"$ssid\" password \"$pass\" ifname \"$iface\" 2>&1 | tee \"$nm_log\"" 3 3 + else + retry_command "nmcli dev wifi connect \"$ssid\" ifname \"$iface\" 2>&1 | tee \"$nm_log\"" 3 3 + fi + rc=$? + [ $rc -eq 0 ] && return 0 + + # Look for the specific error and fall back to creating a connection profile + if grep -qi '802-11-wireless-security\.key-mgmt.*missing' "$nm_log"; then + log_warn "nmcli connect complained about missing key-mgmt; creating an explicit connection profile..." + + nmcli -t -f WIFI nm status >/dev/null 2>&1 || nmcli r wifi on >/dev/null 2>&1 || true + nmcli dev set "$iface" managed yes >/dev/null 2>&1 || true + nmcli dev disconnect "$iface" >/dev/null 2>&1 || true + nmcli dev wifi rescan >/dev/null 2>&1 || true + + con_name="$ssid" + # If a connection with the same name exists, drop it to avoid conflicts + if nmcli -t -f NAME con show 2>/dev/null | grep -Fxq "$con_name"; then + nmcli con delete "$con_name" >/dev/null 2>&1 || true + fi + + if [ -n "$pass" ]; then + # Try WPA2 PSK first (most common) + if nmcli con add type wifi ifname "$iface" con-name "$con_name" ssid "$ssid" \ + wifi-sec.key-mgmt wpa-psk wifi-sec.psk "$pass" >>"$nm_log" 2>&1; then + if nmcli con up "$con_name" ifname "$iface" >>"$nm_log" 2>&1; then + log_pass "Connected to $ssid via explicit profile (wpa-psk)." + return 0 + fi + fi + + # If that failed, try WPA3-Personal (SAE), some APs require it + log_warn "Profile up failed; trying WPA3 (sae) profile..." + nmcli con delete "$con_name" >/dev/null 2>&1 || true + if nmcli con add type wifi ifname "$iface" con-name "$con_name" ssid "$ssid" \ + wifi-sec.key-mgmt sae wifi-sec.psk "$pass" >>"$nm_log" 2>&1; then + if nmcli con up "$con_name" ifname "$iface" >>"$nm_log" 2>&1; then + log_pass "Connected to $ssid via explicit profile (sae)." + return 0 + fi + fi + else + # Open network (no passphrase) + if nmcli con add type wifi ifname "$iface" con-name "$con_name" ssid "$ssid" \ + wifi-sec.key-mgmt none >>"$nm_log" 2>&1; then + if nmcli con up "$con_name" ifname "$iface" >>"$nm_log" 2>&1; then + log_pass "Connected to open network $ssid." + return 0 + fi + fi + fi + + log_fail "Failed to connect to $ssid even after explicit key-mgmt profile. See $nm_log" + return 1 + fi + + # Different error — just bubble up the original failure + log_fail "nmcli failed to connect to $ssid. See $nm_log" + return $rc } # Connect using wpa_supplicant+udhcpc with retries (returns 0 on success) @@ -1751,94 +2843,93 @@ wait_for_path() { return 1 } -# Skip unwanted device tree node names during DT parsing (e.g., aliases, metadata). -# Used to avoid traversing irrelevant or special nodes in recursive scans. -dt_should_skip_node() { - case "$1" in - ''|__*|phandle|name|'#'*|aliases|chosen|reserved-memory|interrupt-controller|thermal-zones) - return 0 ;; - esac - return 1 -} +# --------------------------------------------------------------------- +# Ultra-light DT matchers (no indexing; BusyBox-safe; O(first hit)) +# --------------------------------------------------------------------- +DT_ROOT="/proc/device-tree" -# Recursively yield all directories (nodes) under a DT root path. -# Terminates early if a match result is found in the provided file. -dt_yield_node_paths() { - _root="$1" - _matchresult="$2" - # If we've already matched, stop recursion immediately! - [ -s "$_matchresult" ] && return - for entry in "$_root"/*; do - [ -d "$entry" ] || continue - [ -s "$_matchresult" ] && return - echo "$entry" - dt_yield_node_paths "$entry" "$_matchresult" - done -} +# Print matches for EVERY pattern given; return 0 if any matched, else 1. +# Output format (unchanged): +# - node name match: ": ./path/to/node" +# - compatible file match:": ./path/to/compatible:vendor,chip[,...]" +dt_confirm_node_or_compatible_all() { + LC_ALL=C + any=0 -# Recursively search for files named "compatible" in DT paths. -# Terminates early if a match result is already present. -dt_yield_compatible_files() { - _root="$1" - _matchresult="$2" - [ -s "$_matchresult" ] && return - for entry in "$_root"/*; do - [ -e "$entry" ] || continue - [ -s "$_matchresult" ] && return - if [ -f "$entry" ] && [ "$(basename "$entry")" = "compatible" ]; then - echo "$entry" - elif [ -d "$entry" ]; then - dt_yield_compatible_files "$entry" "$_matchresult" + for pattern in "$@"; do + pl=$(printf '%s' "$pattern" | tr '[:upper:]' '[:lower:]') + + # -------- Pass 1: node name strict match (^pat(@|$)) in .../name files -------- + # BusyBox grep: -r (recursive), -s (quiet errors), -i (CI), -a (treat binary as text), + # -E (ERE), -l (print file names), -m1 (stop after first match). + name_file="$(grep -r -s -i -a -E -l -m1 "^${pl}(@|$)" "$DT_ROOT" 2>/dev/null | grep '/name$' -m1 || true)" + if [ -n "$name_file" ]; then + rel="${name_file#"$DT_ROOT"/}" + rel="${rel%/name}" + log_info "[DTFIND] Node name strict match: /$rel (pattern: $pattern)" + printf '%s: ./%s\n' "$pattern" "$rel" + any=1 + continue fi - done -} - -# Searches /proc/device-tree for nodes or compatible strings matching input patterns. -# Returns success and matched path if any pattern is found; used in pre-test validation. -dt_confirm_node_or_compatible() { - root="/proc/device-tree" - matchflag=$(mktemp) || exit 1 - matchresult=$(mktemp) || { rm -f "$matchflag"; exit 1; } - for pattern in "$@"; do - # Node search: strict prefix (e.g., "isp" only matches "isp@...") - for entry in $(dt_yield_node_paths "$root" "$matchresult"); do - [ -s "$matchresult" ] && break - node=$(basename "$entry") - dt_should_skip_node "$node" && continue - printf '%s' "$node" | grep -iEq "^${pattern}(@|$)" || continue - log_info "[DTFIND] Node name strict prefix match: $entry (pattern: $pattern)" - if [ ! -s "$matchresult" ]; then - echo "${pattern}:${entry}" > "$matchresult" + # -------- Pass 2: compatible matches -------- + # Heuristic: + # - If pattern has a comma (e.g. "sony,imx577"), treat it as a full vendor:part token. + # We just need the first compatible file that contains that exact substring. + # - Else (e.g. "isp", "cam", "camss"), treat as IP family: + # token == pat OR token ends with -pat OR token prefix pat + # We still find a small candidate set via grep and vet tokens precisely. + case "$pattern" in *,*) + # label as chip (the part after the last comma) to avoid "swapped" look + chip_label="${pattern##*,}" + comp_file="$(grep -r -s -i -F -l -m1 -- "$pattern" "$DT_ROOT" 2>/dev/null | grep '/compatible$' -m1 || true)" + if [ -n "$comp_file" ]; then + comp_print="$(tr '\0' ' ' <"$comp_file" 2>/dev/null)" + comp_csv="$(tr '\0' ',' <"$comp_file" 2>/dev/null)"; comp_csv="${comp_csv%,}" + rel="${comp_file#"$DT_ROOT"/}" + log_info "[DTFIND] Compatible strict match: /$rel (${comp_print}) (pattern: $pattern)" + # Label by chip (e.g., "imx577: ./…:sony,imx577") + printf '%s: ./%s:%s\n' "$chip_label" "$rel" "$comp_csv" + any=1 + continue fi - touch "$matchflag" - done - [ -s "$matchresult" ] && break - - # Compatible property search: strict (prefix or whole word) - for file in $(dt_yield_compatible_files "$root" "$matchresult"); do - [ -s "$matchresult" ] && break - compdir=$(dirname "$file") - node=$(basename "$compdir") - dt_should_skip_node "$node" && continue - compval=$(tr '\0' ' ' < "$file") - printf '%s' "$compval" | grep -iEq "(^|[ ,])${pattern}([ ,]|$)" || continue - log_info "[DTFIND] Compatible strict match: $file (${compval}) (pattern: $pattern)" - if [ ! -s "$matchresult" ]; then - echo "${pattern}:${file}" > "$matchresult" + ;; + *) + cand_list="$(grep -r -s -i -F -l -- "$pattern" "$DT_ROOT" 2>/dev/null | grep '/compatible$' | head -n 64 || true)" + if [ -n "$cand_list" ]; then + for comp_file in $cand_list; do + hit=0 + while IFS= read -r tok; do + tokl=$(printf '%s' "$tok" | tr '[:upper:]' '[:lower:]') + case "$tokl" in + "$pl"|*-"$pl"|"$pl"*) hit=1; break ;; + esac + done </dev/null) +EOF + if [ "$hit" -eq 1 ]; then + comp_print="$(tr '\0' ' ' <"$comp_file" 2>/dev/null)" + comp_csv="$(tr '\0' ',' <"$comp_file" 2>/dev/null)"; comp_csv="${comp_csv%,}" + rel="${comp_file#"$DT_ROOT"/}" + log_info "[DTFIND] Compatible strict match: /$rel (${comp_print}) (pattern: $pattern)" + # Label stays as the generic pattern (isp/cam/camss) + printf '%s: ./%s:%s\n' "$pattern" "$rel" "$comp_csv" + any=1 + break + fi + done fi - touch "$matchflag" - done - [ -s "$matchresult" ] && break + ;; + esac + # if neither pass matched, we stay silent for this pattern done - if [ -f "$matchflag" ] && [ -s "$matchresult" ]; then - cat "$matchresult" - rm -f "$matchflag" "$matchresult" - return 0 - fi - rm -f "$matchflag" "$matchresult" - return 1 + [ "$any" -eq 1 ] +} + +# Back-compat: single-pattern wrapper +dt_confirm_node_or_compatible() { + dt_confirm_node_or_compatible_all "$@" } # Detects and returns the first available media node (e.g., /dev/media0). @@ -1941,350 +3032,817 @@ $val" ;; } # Applies media configuration (format and links) using media-ctl from parsed pipeline block. -# Resets media graph first and applies user-specified format override if needed. +# Mirrors manual flow: +# - Global reset (prefer 'reset', fallback to -r) +# - Pads use MBUS code (never '*P'); if USER_FORMAT ends with P, strip P for pads +# - Strip any 'field:*' tokens from -V lines +# - Apply ONLY these 2 links: +# csiphy*:1 -> csid*:0 +# csid*:1 -> *rdi*:0 +# - Small settle before capture configure_pipeline_block() { MEDIA_NODE="$1" + USER_FORMAT="$2" - # Keep arg2 for API compatibility: pads use MBUS formats here; the - # requested video pixfmt is applied later by execute_capture_block(). - # Mark as intentionally read to silence ShellCheck SC2034. - # shellcheck disable=SC2034 - USER_FORMAT="${2:-}" - : "${USER_FORMAT:-}" # intentional no-op read - - # Clean slate, like your manual 'reset' - log_info "[CMD] media-ctl -d \"$MEDIA_NODE\" --reset" - media-ctl -d "$MEDIA_NODE" --reset - - # 1) Apply pad formats exactly as parsed. If a line fails with _1X10, - # retry once with the short token (SRGGB10) — some trees prefer it. - printf '%s\n' "$MEDIA_CTL_V_LIST" | while IFS= read -r vline; do - [ -n "$vline" ] || continue - log_info "[CMD] media-ctl -d \"$MEDIA_NODE\" -V \"$vline\"" - if ! media-ctl -d "$MEDIA_NODE" -V "$vline"; then - # fallback: *_1X10 → SRGGB10 for Bayer 10; extend if needed later - vline_fallback="$(printf '%s' "$vline" | sed -E 's/fmt:SRGGB10_1X10\//fmt:SRGGB10\//g')" - if [ "$vline_fallback" != "$vline" ]; then - log_warn " -V failed, retrying with: $vline_fallback" - log_info "[CMD] media-ctl -d \"$MEDIA_NODE\" -V \"$vline_fallback\"" - media-ctl -d "$MEDIA_NODE" -V "$vline_fallback" || true - fi + # Reset graph + if ! media-ctl -d "$MEDIA_NODE" reset >/dev/null 2>&1; then + media-ctl -d "$MEDIA_NODE" -r >/dev/null 2>&1 || true + fi + + # Apply pad formats (MBUS, never *P). Also strip 'field:*'. + printf "%s\n" "$MEDIA_CTL_V_LIST" | while IFS= read -r vline || [ -n "$vline" ]; do + [ -z "$vline" ] && continue + + curfmt="$(printf "%s" "$vline" | sed -n 's/.*fmt:\([^/]*\)\/.*/\1/p')" + newfmt="$curfmt" + + if [ -n "$USER_FORMAT" ]; then + case "$USER_FORMAT" in + *P) newfmt="${USER_FORMAT%P}" ;; + *) newfmt="$USER_FORMAT" ;; + esac + else + case "$curfmt" in + *P) newfmt="${curfmt%P}" ;; + *) newfmt="$curfmt" ;; + esac + fi + + vline_new="$(printf "%s" "$vline" | sed -E "s/fmt:[^/]+/fmt:${newfmt}/")" + vline_new="$(printf "%s" "$vline_new" | sed -E 's/ field:[^]]*//g')" + + if [ -n "$YAVTA_W" ] && [ -n "$YAVTA_H" ]; then + vline_new="$(printf "%s" "$vline_new" \ + | sed -E "s/(fmt:[^/]+\/)[0-9]+x[0-9]+/\1${YAVTA_W}x${YAVTA_H}/")" fi - + + media-ctl -d "$MEDIA_NODE" -V "$vline_new" >/dev/null 2>&1 done - # 2) Apply links exactly as parsed (same order as your manual sequence). - printf '%s\n' "$MEDIA_CTL_L_LIST" | while IFS= read -r lline; do - [ -n "$lline" ] || continue - log_info "[CMD] media-ctl -d \"$MEDIA_NODE\" -l \"$lline\"" - media-ctl -d "$MEDIA_NODE" -l "$lline" + # Apply ONLY the two links (no fragile case patterns; avoids ShellCheck parse issues). + printf "%s\n" "$MEDIA_CTL_L_LIST" | while IFS= read -r lline || [ -n "$lline" ]; do + [ -z "$lline" ] && continue + sline="$(printf "%s" "$lline" | tr -d '"')" + if [ "${sline#*csiphy*:1->*csid*:0*}" != "$sline" ]; then + media-ctl -d "$MEDIA_NODE" -l "$lline" >/dev/null 2>&1 + elif [ "${sline#*csid*:1->*rdi*:0*}" != "$sline" ]; then + media-ctl -d "$MEDIA_NODE" -l "$lline" >/dev/null 2>&1 + else + : # ignore others + fi done + + sleep 0.15 } - -# Executes yavta pipeline controls and captures frames from a video node. -# Handles pre-capture and post-capture register writes, with detailed result code + +# Executes yavta capture with the same semantics as your manual call. +# Return codes: +# 0: PASS (>=1 frame captured) +# 1: FAIL (capture error) +# 2: SKIP (unsupported format) +# 3: SKIP (missing data) execute_capture_block() { - FRAMES="$1" FORMAT="$2" - - # Pre-stream controls - printf "%s\n" "$YAVTA_CTRL_PRE_LIST" | while IFS= read -r ctrl; do - [ -z "$ctrl" ] && continue - dev=$(echo "$ctrl" | awk '{print $1}') - reg=$(echo "$ctrl" | awk '{print $2}') - val=$(echo "$ctrl" | awk '{print $3}') - yavta --no-query -w "$reg $val" "$dev" >/dev/null 2>&1 - done + FRAMES="$1" + FORMAT="$2" + + [ -z "$YAVTA_DEV" ] && return 3 + [ -z "$FORMAT" ] && return 3 + [ -z "$YAVTA_W" ] && return 3 + [ -z "$YAVTA_H" ] && return 3 + + # Build args as separate, quoted tokens (fixes SC2086 safely) + SFLAG="-s" + SRES="${YAVTA_W}x${YAVTA_H}" + + CAPS="$(v4l2-ctl -D -d "$YAVTA_DEV" 2>/dev/null || true)" + BFLAG="-B" + if printf '%s\n' "$CAPS" | grep -qi 'MPlane'; then + BMODE="capture-mplane" + else + BMODE="capture" + fi - # Stream-on controls - printf "%s\n" "$YAVTA_CTRL_LIST" | while IFS= read -r ctrl; do - [ -z "$ctrl" ] && continue - dev=$(echo "$ctrl" | awk '{print $1}') - reg=$(echo "$ctrl" | awk '{print $2}') - val=$(echo "$ctrl" | awk '{print $3}') - yavta --no-query -w "$reg $val" "$dev" >/dev/null 2>&1 - done + sleep 0.12 - # Capture - if [ -n "$YAVTA_DEV" ] && [ -n "$FORMAT" ] && [ -n "$YAVTA_W" ] && [ -n "$YAVTA_H" ]; then - OUT=$(yavta -B capture-mplane -c -I -n "$FRAMES" -f "$FORMAT" -s "${YAVTA_W}x${YAVTA_H}" -F "$YAVTA_DEV" --capture="$FRAMES" --file="frame-#.bin" 2>&1) - RET=$? - if echo "$OUT" | grep -qi "Unsupported video format"; then - return 2 - elif [ $RET -eq 0 ] && echo "$OUT" | grep -q "Captured [1-9][0-9]* frames"; then - return 0 - else - return 1 - fi - else - return 3 + do_capture_once() { + # $1 = mode (capture|capture-mplane) + yavta "$BFLAG" "$1" -c -I -n "$FRAMES" -f "$FORMAT" "$SFLAG" "$SRES" \ + -F "$YAVTA_DEV" --capture="$FRAMES" --file='frame-#.bin' 2>&1 + return $? + } + + OUT="$(do_capture_once "$BMODE")"; RET=$? + if [ $RET -eq 0 ] && echo "$OUT" | grep -q "Captured [1-9][0-9]* frames"; then + return 0 fi + echo "$OUT" | grep -qi "Unsupported video format" && return 2 - # Post-stream controls - printf "%s\n" "$YAVTA_CTRL_POST_LIST" | while IFS= read -r ctrl; do - [ -z "$ctrl" ] && continue - dev=$(echo "$ctrl" | awk '{print $1}') - reg=$(echo "$ctrl" | awk '{print $2}') - val=$(echo "$ctrl" | awk '{print $3}') - yavta --no-query -w "$reg $val" "$dev" >/dev/null 2>&1 - done -} + sleep 0.10 + OUT="$(do_capture_once "$BMODE")"; RET=$? + if [ $RET -eq 0 ] && echo "$OUT" | grep -q "Captured [1-9][0-9]* frames"; then + return 0 + fi + echo "$OUT" | grep -qi "Unsupported video format" && return 2 -# ---- Wayland/Weston helpers ----------------------- -# Ensure a private XDG runtime directory exists and is usable (0700). -ensure_private_runtime_dir() { - d="$1" - [ -n "$d" ] || return 1 - [ -d "$d" ] || mkdir -p "$d" 2>/dev/null || return 1 - chmod 0700 "$d" 2>/dev/null || return 1 - : >"$d/.xdg-test" 2>/dev/null || return 1 - rm -f "$d/.xdg-test" 2>/dev/null - return 0 -} + # Plane flip + case "$BMODE" in + capture) FLIPMODE="capture-mplane" ;; + *) FLIPMODE="capture" ;; + esac + OUT="$(do_capture_once "$FLIPMODE")"; RET=$? + if [ $RET -eq 0 ] && echo "$OUT" | grep -q "Captured [1-9][0-9]* frames"; then + return 0 + fi + echo "$OUT" | grep -qi "Unsupported video format" && return 2 -# Return first Wayland socket under a base dir (prints path or fails). -find_wayland_socket_in() { - base="$1" - [ -d "$base" ] || return 1 - for s in "$base"/wayland-*; do - [ -S "$s" ] || continue - printf '%s\n' "$s" + # Try P<->non-P sibling of FORMAT + case "$FORMAT" in *P) ALT="${FORMAT%P}" ;; *) ALT="${FORMAT}P" ;; esac + FORMAT="$ALT" + OUT="$(do_capture_once "$BMODE")"; RET=$? + if [ $RET -eq 0 ] && echo "$OUT" | grep -q "Captured [1-9][0-9]* frames"; then return 0 - done - return 1 -} + fi + echo "$OUT" | grep -qi "Unsupported video format" && return 2 + + OUT="$(do_capture_once "$FLIPMODE")"; RET=$? + if [ $RET -eq 0 ] && echo "$OUT" | grep -q "Captured [1-9][0-9]* frames"; then + return 0 + fi + echo "$OUT" | grep -qi "Unsupported video format" && return 2 -# Best-effort discovery of a usable Wayland socket anywhere. -discover_wayland_socket_anywhere() { - uid="$(id -u 2>/dev/null || echo 0)" - bases="" - [ -n "$XDG_RUNTIME_DIR" ] && bases="$bases $XDG_RUNTIME_DIR" - bases="$bases /dev/socket/weston /run/user/$uid /tmp/wayland-$uid /dev/shm" - for b in $bases; do - ensure_private_runtime_dir "$b" >/dev/null 2>&1 || true - if s="$(find_wayland_socket_in "$b")"; then - printf '%s\n' "$s" - return 0 - fi - done return 1 } -# Adopt env from a Wayland socket path like /run/user/0/wayland-0 -# Sets XDG_RUNTIME_DIR and WAYLAND_DISPLAY. Returns 0 on success. -adopt_wayland_env_from_socket() { - sock="$1" - [ -n "$sock" ] || { log_warn "adopt_wayland_env_from_socket: no socket path given"; return 1; } - [ -S "$sock" ] || { log_warn "adopt_wayland_env_from_socket: not a socket: $sock"; return 1; } +print_planned_commands() { + media_node="$1" + pixfmt="$2" + + # Pads should use MBUS code (strip trailing 'P' if present) + padfmt="$(printf '%s' "$pixfmt" | sed 's/P$//')" - # Derive components without external dirname/basename - XDG_RUNTIME_DIR=${sock%/*} - WAYLAND_DISPLAY=${sock##*/} + log_info "[CI] Planned sequence:" + log_info " media-ctl -d $media_node --reset" + + # Pad formats: show MBUS (non-P) on -V lines + if [ -n "$MEDIA_CTL_V_LIST" ]; then + printf '%s\n' "$MEDIA_CTL_V_LIST" | while IFS= read -r vline; do + [ -z "$vline" ] && continue + vline_out="$(printf '%s' "$vline" | sed -E "s/fmt:[^/]+\/([0-9]+x[0-9]+)/fmt:${padfmt}\/\1/g")" + log_info " media-ctl -d $media_node -V '$vline_out'" + done + fi + + # Links unchanged + if [ -n "$MEDIA_CTL_L_LIST" ]; then + printf '%s\n' "$MEDIA_CTL_L_LIST" | while IFS= read -r lline; do + [ -z "$lline" ] && continue + log_info " media-ctl -d $media_node -l '$lline'" + done + fi + + # Any pre/post yavta register writes (unchanged) + if [ -n "$YAVTA_CTRL_PRE_LIST" ]; then + printf '%s\n' "$YAVTA_CTRL_PRE_LIST" | while IFS= read -r ctrl; do + [ -z "$ctrl" ] && continue + dev="$(printf '%s' "$ctrl" | awk '{print $1}')" + reg="$(printf '%s' "$ctrl" | awk '{print $2}')" + val="$(printf '%s' "$ctrl" | awk '{print $3}')" + [ -n "$dev" ] && [ -n "$reg" ] && [ -n "$val" ] && \ + log_info " yavta --no-query -w '$reg $val' $dev" + done + fi - export XDG_RUNTIME_DIR - export WAYLAND_DISPLAY + size_arg="" + if [ -n "$YAVTA_W" ] && [ -n "$YAVTA_H" ]; then + size_arg="-s ${YAVTA_W}x${YAVTA_H}" + fi + if [ -n "$YAVTA_DEV" ]; then + # Show pixel format (SRGGB10P) only on the video node + log_info " yavta -B capture-mplane -c -I -n $FRAMES -f $pixfmt $size_arg -F $YAVTA_DEV --capture=$FRAMES --file='frame-#.bin'" + fi - log_info "Selected Wayland socket: $sock" - log_info "Wayland env: XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR WAYLAND_DISPLAY=$WAYLAND_DISPLAY" - return 0 + if [ -n "$YAVTA_CTRL_POST_LIST" ]; then + printf '%s\n' "$YAVTA_CTRL_POST_LIST" | while IFS= read -r ctrl; do + [ -z "$ctrl" ] && continue + dev="$(printf '%s' "$ctrl" | awk '{print $1}')" + reg="$(printf '%s' "$ctrl" | awk '{print $2}')" + val="$(printf '%s' "$ctrl" | awk '{print $3}')" + [ -n "$dev" ] && [ -n "$reg" ] && [ -n "$val" ] && \ + log_info " yavta --no-query -w '$reg $val' $dev" + done + fi } -# Try to connect to Wayland. Returns 0 on OK. -wayland_can_connect() { - if command -v weston-info >/dev/null 2>&1; then - weston-info >/dev/null 2>&1 - return $? - fi - # fallback: quick client probe - ( env -i XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" WAYLAND_DISPLAY="$WAYLAND_DISPLAY" true ) >/dev/null 2>&1 - return $? +log_soc_info() { + m=""; s=""; pv="" + [ -r /sys/devices/soc0/machine ] && m="$(cat /sys/devices/soc0/machine 2>/dev/null)" + [ -r /sys/devices/soc0/soc_id ] && s="$(cat /sys/devices/soc0/soc_id 2>/dev/null)" + [ -r /sys/devices/soc0/platform_version ] && pv="$(cat /sys/devices/soc0/platform_version 2>/dev/null)" + [ -n "$m" ] && log_info "SoC.machine: $m" + [ -n "$s" ] && log_info "SoC.soc_id: $s" + [ -n "$pv" ] && log_info "SoC.platform_version: $pv" } -# Ensure a Weston socket exists; if not, stop+start Weston and adopt helper socket. -weston_pick_env_or_start() { - sock="$(discover_wayland_socket_anywhere 2>/dev/null || true)" - if [ -n "$sock" ]; then - adopt_wayland_env_from_socket "$sock" - log_info "Selected Wayland socket: $sock" - return 0 +############################################################################### +# Platform detection (SoC / Target / Machine / OS) — POSIX, no sourcing files +# Sets & exports: +# PLATFORM_KERNEL, PLATFORM_ARCH, PLATFORM_UNAME_S, PLATFORM_HOSTNAME +# PLATFORM_SOC_MACHINE, PLATFORM_SOC_ID, PLATFORM_SOC_FAMILY +# PLATFORM_DT_MODEL, PLATFORM_DT_COMPAT +# PLATFORM_OS_LIKE, PLATFORM_OS_NAME +# PLATFORM_TARGET, PLATFORM_MACHINE +############################################################################### +detect_platform() { + # --- Basic uname/host --- + PLATFORM_KERNEL="$(uname -r 2>/dev/null)" + PLATFORM_ARCH="$(uname -m 2>/dev/null)" + PLATFORM_UNAME_S="$(uname -s 2>/dev/null)" + PLATFORM_HOSTNAME="$(hostname 2>/dev/null)" + + # --- soc0 details --- + if [ -r /sys/devices/soc0/machine ]; then + PLATFORM_SOC_MACHINE="$(cat /sys/devices/soc0/machine 2>/dev/null)" + else + PLATFORM_SOC_MACHINE="" fi - - if weston_is_running; then - log_info "Stopping Weston..." - weston_stop - i=0; while weston_is_running && [ "$i" -lt 5 ]; do i=$((i+1)); sleep 1; done + + if [ -r /sys/devices/soc0/soc_id ]; then + PLATFORM_SOC_ID="$(cat /sys/devices/soc0/soc_id 2>/dev/null)" + else + PLATFORM_SOC_ID="" fi - - log_info "Starting Weston..." - weston_start - i=0; sock="" - while [ "$i" -lt 6 ]; do - sock="$(find_wayland_socket_in /dev/socket/weston 2>/dev/null || true)" - [ -n "$sock" ] && break - sleep 1; i=$((i+1)) - done - if [ -z "$sock" ]; then - log_fail "Could not find Wayland socket after starting Weston." - return 1 + + if [ -r /sys/devices/soc0/family ]; then + PLATFORM_SOC_FAMILY="$(cat /sys/devices/soc0/family 2>/dev/null)" + else + PLATFORM_SOC_FAMILY="" fi - adopt_wayland_env_from_socket "$sock" - log_info "Weston started; socket: $sock" + + # --- Device Tree model / compatible (strip NULs) --- + if [ -r /proc/device-tree/model ]; then + PLATFORM_DT_MODEL="$(tr -d '\000' /dev/null | head -n 1)" + else + PLATFORM_DT_MODEL="" + fi + + PLATFORM_DT_COMPAT="" + if [ -d /proc/device-tree ]; then + for f in /proc/device-tree/compatible /proc/device-tree/*/compatible; do + if [ -f "$f" ]; then + PLATFORM_DT_COMPAT="$(tr -d '\000' <"$f" 2>/dev/null | tr '\n' ' ')" + break + fi + done + fi + + # --- OS (parse, do not source /etc/os-release) --- + PLATFORM_OS_LIKE="" + PLATFORM_OS_NAME="" + if [ -r /etc/os-release ]; then + PLATFORM_OS_LIKE="$( + awk -F= '$1=="ID_LIKE"{gsub(/"/,"",$2); print $2}' /etc/os-release 2>/dev/null + )" + if [ -z "$PLATFORM_OS_LIKE" ]; then + PLATFORM_OS_LIKE="$( + awk -F= '$1=="ID"{gsub(/"/,"",$2); print $2}' /etc/os-release 2>/dev/null + )" + fi + PLATFORM_OS_NAME="$( + awk -F= '$1=="PRETTY_NAME"{gsub(/"/,"",$2); print $2}' /etc/os-release 2>/dev/null + )" + fi + + # --- Target guess (mutually-exclusive; generic names only) --- + lc_compat="$(printf '%s %s' "$PLATFORM_DT_MODEL" "$PLATFORM_DT_COMPAT" \ + | tr '[:upper:]' '[:lower:]')" + + case "$lc_compat" in + # Kodiak: qcs6490 / RB3 Gen2 + *qcs6490*|*kodiak*|*rb3gen2*|*rb3-gen2*|*rb3*gen2*) + PLATFORM_TARGET="Kodiak" + ;; + # LeMans family: qcs9100, qcs9075, SA8775P, IQ-9075-EVK (accept 'lemand' too) + *qcs9100*|*qcs9075*|*lemans*|*lemand*|*sa8775p*|*iq-9075-evk*) + PLATFORM_TARGET="LeMans" + ;; + # Monaco: qcs8300 + *qcs8300*|*monaco*) + PLATFORM_TARGET="Monaco" + ;; + # Agatti: QRB2210 RB1 Core Kit + *qrb2210*|*agatti*|*rb1-core-kit*) + PLATFORM_TARGET="Agatti" + ;; + # Talos: QCS615 ADP Air + *qcs615*|*talos*|*adp-air*) + PLATFORM_TARGET="Talos" + ;; + *) + PLATFORM_TARGET="unknown" + ;; + esac + + # --- Human-friendly machine name --- + if [ -n "$PLATFORM_DT_MODEL" ]; then + PLATFORM_MACHINE="$PLATFORM_DT_MODEL" + else + if [ -n "$PLATFORM_SOC_MACHINE" ]; then + PLATFORM_MACHINE="$PLATFORM_SOC_MACHINE" + else + PLATFORM_MACHINE="$PLATFORM_HOSTNAME" + fi + fi + + # Export for callers (and to silence SC2034 in this file) + export \ + PLATFORM_KERNEL PLATFORM_ARCH PLATFORM_UNAME_S PLATFORM_HOSTNAME \ + PLATFORM_SOC_MACHINE PLATFORM_SOC_ID PLATFORM_SOC_FAMILY \ + PLATFORM_DT_MODEL PLATFORM_DT_COMPAT PLATFORM_OS_LIKE PLATFORM_OS_NAME \ + PLATFORM_TARGET PLATFORM_MACHINE + return 0 } -# Find candidate Wayland sockets in common locations. -# Prints absolute socket paths, one per line, most-preferred first. -find_wayland_sockets() { - uid="$(id -u 2>/dev/null || echo 0)" +# ---------- minimal root / FS helpers (Yocto-safe, no underscores) ---------- +isroot() { uid="$(id -u 2>/dev/null || echo 1)"; [ "$uid" -eq 0 ]; } - # Start with $XDG_RUNTIME_DIR if set. - if [ -n "$XDG_RUNTIME_DIR" ] && [ -d "$XDG_RUNTIME_DIR" ]; then - if [ -e "$XDG_RUNTIME_DIR/wayland-1" ]; then - printf '%s\n' "$XDG_RUNTIME_DIR/wayland-1" - fi - if [ -e "$XDG_RUNTIME_DIR/wayland-0" ]; then - printf '%s\n' "$XDG_RUNTIME_DIR/wayland-0" - fi - fi +iswritabledir() { d="$1"; [ -d "$d" ] && [ -w "$d" ]; } - # Qualcomm/Yocto common path. - if [ -e /dev/socket/weston/wayland-1 ]; then - printf '%s\n' /dev/socket/weston/wayland-1 - fi - if [ -e /dev/socket/weston/wayland-0 ]; then - printf '%s\n' /dev/socket/weston/wayland-0 - fi +mountpointfor() { + p="$1" + awk -v p="$p" ' + BEGIN{best="/"; bestlen=1} + { + mp=$2 + if (index(p, mp)==1 && length(mp)>bestlen) { best=mp; bestlen=length(mp) } + } + END{print best} + ' /proc/mounts 2>/dev/null +} - # XDG spec default per-user location. - if [ -e "/run/user/$uid/wayland-1" ]; then - printf '%s\n' "/run/user/$uid/wayland-1" - fi - if [ -e "/run/user/$uid/wayland-0" ]; then - printf '%s\n' "/run/user/$uid/wayland-0" +tryremountrw() { + path="$1" + mp="$(mountpointfor "$path")" + [ -n "$mp" ] || mp="/" + if mount -o remount,rw "$mp" 2>/dev/null; then + printf '%s\n' "$mp" + return 0 fi + return 1 } -# Ensure XDG_RUNTIME_DIR has owner=current-user and mode 0700. -# Returns 0 if OK (or fixed), non-zero if still not compliant. -ensure_wayland_runtime_dir_perms() { - dir="$1" - [ -n "$dir" ] && [ -d "$dir" ] || return 1 +# ---------------- DSP autolink (generic, sudo-free, no underscores) ---------------- +# Env: +# FASTRPC_DSP_AUTOLINK=yes|no (default: yes) +# FASTRPC_DSP_SRC=/path/to/dsp (force a source dir) +# FASTRPC_AUTOREMOUNT=yes|no (default: no) allow remount rw if needed +# FASTRPC_AUTOREMOUNT_RO=yes|no (default: yes) remount ro after linking +ensure_usr_lib_dsp_symlinks() { + [ "${FASTRPC_DSP_AUTOLINK:-yes}" = "yes" ] || { log_info "DSP autolink disabled"; return 0; } - cur_uid="$(id -u 2>/dev/null || echo 0)" - cur_gid="$(id -g 2>/dev/null || echo 0)" + dsptgt="/usr/lib/dsp" - # Best-effort fixups first (don’t error if chown/chmod fail) - chown "$cur_uid:$cur_gid" "$dir" 2>/dev/null || true - chmod 0700 "$dir" 2>/dev/null || true + # If already populated, skip + if [ -d "$dsptgt" ]; then + if find "$dsptgt" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null | grep -q .; then + log_info "$dsptgt already populated, skipping DSP autolink" + return 0 + fi + fi - # Verify using stat (GNU first, then BSD). If stat is unavailable, - # we can’t verify—assume OK to avoid SC2012 (ls) usage. - if command -v stat >/dev/null 2>&1; then - # Mode: GNU: %a ; BSD: %Lp - mode="$(stat -c '%a' "$dir" 2>/dev/null || stat -f '%Lp' "$dir" 2>/dev/null || echo '')" - # Owner uid: GNU: %u ; BSD: %u - uid="$(stat -c '%u' "$dir" 2>/dev/null || stat -f '%u' "$dir" 2>/dev/null || echo '')" + # Choose source: explicit env wins, else discover + if [ -n "${FASTRPC_DSP_SRC:-}" ] && [ -d "$FASTRPC_DSP_SRC" ]; then + dspsrc="$FASTRPC_DSP_SRC" + else + # Best-effort platform hints (detect_platform may exist in functestlib) + if command -v detect_platform >/dev/null 2>&1; then detect_platform >/dev/null 2>&1 || true; fi + hintstr="$(printf '%s %s %s %s' \ + "${PLATFORM_SOC_ID:-}" "${PLATFORM_DT_MODEL:-}" "${PLATFORM_DT_COMPAT:-}" "${PLATFORM_TARGET:-}" \ + | tr '[:upper:]' '[:lower:]')" + + candidateslist="$(find /usr/share/qcom -maxdepth 6 -type d -name dsp 2>/dev/null | sort)" + best=""; bestscore=-1; bestcount=-1 + IFS=' +' + for d in $candidateslist; do + [ -d "$d" ] || continue + if ! find "$d" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null | grep -q .; then + continue + fi + score=0 + pathlc="$(printf '%s' "$d" | tr '[:upper:]' '[:lower:]')" + for tok in $hintstr; do + [ -n "$tok" ] || continue + case "$pathlc" in *"$tok"*) score=$((score+1));; esac + done + cnt="$(find "$d" -mindepth 1 -maxdepth 1 -printf '.' 2>/dev/null | wc -c | tr -d ' ')" + if [ "$score" -gt "$bestscore" ] || { [ "$score" -eq "$bestscore" ] && [ "$cnt" -gt "$bestcount" ]; }; then + best="$d"; bestscore="$score"; bestcount="$cnt" + fi + done + unset IFS + dspsrc="$best" + fi - [ "$mode" = "700" ] && [ "$uid" = "$cur_uid" ] && return 0 - return 1 + if [ -z "$dspsrc" ]; then + log_warn "No DSP skeleton source found under /usr/share/qcom, skipping autolink." + return 0 fi - # No stat available: directory exists and we attempted to fix perms/owner. - # Treat as success so clients can try; avoids SC2012 warnings. - return 0 -} - -# Quick Wayland handshake check. -# Prefers `wayland-info` with a short timeout; otherwise validates socket presence. -# Also enforces/fixes XDG_RUNTIME_DIR permissions so clients won’t reject it. -wayland_connection_ok() { - if [ -n "$XDG_RUNTIME_DIR" ]; then - ensure_wayland_runtime_dir_perms "$XDG_RUNTIME_DIR" || return 1 + # Must be root on Yocto (no sudo). If not root, skip safely. + if ! isroot; then + log_warn "Not root; cannot write to $dsptgt on Yocto (no sudo). Skipping DSP autolink." + return 0 fi - if command -v wayland-info >/dev/null 2>&1; then - if command -v timeout >/dev/null 2>&1; then - timeout 3s wayland-info >/dev/null 2>&1 - return $? + # Ensure target dir exists; handle read-only rootfs if requested + remounted="" + mountpt="" + if ! mkdir -p "$dsptgt" 2>/dev/null; then + if [ "${FASTRPC_AUTOREMOUNT:-no}" = "yes" ]; then + mountpt="$(tryremountrw "$dsptgt")" || { + log_warn "Rootfs read-only and remount failed, skipping DSP autolink." + return 0 + } + remounted="yes" + if ! mkdir -p "$dsptgt" 2>/dev/null; then + log_warn "mkdir -p $dsptgt still failed after remount, skipping." + if [ -n "$mountpt" ] && [ "${FASTRPC_AUTOREMOUNT_RO:-yes}" = "yes" ]; then + mount -o remount,ro "$mountpt" 2>/dev/null || true + fi + return 0 + fi + else + log_warn "Rootfs likely read-only. Set FASTRPC_AUTOREMOUNT=yes to remount rw automatically." + return 0 fi - wayland-info >/dev/null 2>&1 - return $? fi - # Fallback: env variables and socket must exist. - if [ -n "$XDG_RUNTIME_DIR" ] && [ -n "$WAYLAND_DISPLAY" ] && [ -e "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]; then + # If something appeared meanwhile, stop (idempotent) + if find "$dsptgt" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null | grep -q .; then + log_info "$dsptgt now contains entries, not linking from $dspsrc" + if [ -n "$remounted" ] && [ -n "$mountpt" ] && [ "${FASTRPC_AUTOREMOUNT_RO:-yes}" = "yes" ]; then + mount -o remount,ro "$mountpt" 2>/dev/null || true + fi return 0 fi - return 1 -} - -# Print concise metadata for a path (portable). -# Prefers stat(1) (GNU or BSD); falls back to ls(1) only if needed. -# Usage: print_path_meta "/some/path" -print_path_meta() { - p=$1 - if [ -z "$p" ]; then - return 1 + + # Link both files and directories; don't clobber existing names + log_info "Linking DSP artifacts from: $dspsrc → $dsptgt" + linked=0 + for f in "$dspsrc"/*; do + [ -e "$f" ] || continue + base="$(basename "$f")" + if [ ! -e "$dsptgt/$base" ]; then + if ln -s "$f" "$dsptgt/$base" 2>/dev/null; then + linked=$((linked+1)) + else + log_warn "ln -s failed: $f" + fi + fi + done + + # Final visibility + sanity + if find "$dsptgt" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null | grep -q .; then + log_info "DSP autolink complete ($linked link(s))" + find "$dsptgt" \ + -mindepth 1 \ + -maxdepth 1 \ + -printf '%M %u %g %6s %TY-%Tm-%Td %TH:%TM %p\n' 2>/dev/null \ + | sed 's/^/[INFO] /' \ + || true + else + log_warn "DSP autolink finished but $dsptgt is still empty, source may contain only nested content or FS is RO." fi - # GNU stat - if stat -c '%A %U %G %a %n' "$p" >/dev/null 2>&1; then - stat -c '%A %U %G %a %n' "$p" - return 0 + + # Optionally restore read-only + if [ -n "$remounted" ] && [ -n "$mountpt" ] && [ "${FASTRPC_AUTOREMOUNT_RO:-yes}" = "yes" ]; then + mount -o remount,ro "$mountpt" 2>/dev/null || true fi - # BSD/Mac stat - if stat -f '%Sp %Su %Sg %OLp %N' "$p" >/dev/null 2>&1; then - stat -f '%Sp %Su %Sg %OLp %N' "$p" +} + +# --- Ensure rootfs has minimum size (defaults to 2GiB) ----------------------- +# Usage: ensure_rootfs_min_size [min_gib] +# - Checks / size (df -P /) in KiB. If < min, runs resize2fs on the rootfs device. +# - Logs to $LOG_DIR/resize2fs.log if LOG_DIR is set, else /tmp/resize2fs.log. +ensure_rootfs_min_size() { + min_gib="${1:-2}" + min_kb=$((min_gib*1024*1024)) + + total_kb="$(df -P / 2>/dev/null | awk 'NR==2{print $2}')" + [ -n "$total_kb" ] || { log_warn "df check failed; skipping resize."; return 0; } + + if [ "$total_kb" -ge "$min_kb" ] 2>/dev/null; then + log_info "Rootfs size OK (>=${min_gib}GiB)." + return 0 + fi + + # Pick root device: prefer by-partlabel/rootfs, else actual source of / + root_dev="/dev/disk/by-partlabel/rootfs" + if [ ! -e "$root_dev" ]; then + if command -v findmnt >/dev/null 2>&1; then + root_dev="$(findmnt -n -o SOURCE / 2>/dev/null | head -n1)" + else + root_dev="$(awk '$2=="/"{print $1; exit}' /proc/mounts 2>/dev/null)" + fi + fi + + # Detect filesystem type robustly + fstype="" + if command -v blkid >/dev/null 2>&1; then + fstype="$(blkid -o value -s TYPE "$root_dev" 2>/dev/null | head -n1)" + case "$fstype" in + *TYPE=*) + fstype="$(printf '%s' "$fstype" | sed -n 's/.*TYPE="\([^"]*\)".*/\1/p')" + ;; + esac + if [ -z "$fstype" ] && command -v lsblk >/dev/null 2>&1; then + fstype="$(lsblk -no FSTYPE "$root_dev" 2>/dev/null | head -n1)" + fi + if [ -z "$fstype" ]; then + fstype="$(blkid "$root_dev" 2>/dev/null | sed -n 's/.*TYPE="\([^"]*\)".*/\1/p')" + fi + case "$fstype" in + ext2|ext3|ext4) : ;; + *) + log_warn "Rootfs type '${fstype:-unknown}' not ext*, skipping resize." + return 0 + ;; + esac + fi + + log_dir="${LOG_DIR:-/tmp}" + mkdir -p "$log_dir" 2>/dev/null || true + + if command -v resize2fs >/dev/null 2>&1; then + mib="$(printf '%s\n' "$total_kb" | awk '{printf "%d",$1/1024}')" + log_info "Rootfs <${min_gib}GiB (${mib} MiB). Resizing filesystem on $root_dev ..." + if resize2fs "$root_dev" >>"$log_dir/resize2fs.log" 2>&1; then + log_pass "resize2fs completed on $root_dev (see $log_dir/resize2fs.log)." + else + log_warn "resize2fs failed on $root_dev (see $log_dir/resize2fs.log)." + fi + else + log_warn "resize2fs not available; skipping resize." + fi return 0 - fi - # shellcheck disable=SC2012 - ls -ld -- "$p" 2>/dev/null } +# ---- Connectivity probe (0 OK, 2 IP/no-internet, 1 no IP) ---- +# Env overrides: +# NET_PROBE_ROUTE_IP=1.1.1.1 +# NET_PING_HOST=8.8.8.8 +check_network_status_rc() { + net_probe_route_ip="${NET_PROBE_ROUTE_IP:-1.1.1.1}" + net_ping_host="${NET_PING_HOST:-8.8.8.8}" -soc_id() { - if [ -r /sys/devices/soc0/soc_id ]; then - cat /sys/devices/soc0/soc_id 2>/dev/null - elif [ -r /proc/device-tree/compatible ]; then - tr -d '\0' /dev/null | head -n 1 + if command -v ip >/dev/null 2>&1; then + net_ip_addr="$(ip -4 route get "$net_probe_route_ip" 2>/dev/null \ + | awk 'NR==1{for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}')" + if [ -z "$net_ip_addr" ]; then + net_ip_addr="$(ip -o -4 addr show scope global up 2>/dev/null \ + | awk 'NR==1{split($4,a,"/"); print a[1]}')" + fi else - uname -r + net_ip_addr="" + fi + + if [ -n "$net_ip_addr" ]; then + if command -v ping >/dev/null 2>&1; then + if ping -c 1 -W 2 "$net_ping_host" >/dev/null 2>&1 \ + || ping -c 1 -w 2 "$net_ping_host" >/dev/null 2>&1; then + unset net_probe_route_ip net_ping_host net_ip_addr + return 0 + fi + unset net_probe_route_ip net_ping_host net_ip_addr + return 2 + else + unset net_probe_route_ip net_ping_host net_ip_addr + return 2 + fi fi + + unset net_probe_route_ip net_ping_host net_ip_addr + return 1 } -# --- BusyBox-safe helper: convert "15s" -> 15, pass-through if already integer --- -timeout_to_seconds() { - case "$1" in *s) echo "${1%s}";; *) echo "$1";; esac +# ---- Interface snapshot (INFO log only) ---- +net_log_iface_snapshot() { + net_ifc="$1" + [ -n "$net_ifc" ] || { unset net_ifc; return 0; } + + net_admin="DOWN" + net_oper="unknown" + net_carrier="0" + net_ip="none" + + if command -v ip >/dev/null 2>&1 && ip -o link show "$net_ifc" >/dev/null 2>&1; then + if ip -o link show "$net_ifc" | awk -F'[<>]' '{print $2}' | grep -qw UP; then + net_admin="UP" + fi + fi + [ -r "/sys/class/net/$net_ifc/operstate" ] && net_oper="$(cat "/sys/class/net/$net_ifc/operstate" 2>/dev/null)" + [ -r "/sys/class/net/$net_ifc/carrier" ] && net_carrier="$(cat "/sys/class/net/$net_ifc/carrier" 2>/dev/null)" + + if command -v get_ip_address >/dev/null 2>&1; then + net_ip="$(get_ip_address "$net_ifc" 2>/dev/null)" + [ -n "$net_ip" ] || net_ip="none" + fi + + log_info "[NET] ${net_ifc}: admin=${net_admin} oper=${net_oper} carrier=${net_carrier} ip=${net_ip}" + + unset net_ifc net_admin net_oper net_carrier net_ip } -# --- POSIX timeout without GNU coreutils --- -sh_timeout() { - dur="$1"; shift - [ "$1" = "--" ] && shift - case "$dur" in *s) sec=${dur%s} ;; *) sec=$dur ;; esac - [ -z "$sec" ] && sec=0 +# ---- Bring the system online if possible (0 OK, 2 IP/no-internet, 1 no IP) ---- +ensure_network_online() { + check_network_status_rc; net_rc=$? + if [ "$net_rc" -eq 0 ]; then + ensure_reasonable_clock || log_warn "Proceeding in limited-network mode." + unset net_rc + return 0 + fi - "$@" & - pid=$! + if command -v systemctl >/dev/null 2>&1 && command -v check_systemd_services >/dev/null 2>&1; then + check_systemd_services NetworkManager systemd-networkd connman || true + fi - t=0 - while kill -0 "$pid" 2>/dev/null; do - if [ "$t" -ge "$sec" ] 2>/dev/null; then - kill "$pid" 2>/dev/null || true - sleep 1 - kill -9 "$pid" 2>/dev/null || true - wait "$pid" 2>/dev/null - return 124 + net_had_any_ip=0 + + # -------- Ethernet pass -------- + net_ifaces="" + if command -v get_ethernet_interfaces >/dev/null 2>&1; then + net_ifaces="$(get_ethernet_interfaces 2>/dev/null)" + fi + + for net_ifc in $net_ifaces; do + net_log_iface_snapshot "$net_ifc" + + if command -v is_link_up >/dev/null 2>&1; then + if ! is_link_up "$net_ifc"; then + log_info "[NET] ${net_ifc}: link=down → skipping DHCP" + continue + fi fi - sleep 1 - t=$((t+1)) + + log_info "[NET] ${net_ifc}: bringing up and requesting DHCP..." + if command -v bringup_interface >/dev/null 2>&1; then + bringup_interface "$net_ifc" 2 2 || true + fi + + if command -v run_dhcp_client >/dev/null 2>&1; then + run_dhcp_client "$net_ifc" 10 >/dev/null 2>&1 || true + elif command -v try_dhcp_client_safe >/dev/null 2>&1; then + try_dhcp_client_safe "$net_ifc" 8 || true + fi + + net_log_iface_snapshot "$net_ifc" + + check_network_status_rc; net_rc=$? + case "$net_rc" in + 0) + log_pass "[NET] ${net_ifc}: internet reachable" + ensure_reasonable_clock || log_warn "Proceeding in limited-network mode." + unset net_ifaces net_ifc net_rc net_had_any_ip + return 0 + ;; + 2) + log_warn "[NET] ${net_ifc}: IP assigned but internet not reachable" + net_had_any_ip=1 + ;; + 1) + log_info "[NET] ${net_ifc}: still no IP after DHCP attempt" + ;; + esac done - wait "$pid"; return $? -} -# Map test duration keywords to seconds -duration_to_secs() { - case "$1" in - short) echo 5 ;; - medium) echo 15 ;; - long) echo 30 ;; - *) echo 5 ;; - esac -} \ No newline at end of file + # -------- Wi-Fi pass (with bounded retry) -------- + net_wifi="" + if command -v get_wifi_interface >/dev/null 2>&1; then + net_wifi="$(get_wifi_interface 2>/dev/null || echo "")" + fi + if [ -n "$net_wifi" ]; then + net_log_iface_snapshot "$net_wifi" + log_info "[NET] ${net_wifi}: bringing up Wi-Fi..." + + if command -v bringup_interface >/dev/null 2>&1; then + bringup_interface "$net_wifi" 2 2 || true + fi + + net_creds="" + if command -v get_wifi_credentials >/dev/null 2>&1; then + net_creds="$(get_wifi_credentials "" "" 2>/dev/null || true)" + fi + + # ---- New: retry knobs (env-overridable) ---- + wifi_max_attempts="${NET_WIFI_RETRIES:-2}" + wifi_retry_delay="${NET_WIFI_RETRY_DELAY:-5}" + if [ -z "$wifi_max_attempts" ] || [ "$wifi_max_attempts" -lt 1 ] 2>/dev/null; then + wifi_max_attempts=1 + fi + if [ -z "$wifi_retry_delay" ] || [ "$wifi_retry_delay" -lt 0 ] 2>/dev/null; then + wifi_retry_delay=0 + fi + + wifi_attempt=1 + while [ "$wifi_attempt" -le "$wifi_max_attempts" ]; do + if [ "$wifi_max_attempts" -gt 1 ]; then + log_info "[NET] ${net_wifi}: Wi-Fi attempt ${wifi_attempt}/${wifi_max_attempts}" + fi + + if [ -n "$net_creds" ]; then + net_ssid="$(printf '%s\n' "$net_creds" | awk '{print $1}')" + net_pass="$(printf '%s\n' "$net_creds" | awk '{print $2}')" + log_info "[NET] ${net_wifi}: trying nmcli for SSID='${net_ssid}'" + if command -v wifi_connect_nmcli >/dev/null 2>&1; then + wifi_connect_nmcli "$net_wifi" "$net_ssid" "$net_pass" || true + fi + + # If nmcli brought us up, do NOT fall back to wpa_supplicant + check_network_status_rc; net_rc=$? + if [ "$net_rc" -ne 0 ]; then + log_info "[NET] ${net_wifi}: falling back to wpa_supplicant + DHCP" + if command -v wifi_connect_wpa_supplicant >/dev/null 2>&1; then + wifi_connect_wpa_supplicant "$net_wifi" "$net_ssid" "$net_pass" || true + fi + if command -v run_dhcp_client >/dev/null 2>&1; then + run_dhcp_client "$net_wifi" 10 >/dev/null 2>&1 || true + fi + fi + else + log_info "[NET] ${net_wifi}: no credentials provided → DHCP only" + if command -v run_dhcp_client >/dev/null 2>&1; then + run_dhcp_client "$net_wifi" 10 >/dev/null 2>&1 || true + fi + fi + + net_log_iface_snapshot "$net_wifi" + check_network_status_rc; net_rc=$? + case "$net_rc" in + 0) + log_pass "[NET] ${net_wifi}: internet reachable" + ensure_reasonable_clock || log_warn "Proceeding in limited-network mode." + unset net_wifi net_ifaces net_ifc net_rc net_had_any_ip net_creds net_ssid net_pass wifi_attempt wifi_max_attempts wifi_retry_delay + return 0 + ;; + 2) + log_warn "[NET] ${net_wifi}: IP assigned but internet not reachable" + net_had_any_ip=1 + ;; + 1) + log_info "[NET] ${net_wifi}: still no IP after connect/DHCP attempt" + ;; + esac + + # If not last attempt, cooldown + cleanup before retry + if [ "$wifi_attempt" -lt "$wifi_max_attempts" ]; then + if command -v wifi_cleanup >/dev/null 2>&1; then + wifi_cleanup "$net_wifi" || true + fi + if command -v bringup_interface >/dev/null 2>&1; then + bringup_interface "$net_wifi" 2 2 || true + fi + if [ "$wifi_retry_delay" -gt 0 ] 2>/dev/null; then + log_info "[NET] ${net_wifi}: retrying in ${wifi_retry_delay}s…" + sleep "$wifi_retry_delay" + fi + fi + + wifi_attempt=$((wifi_attempt + 1)) + done + fi + + # -------- DHCP/route/DNS fixup (udhcpc script) -------- + net_script_path="" + if command -v ensure_udhcpc_script >/dev/null 2>&1; then + net_script_path="$(ensure_udhcpc_script 2>/dev/null || echo "")" + fi + if [ -n "$net_script_path" ]; then + log_info "[NET] udhcpc default.script present → refreshing leases" + for net_ifc in $net_ifaces $net_wifi; do + [ -n "$net_ifc" ] || continue + if command -v run_dhcp_client >/dev/null 2>&1; then + run_dhcp_client "$net_ifc" 8 >/dev/null 2>&1 || true + fi + done + check_network_status_rc; net_rc=$? + case "$net_rc" in + 0) + log_pass "[NET] connectivity restored after udhcpc fixup" + ensure_reasonable_clock || log_warn "Proceeding in limited-network mode." + unset net_script_path net_ifaces net_wifi net_ifc net_rc net_had_any_ip + return 0 + ;; + 2) + log_warn "[NET] IP present but still no internet after udhcpc fixup" + net_had_any_ip=1 + ;; + esac + fi + + if [ "$net_had_any_ip" -eq 1 ] 2>/dev/null; then + unset net_script_path net_ifaces net_wifi net_ifc net_rc net_had_any_ip + return 2 + fi + unset net_script_path net_ifaces net_wifi net_ifc net_rc net_had_any_ip + return 1 +} diff --git a/Runner/utils/lib_video.sh b/Runner/utils/lib_video.sh new file mode 100755 index 00000000..31086b78 --- /dev/null +++ b/Runner/utils/lib_video.sh @@ -0,0 +1,1952 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause-Clear +# Common, POSIX-compliant helpers for Qualcomm video stack selection and V4L2 testing. +# Requires functestlib.sh: log_info/log_warn/log_pass/log_fail/log_skip, +# check_dependencies, extract_tar_from_url, (optional) run_with_timeout, ensure_network_online. + +# ----------------------------------------------------------------------------- +# Public env knobs (may be exported by caller or set via CLI in run.sh) +# ----------------------------------------------------------------------------- +# VIDEO_STACK auto|upstream|downstream|base|overlay|up|down (default: auto) +# VIDEO_PLATFORM lemans|monaco|kodiak|"" (auto-detect) +# TAR_URL bundle for input clips (used by video_ensure_clips_present_or_fetch) +# VIDEO_APP path to iris_v4l2_test (default /usr/bin/iris_v4l2_test) + +# ----------------------------------------------------------------------------- +# Constants / tool paths +# ----------------------------------------------------------------------------- +IRIS_UP_MOD="qcom_iris" +IRIS_VPU_MOD="iris_vpu" +VENUS_CORE_MOD="venus_core" +VENUS_DEC_MOD="venus_dec" +VENUS_ENC_MOD="venus_enc" + +# We purposely avoid persistent blacklist here (no /etc changes). +# Session-only blocks live under /run/modprobe.d +RUNTIME_BLOCK_DIR="/run/modprobe.d" + +# Optional custom module sources (set by run.sh only when user opts in) +# NOTE: Leaving these unset keeps the exact existing behavior. +KO_DIRS="${KO_DIRS:-}" # colon-separated dirs containing .ko files +KO_TREE="${KO_TREE:-}" # alt root containing lib/modules/$(uname -r) +KO_PREFER_CUSTOM="${KO_PREFER_CUSTOM:-0}" # 1 = prefer KO_DIRS before system tree + +# Firmware path for Kodiak downstream blob +FW_PATH_KODIAK="/lib/firmware/qcom/vpu/vpu20_p1_gen2.mbn" +: "${FW_BACKUP_DIR:=/opt}" + +MODPROBE="$(command -v modprobe 2>/dev/null || printf '%s' /sbin/modprobe)" +LSMOD="$(command -v lsmod 2>/dev/null || printf '%s' /sbin/lsmod)" + +# Default app path (caller may override via env) +VIDEO_APP="${VIDEO_APP:-/usr/bin/iris_v4l2_test}" + +# ----------------------------------------------------------------------------- +# NEW: settle / retry tunables (env-only; no CLI) +# ----------------------------------------------------------------------------- +: "${MOD_RETRY_COUNT:=3}" +: "${MOD_RETRY_SLEEP:=0.4}" +: "${MOD_SETTLE_SLEEP:=0.5}" + +# ----------------------------------------------------------------------------- +# Tiny utils +# ----------------------------------------------------------------------------- +video_exist_cmd() { + command -v "$1" >/dev/null 2>&1 +} + +video_usleep() { + # Safe sleep wrapper (accepts integers or decimals) + dur="$1" + if [ -z "$dur" ]; then + dur=0 + fi + sleep "$dur" 2>/dev/null || true +} + +video_warn_if_not_root() { + uid="$(id -u 2>/dev/null || printf '%s' 1)" + if [ "$uid" -ne 0 ] 2>/dev/null; then + log_warn "Not running as root; module/blacklist operations may fail." + fi +} + +video_has_module_loaded() { + "$LSMOD" 2>/dev/null | awk '{print $1}' | grep -q "^$1$" +} + +video_devices_present() { + set -- /dev/video* 2>/dev/null + [ -e "$1" ] +} + +video_step() { + id="$1" + msg="$2" + if [ -n "$id" ]; then + log_info "[$id] STEP: $msg" + else + log_info "STEP: $msg" + fi +} + +# ----------------------------------------------------------------------------- +# Optional: log firmware hint after reload +# ----------------------------------------------------------------------------- +video_log_fw_hint() { + if video_exist_cmd dmesg; then + out="$(dmesg 2>/dev/null | tail -n 200 | grep -Ei 'firmware|iris_vpu|venus' | tail -n 10)" + if [ -n "$out" ]; then + printf '%s\n' "$out" | while IFS= read -r ln; do + log_info "dmesg: $ln" + done + fi + fi +} + +# Install a module file into /lib/modules/$(uname -r)/updates and run depmod. +# Usage: video_ensure_moddir_install +video_ensure_moddir_install() { + mp="$1" # source path to .ko + mname="$2" # logical module name for logging + kr="$(uname -r 2>/dev/null)" + [ -z "$kr" ] && kr="$(find /lib/modules -mindepth 1 -maxdepth 1 -type d -printf '%f\n' 2>/dev/null | head -n 1)" + + if [ -z "$mp" ] || [ ! -f "$mp" ]; then + log_warn "install-ko: invalid module path: $mp" + return 1 + fi + + updates="/lib/modules/$kr/updates" + if [ ! -d "$updates" ]; then + if ! mkdir -p "$updates" 2>/dev/null; then + log_warn "install-ko: cannot create $updates (read-only FS?)" + return 1 + fi + fi + + base="$(basename "$mp")" + dst="$updates/$base" + + do_copy=1 + if [ -f "$dst" ]; then + if command -v md5sum >/dev/null 2>&1; then + src_md5="$(md5sum "$mp" 2>/dev/null | awk '{print $1}')" + dst_md5="$(md5sum "$dst" 2>/dev/null | awk '{print $1}')" + [ -n "$src_md5" ] && [ "$src_md5" = "$dst_md5" ] && do_copy=0 + else + if cmp -s "$mp" "$dst" 2>/dev/null; then + do_copy=0 + fi + fi + fi + + if [ "$do_copy" -eq 1 ]; then + if ! cp -f "$mp" "$dst" 2>/dev/null; then + log_warn "install-ko: failed to copy $mp -> $dst" + return 1 + fi + chmod 0644 "$dst" 2>/dev/null || true + sync 2>/dev/null || true + log_info "install-ko: copied $(basename "$mp") to $dst" + else + log_info "install-ko: up-to-date at $dst" + fi + + if command -v depmod >/dev/null 2>&1; then + if depmod -a "$kr" >/dev/null 2>&1; then + log_info "install-ko: depmod -a $kr done" + else + log_warn "install-ko: depmod -a $kr failed (continuing)" + fi + else + log_warn "install-ko: depmod not found; modprobe may fail to resolve deps" + fi + + [ -n "$mname" ] && video_log_resolve "$mname" updates-tree "$dst" + printf '%s\n' "$dst" + return 0 +} + +# Takes a logical module name (e.g. iris_vpu), resolves/copies into updates/, depmods, then loads deps+module. +video_insmod_with_deps() { + m="$1" + [ -z "$m" ] && { log_warn "insmod-with-deps: empty module name"; return 1; } + + [ -z "$MODPROBE" ] && MODPROBE="modprobe" + + if video_has_module_loaded "$m"; then + log_info "module already loaded (skip): $m" + return 0 + fi + + mp="$(video_find_module_file "$m")" || mp="" + if [ -z "$mp" ] || [ ! -f "$mp" ]; then + log_warn "insmod fallback: could not locate module file for $m" + return 1 + fi + log_info "resolve: $m -> $mp" + + case "$mp" in + /lib/modules/*) staged="$mp" ;; + *) + staged="$(video_ensure_moddir_install "$mp" "$m")" || staged="" + if [ -z "$staged" ]; then + log_warn "insmod-with-deps: staging into updates/ failed for $m" + return 1 + fi + ;; + esac + + if "$MODPROBE" -q "$m" 2>/dev/null; then + video_log_load_success "$m" modprobe "$staged" + return 0 + fi + log_warn "modprobe failed: $m (attempting direct insmod)" + + deps="" + if video_exist_cmd modinfo; then + deps="$(modinfo -F depends "$staged" 2>/dev/null | tr ',' ' ' | tr -s ' ')" + fi + + for d in $deps; do + [ -z "$d" ] && continue + if video_has_module_loaded "$d"; then + continue + fi + if ! "$MODPROBE" -q "$d" 2>/dev/null; then + dpath="$(video_find_module_file "$d")" || dpath="" + if [ -z "$dpath" ] || [ ! -f "$dpath" ]; then + kr="$(uname -r 2>/dev/null)" + [ -z "$kr" ] && kr="$(find /lib/modules -mindepth 1 -maxdepth 1 -type d -printf '%f\n' 2>/dev/null | head -n 1)" + cand="/lib/modules/$kr/updates/${d}.ko" + [ -f "$cand" ] && dpath="$cand" + fi + if [ -n "$dpath" ] && [ -f "$dpath" ]; then + if insmod "$dpath" 2>/dev/null; then + video_log_load_success "$d" dep-insmod "$dpath" + else + log_warn "dep insmod failed: $d ($dpath)" + fi + else + log_warn "dep resolve failed: $d" + fi + else + video_log_load_success "$d" dep-modprobe + fi + done + + if insmod "$staged" 2>/dev/null; then + video_log_load_success "$m" insmod "$staged" + return 0 + fi + + log_warn "insmod failed for $staged" + return 1 +} + +video_modprobe_dryrun() { + m="$1" + out=$("$MODPROBE" -n "$m" 2>/dev/null) + if [ -n "$out" ]; then + printf '%s\n' "$out" | while IFS= read -r ln; do + log_info "modprobe -n $m: $ln" + done + else + log_info "modprobe -n $m:" + fi +} + +video_list_runtime_blocks() { + found=0 + for f in "$RUNTIME_BLOCK_DIR"/*.conf; do + [ -e "$f" ] || continue + if grep -Eiq '(^|[[:space:]])(blacklist|install)[[:space:]]+(qcom[-_]iris|iris[-_]vpu|venus[-_](core|dec|enc))' "$f" 2>/dev/null; then + log_info "$f" + found=1 + fi + done + if [ "$found" -eq 0 ]; then + log_info "(none)" + fi +} + +# ------------------------------------------------------------------------- +# Path resolution & load logging helpers +# ------------------------------------------------------------------------- +video_dump_stack_state() { + when="$1" # pre|post + + log_info "Modules ($when):" + log_info "lsmod (iris/venus):" + "$LSMOD" 2>/dev/null | awk 'NR==1 || $1 ~ /^(iris_vpu|qcom_iris|venus_core|venus_dec|venus_enc)$/ {print}' + + video_modprobe_dryrun qcom_iris + video_modprobe_dryrun iris_vpu + video_modprobe_dryrun venus_core + video_modprobe_dryrun venus_dec + video_modprobe_dryrun venus_enc + + log_info "runtime blocks:" + video_list_runtime_blocks +} + +video_log_resolve() { + # usage: video_log_resolve + # how: modinfo | system-tree | updates-tree | altroot-tree | ko-dir + m="$1"; how="$2"; p="$3" + case "$how" in + modinfo) + log_info "resolve-path: $m via modinfo -n => $p" + ;; + system-tree) + log_info "resolve-path: $m via /lib/modules => $p" + ;; + updates-tree) + log_info "resolve-path: $m via /lib/modules/*/updates => $p" + ;; + altroot-tree) + log_info "resolve-path: $m via KO_TREE => $p" + ;; + ko-dir) + log_info "resolve-path: $m via KO_DIRS => $p" + ;; + esac +} + +video_log_load_success() { + # usage: video_log_load_success [extra] + # how: modprobe-system | modprobe-altroot | insmod | dep-modprobe | dep-insmod + m="$1"; how="$2"; extra="$3" + case "$how" in + modprobe-system) + log_info "load-path: modprobe(system): $m" + ;; + modprobe-altroot) + log_info "load-path: modprobe(altroot=$KO_TREE): $m" + ;; + insmod) + # extra = path + log_info "load-path: insmod: $extra" + ;; + dep-modprobe) + log_info "load-path(dep): modprobe(system): $m" + ;; + dep-insmod) + # extra = path + log_info "load-path(dep): insmod: $extra" + ;; + esac +} + +video_find_module_file() { + # Resolve a module file path for a logical mod name (handles _ vs -). + # Prefers: modinfo -n, then .../updates/, then general search (and KO_DIRS/KO_TREE if provided). + m="$1" + kr="$(uname -r 2>/dev/null)" + + if [ -z "$kr" ]; then + kr="$(find /lib/modules -mindepth 1 -maxdepth 1 -type d -printf '%f\n' 2>/dev/null | head -n 1)" + fi + + if video_exist_cmd modinfo; then + p="$(modinfo -n "$m" 2>/dev/null)" + if [ -n "$p" ] && [ -f "$p" ]; then + video_log_resolve "$m" modinfo "$p" + printf '%s\n' "$p" + return 0 + fi + fi + + m_us="$m" + m_hy="$(printf '%s' "$m" | tr '_' '-')" + m_alt="$(printf '%s' "$m" | tr '-' '_')" + + # Helper to scan KO_DIRS (bounded search) + scan_ko_dirs() { + modbase="$1" # without .ko* + if [ -z "$KO_DIRS" ]; then + return 1 + fi + OLD_IFS="$IFS" + IFS=':' + for d in $KO_DIRS; do + IFS="$OLD_IFS" + # SC2015 fix: avoid `[ -n "$d" ] && [ -d "$d" ] || continue` + if [ -z "$d" ] || [ ! -d "$d" ]; then + continue + fi + p="$(find "$d" -maxdepth 3 -type f -name "${modbase}.ko*" -print -quit 2>/dev/null)" + if [ -n "$p" ]; then + video_log_resolve "$m" ko-dir "$p" + printf '%s\n' "$p" + return 0 + fi + done + IFS="$OLD_IFS" + return 1 + } + + # Optional order: prefer KO_DIRS first if requested + if [ "$KO_PREFER_CUSTOM" -eq 1 ] 2>/dev/null; then + for pat in "$m_us" "$m_hy" "$m_alt"; do + if scan_ko_dirs "$pat"; then + return 0 + fi + done + fi + + # Optional altroot tree (KO_TREE) first, if provided + if [ -n "$KO_TREE" ] && [ -d "$KO_TREE" ]; then + for pat in "$m_us" "$m_hy" "$m_alt"; do + p="$(find "$KO_TREE/lib/modules/$kr" -type f -name "${pat}.ko*" -print -quit 2>/dev/null)" + if [ -n "$p" ]; then + video_log_resolve "$m" altroot-tree "$p" + printf '%s\n' "$p" + return 0 + fi + done + fi + + for pat in "$m_us" "$m_hy" "$m_alt"; do + p="$(find "/lib/modules/$kr/updates" -type f -name "${pat}.ko*" 2>/dev/null | head -n 1)" + if [ -n "$p" ]; then + video_log_resolve "$m" updates-tree "$p" + printf '%s\n' "$p" + return 0 + fi + done + + for pat in "$m_us" "$m_hy" "$m_alt"; do + p="$(find "/lib/modules/$kr" -type f -name "${pat}.ko*" 2>/dev/null | head -n 1)" + if [ -n "$p" ]; then + video_log_resolve "$m" system-tree "$p" + printf '%s\n' "$p" + return 0 + fi + done + + # If not preferred-first, still try KO_DIRS at the end + if [ "$KO_PREFER_CUSTOM" -ne 1 ] 2>/dev/null; then + for pat in "$m_us" "$m_hy" "$m_alt"; do + if scan_ko_dirs "$pat"; then + return 0 + fi + done + fi + + return 1 +} + +modprobe_dryrun() { + video_modprobe_dryrun "$@" +} + +list_runtime_blocks() { + video_list_runtime_blocks "$@" +} + +dump_stack_state() { + video_dump_stack_state "$@" +} + +# ----------------------------------------------------------------------------- +# Runtime-only (session) block/unblock: lives under /run/modprobe.d +# ----------------------------------------------------------------------------- +video_block_mod_now() { + # usage: video_block_mod_now qcom_iris [iris_vpu ...] + mkdir -p "$RUNTIME_BLOCK_DIR" 2>/dev/null || true + + for m in "$@"; do + printf 'install %s /bin/false\n' "$m" > "$RUNTIME_BLOCK_DIR/${m}-block.conf" + done + + depmod -a 2>/dev/null || true + + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules 2>/dev/null || true + udevadm settle 2>/dev/null || true + fi + + video_usleep "${MOD_SETTLE_SLEEP}" +} + +video_unblock_mod_now() { + # usage: video_unblock_mod_now qcom_iris [iris_vpu ...] + for m in "$@"; do + rm -f "$RUNTIME_BLOCK_DIR/${m}-block.conf" + done + + depmod -a 2>/dev/null || true + + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules 2>/dev/null || true + udevadm settle 2>/dev/null || true + fi + + video_usleep "${MOD_SETTLE_SLEEP}" +} + +# ----------------------------------------------------------------------------- +# Persistent de-blacklist for a module (handles qcom_iris ↔ qcom-iris) +# ----------------------------------------------------------------------------- +video_persistent_unblock_module() { + m="$1" + if [ -z "$m" ]; then + return 0 + fi + + m_us="$(printf '%s' "$m" | tr '-' '_')" + m_hy="$(printf '%s' "$m" | tr '_' '-')" + + if command -v video_unblock_mod_now >/dev/null 2>&1; then + video_unblock_mod_now "$m_us" "$m_hy" + else + rm -f "/run/modprobe.d/${m_us}-block.conf" "/run/modprobe.d/${m_hy}-block.conf" 2>/dev/null || true + depmod -a 2>/dev/null || true + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules 2>/dev/null || true + udevadm settle 2>/dev/null || true + fi + fi + + for d in /etc/modprobe.d /lib/modprobe.d; do + [ -d "$d" ] || continue + for f in "$d"/*.conf; do + [ -e "$f" ] || continue + tmp="$f.tmp.$$" + awk -v a="$m_us" -v b="$m_hy" ' + BEGIN { IGNORECASE=1 } + { + line = $0 + bl1 = "^[[:space:]]*blacklist[[:space:]]*" a "([[:space:]]|$)" + bl2 = "^[[:space:]]*blacklist[[:space:]]*" b "([[:space:]]|$)" + in1 = "^[[:space:]]*install[[:space:]]*" a "([[:space:]]|$).*[/]bin[/]false" + in2 = "^[[:space:]]*install[[:space:]]*" b "([[:space:]]|$).*[/]bin[/]false" + if (line ~ bl1 || line ~ bl2 || line ~ in1 || line ~ in2) next + print line + } + ' "$f" >"$tmp" 2>/dev/null || { + rm -f "$tmp" >/dev/null 2>&1 + continue + } + mv "$tmp" "$f" + done + done + + for f in "/etc/modprobe.d/blacklist-${m_us}.conf" "/etc/modprobe.d/blacklist-${m_hy}.conf"; do + [ -e "$f" ] || continue + if ! grep -Eq '^[[:space:]]*[^#[:space:]]' "$f" 2>/dev/null; then + rm -f "$f" 2>/dev/null || true + fi + done + + depmod -a 2>/dev/null || true + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules 2>/dev/null || true + udevadm settle 2>/dev/null || true + fi + + video_usleep "${MOD_SETTLE_SLEEP}" + + log_info "persistent unblock done for $m (and aliases: $m_us, $m_hy)" + return 0 +} + +# ----------------------------------------------------------------------------- +# Retry wrapper for modprobe +# ----------------------------------------------------------------------------- +video_retry_modprobe() { + m="$1" + n="${MOD_RETRY_COUNT}" + i=0 + + if video_has_module_loaded "$m"; then + log_info "module already loaded (retry path skipped): $m" + return 0 + fi + + while [ "$i" -lt "$n" ]; do + i=$((i+1)) + + if video_has_module_loaded "$m"; then + log_info "module became present before attempt $i: $m" + return 0 + fi + + if [ -n "$KO_TREE" ] && [ -d "$KO_TREE" ]; then + log_info "modprobe attempt $i/$n (altroot=$KO_TREE): $m" + if "$MODPROBE" -d "$KO_TREE" "$m" 2>/dev/null; then + video_log_load_success "$m" modprobe-altroot + return 0 + fi + else + log_info "modprobe attempt $i/$n (system): $m" + if "$MODPROBE" "$m" 2>/dev/null; then + video_log_load_success "$m" modprobe-system + return 0 + fi + fi + + video_usleep "${MOD_RETRY_SLEEP}" + done + + if video_has_module_loaded "$m"; then + log_info "module present after retries: $m" + return 0 + fi + + log_warn "modprobe failed after $n attempts: $m" + return 1 +} + +# ----------------------------------------------------------------------------- +# modprobe with insmod fallback (standalone-like resilience) +# ----------------------------------------------------------------------------- +video_modprobe_or_insmod() { + m="$1" + + if video_has_module_loaded "$m"; then + log_info "module already loaded (modprobe/insmod path skipped): $m" + return 0 + fi + + if video_retry_modprobe "$m"; then + return 0 + fi + + log_warn "modprobe $m failed, attempting de-blacklist + retry" + video_persistent_unblock_module "$m" + video_usleep "${MOD_SETTLE_SLEEP}" + + if video_retry_modprobe "$m"; then + return 0 + fi + + if video_insmod_with_deps "$m"; then + return 0 + fi + + malt="$(printf '%s' "$m" | tr '_-' '-_')" + if [ "$malt" != "$m" ]; then + if video_retry_modprobe "$malt"; then + return 0 + fi + if video_insmod_with_deps "$malt"; then + return 0 + fi + fi + + log_warn "modprobe $m failed, and insmod fallback did not succeed" + return 1 +} + +# ----------------------------------------------------------------------------- +# Stack normalization & auto preference +# ----------------------------------------------------------------------------- +video_normalize_stack() { + s="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" + case "$s" in + upstream|base|up) + printf '%s\n' "upstream" + ;; + downstream|overlay|down) + printf '%s\n' "downstream" + ;; + auto|"") + printf '%s\n' "auto" + ;; + *) + printf '%s\n' "$s" + ;; + esac +} + +# ----------------------------------------------------------------------------- +# Platform detect → lemans|monaco|kodiak|unknown +# ----------------------------------------------------------------------------- +video_detect_platform() { + model="" + compat="" + + if [ -r /proc/device-tree/model ]; then + model=$(tr -d '\000' /dev/null) + fi + + if [ -r /proc/device-tree/compatible ]; then + compat=$(tr -d '\000' /dev/null) + fi + + s=$(printf '%s\n%s\n' "$model" "$compat" | tr '[:upper:]' '[:lower:]') + + # Monaco: qcs8300-ride, iq-8275-evk, qcs8275, generic qcs8300, or ride-sx+8300 + monaco_pat='qcs8300-ride|iq-8275-evk|qcs8275|qcs8300|ride-sx.*8300|8300.*ride-sx' + + # LeMans: qcs9100-ride, qcs9075, generic qcs9100, or ride-sx+9100 + lemans_pat='qcs9100-ride|qcs9075|qcs9100|ride-sx.*9100|9100.*ride-sx' + + # Kodiak: qcs6490, qcm6490, or rb3+6490 + kodiak_pat='qcs6490|qcm6490|rb3.*6490|6490.*rb3' + + if printf '%s' "$s" | grep -Eq "$lemans_pat"; then + printf '%s\n' "lemans" + return 0 + fi + + if printf '%s' "$s" | grep -Eq "$monaco_pat"; then + printf '%s\n' "monaco" + return 0 + fi + + if printf '%s' "$s" | grep -Eq "$kodiak_pat"; then + printf '%s\n' "kodiak" + return 0 + fi + + printf '%s\n' "unknown" +} + +# ----------------------------------------------------------------------------- +# Validation helpers +# ----------------------------------------------------------------------------- +video_validate_upstream_loaded() { + plat="$1" + + if [ -z "$plat" ]; then + plat="$(video_detect_platform)" + fi + + case "$plat" in + lemans|monaco) + # Any upstream build has qcom_iris present + if video_has_module_loaded qcom_iris; then + return 0 + fi + return 1 + ;; + + kodiak) + # Upstream valid if Venus trio present OR pure qcom_iris-only present + if video_has_module_loaded venus_core && video_has_module_loaded venus_dec && video_has_module_loaded venus_enc; then + return 0 + fi + + if video_has_module_loaded qcom_iris && ! video_has_module_loaded iris_vpu; then + return 0 + fi + + return 1 + ;; + esac + + return 1 +} + +video_validate_downstream_loaded() { + plat="$1" + case "$plat" in + lemans|monaco) + if video_has_module_loaded "$IRIS_VPU_MOD" && ! video_has_module_loaded "$IRIS_UP_MOD"; then + return 0 + fi + return 1 + ;; + kodiak) + if video_has_module_loaded "$IRIS_VPU_MOD" && ! video_has_module_loaded "$VENUS_CORE_MOD"; then + return 0 + fi + return 1 + ;; + *) + return 1 + ;; + esac +} + +video_assert_stack() { + plat="$1" + want="$(video_normalize_stack "$2")" + + case "$want" in + upstream|up|base) + if video_validate_upstream_loaded "$plat"; then + return 0 + fi + case "$plat" in + lemans|monaco) + log_fail "[STACK] Upstream requested but qcom_iris + iris_vpu are not both present." + ;; + kodiak) + log_fail "[STACK] Upstream requested but venus_core/dec/enc are not all present." + ;; + *) + log_fail "[STACK] Upstream requested but platform '$plat' is unknown." + ;; + esac + return 1 + ;; + downstream|overlay|down) + if video_validate_downstream_loaded "$plat"; then + return 0 + fi + case "$plat" in + lemans|monaco) + log_fail "[STACK] Downstream requested but iris_vpu not present or qcom_iris still loaded." + ;; + kodiak) + log_fail "[STACK] Downstream requested but iris_vpu not present or venus_core still loaded." + ;; + *) + log_fail "[STACK] Downstream requested but platform '$plat' is unknown." + ;; + esac + return 1 + ;; + *) + log_fail "[STACK] Unknown target stack '$want'." + return 1 + ;; + esac +} + +video_stack_status() { + plat="$1" + + if [ -z "$plat" ]; then + plat="$(video_detect_platform)" + fi + + case "$plat" in + lemans|monaco) + # Upstream accepted if: + # - pure upstream build: qcom_iris present and iris_vpu absent + # - base+overlay build: qcom_iris and iris_vpu both present + if video_has_module_loaded qcom_iris && ! video_has_module_loaded iris_vpu; then + printf '%s\n' "upstream" + return 0 + fi + + if video_has_module_loaded qcom_iris && video_has_module_loaded iris_vpu; then + printf '%s\n' "upstream" + return 0 + fi + + # Downstream if only iris_vpu is present (no qcom_iris) + if video_has_module_loaded iris_vpu && ! video_has_module_loaded qcom_iris; then + printf '%s\n' "downstream" + return 0 + fi + ;; + + kodiak) + # Upstream accepted if: + # - Venus trio present (canonical upstream on Kodiak), OR + # - pure upstream build: qcom_iris present and iris_vpu absent + if video_has_module_loaded venus_core && video_has_module_loaded venus_dec && video_has_module_loaded venus_enc; then + printf '%s\n' "upstream" + return 0 + fi + + if video_has_module_loaded qcom_iris && ! video_has_module_loaded iris_vpu; then + printf '%s\n' "upstream" + return 0 + fi + + # Downstream if iris_vpu present and Venus core not loaded + if video_has_module_loaded iris_vpu && ! video_has_module_loaded venus_core; then + printf '%s\n' "downstream" + return 0 + fi + ;; + esac + + printf '%s\n' "unknown" + return 1 +} + +# ----------------------------------------------------------------------------- +# Unload all video modules relevant to platform (standalone-like) +# ----------------------------------------------------------------------------- +video_unload_all_video_modules() { + plat="$1" + + tryrmmod() { + mod="$1" + if video_has_module_loaded "$mod"; then + if "$MODPROBE" -r "$mod" 2>/dev/null; then + log_info "Removed module: $mod" + else + log_warn "Could not remove $mod via modprobe -r; retrying after short delay" + video_usleep 0.2 + if "$MODPROBE" -r "$mod" 2>/dev/null; then + log_info "Removed module after retry: $mod" + else + log_warn "Still could not remove: $mod" + fi + fi + fi + } + + case "$plat" in + lemans|monaco) + tryrmmod "$IRIS_UP_MOD" + tryrmmod "$IRIS_VPU_MOD" + tryrmmod "$IRIS_UP_MOD" + ;; + kodiak) + tryrmmod "$VENUS_ENC_MOD" + tryrmmod "$VENUS_DEC_MOD" + tryrmmod "$VENUS_CORE_MOD" + tryrmmod "$IRIS_VPU_MOD" + tryrmmod "$IRIS_UP_MOD" + ;; + *) + : + ;; + esac + + depmod -a 2>/dev/null || true + if command -v udevadm >/dev/null 2>&1; then + udevadm settle 2>/dev/null || true + fi + video_usleep "${MOD_SETTLE_SLEEP}" +} + +# ----------------------------------------------------------------------------- +# Hot switch (best-effort, no reboot) — mirrors standalone flow +# ----------------------------------------------------------------------------- +video_hot_switch_modules() { + plat="$1" + stack="$2" + rc=0 + + case "$plat" in + lemans|monaco) + if [ "$stack" = "downstream" ]; then + video_block_upstream_strict + video_unblock_mod_now "$IRIS_VPU_MOD" + video_usleep "${MOD_SETTLE_SLEEP}" + + video_unload_all_video_modules "$plat" + + if ! video_modprobe_or_insmod "$IRIS_VPU_MOD"; then + rc=1 + fi + video_usleep "${MOD_SETTLE_SLEEP}" + + if video_has_module_loaded "$IRIS_UP_MOD"; then + log_warn "$IRIS_UP_MOD reloaded unexpectedly; unloading and keeping block in place" + "$MODPROBE" -r "$IRIS_UP_MOD" 2>/dev/null || rc=1 + video_usleep 0.2 + fi + else + video_unblock_mod_now "$IRIS_UP_MOD" "$IRIS_VPU_MOD" + video_usleep "${MOD_SETTLE_SLEEP}" + + video_unload_all_video_modules "$plat" + + if ! video_modprobe_or_insmod "$IRIS_UP_MOD"; then + log_warn "modprobe $IRIS_UP_MOD failed; printing current runtime blocks & retrying" + video_list_runtime_blocks + video_unblock_mod_now "$IRIS_UP_MOD" + video_usleep "${MOD_SETTLE_SLEEP}" + video_modprobe_or_insmod "$IRIS_UP_MOD" || rc=1 + fi + video_modprobe_or_insmod "$IRIS_VPU_MOD" || true + video_usleep "${MOD_SETTLE_SLEEP}" + fi + ;; + kodiak) + if [ "$stack" = "downstream" ]; then + video_block_mod_now "$VENUS_CORE_MOD" "$VENUS_DEC_MOD" "$VENUS_ENC_MOD" "$IRIS_UP_MOD" + video_unblock_mod_now "$IRIS_VPU_MOD" + video_usleep "${MOD_SETTLE_SLEEP}" + + "$MODPROBE" -r "$IRIS_VPU_MOD" 2>/dev/null || true + "$MODPROBE" -r "$VENUS_ENC_MOD" 2>/dev/null || true + "$MODPROBE" -r "$VENUS_DEC_MOD" 2>/dev/null || true + "$MODPROBE" -r "$VENUS_CORE_MOD" 2>/dev/null || true + "$MODPROBE" -r "$IRIS_UP_MOD" 2>/dev/null || true + video_usleep "${MOD_SETTLE_SLEEP}" + + if ! video_modprobe_or_insmod "$IRIS_VPU_MOD"; then + log_warn "Kodiak: failed to load $IRIS_VPU_MOD" + rc=1 + fi + + log_info "Kodiak: invoking firmware swap/reload helper (if VIDEO_FW_DS provided)" + if ! video_kodiak_swap_and_reload "${VIDEO_FW_DS}"; then + log_warn "Kodiak: swap/reload helper reported failure (continuing)" + fi + video_usleep "${MOD_SETTLE_SLEEP}" + + video_log_fw_hint + else + video_unblock_mod_now "$VENUS_CORE_MOD" "$VENUS_DEC_MOD" "$VENUS_ENC_MOD" "$IRIS_VPU_MOD" + "$MODPROBE" -r "$IRIS_VPU_MOD" 2>/dev/null || true + video_usleep "${MOD_SETTLE_SLEEP}" + video_modprobe_or_insmod "$VENUS_CORE_MOD" || rc=1 + video_modprobe_or_insmod "$VENUS_DEC_MOD" || true + video_modprobe_or_insmod "$VENUS_ENC_MOD" || true + video_usleep "${MOD_SETTLE_SLEEP}" + video_log_fw_hint + fi + ;; + *) + rc=1 + ;; + esac + + return $rc +} + +# ----------------------------------------------------------------------------- +# Entry point: ensure desired stack +# ----------------------------------------------------------------------------- +video_ensure_stack() { + want_raw="$1" # upstream|downstream|auto|base|overlay|up|down + plat="$2" + + if [ -z "$plat" ]; then + plat="$(video_detect_platform)" + fi + + want="$(video_normalize_stack "$want_raw")" + + if [ "$want" = "auto" ]; then + pref="$(video_auto_preference_from_blacklist "$plat")" + if [ "$pref" != "unknown" ]; then + want="$pref" + else + cur_aut="$(video_stack_status "$plat")" + if [ "$cur_aut" != "unknown" ]; then + want="$cur_aut" + else + want="upstream" + fi + fi + log_info "AUTO stack selection => $want" + fi + + # ---------------------------------------------------------------------- + # Early no-op: if current state already equals desired, do NOT hot switch. + # This covers: + # - Build #1 (pure upstream: qcom_iris only) on lemans/monaco/kodiak + # - Build #2 (base+overlay: qcom_iris + iris_vpu) when upstream is requested + # - Downstream already active (e.g., iris_vpu only on lemans/monaco, or kodiak downstream) + # Still allow Kodiak downstream FW swap without touching modules. + # ---------------------------------------------------------------------- + cur_state="$(video_stack_status "$plat")" + + if [ "$cur_state" = "$want" ]; then + if [ "$plat" = "kodiak" ] && [ "$want" = "downstream" ] && [ -n "$VIDEO_FW_DS" ] && [ -f "$VIDEO_FW_DS" ]; then + log_info "Kodiak: downstream already active; applying FW swap + live reload without hot switch." + video_kodiak_swap_and_reload "$VIDEO_FW_DS" || log_warn "Kodiak: FW swap/reload failed (continuing)" + fi + printf '%s\n' "$cur_state" + return 0 + fi + + # Fast paths (retain existing logic; these also help when cur_state was unknown) + case "$want" in + upstream|up|base) + case "$plat" in + lemans|monaco) + if video_has_module_loaded qcom_iris && ! video_has_module_loaded iris_vpu; then + printf '%s\n' "upstream" + return 0 + fi + ;; + kodiak) + if video_has_module_loaded venus_core && video_has_module_loaded venus_dec && video_has_module_loaded venus_enc; then + printf '%s\n' "upstream" + return 0 + fi + if video_has_module_loaded qcom_iris && ! video_has_module_loaded iris_vpu; then + printf '%s\n' "upstream" + return 0 + fi + ;; + esac + if video_validate_upstream_loaded "$plat"; then + printf '%s\n' "upstream" + return 0 + fi + ;; + downstream|overlay|down) + if video_validate_downstream_loaded "$plat"; then + if [ "$plat" = "kodiak" ] && [ -n "$VIDEO_FW_DS" ]; then + log_info "Kodiak: downstream already loaded, FW override provided — performing FW swap + live reload." + video_kodiak_swap_and_reload "$VIDEO_FW_DS" || log_warn "Kodiak: post-switch FW swap/reload failed (continuing)" + fi + printf '%s\n' "downstream" + return 0 + fi + ;; + esac + + # Only reach here if a switch is actually required. + video_apply_blacklist_for_stack "$plat" "$want" || return 1 + video_usleep "${MOD_SETTLE_SLEEP}" + + video_hot_switch_modules "$plat" "$want" || true + + if [ "$plat" = "kodiak" ] && [ "$want" = "downstream" ] && [ -n "$VIDEO_FW_DS" ] && [ -f "$VIDEO_FW_DS" ]; then + if video_validate_downstream_loaded "$plat"; then + log_info "Kodiak: downstream active and FW override detected — ensuring FW swap + live reload." + video_kodiak_swap_and_reload "$VIDEO_FW_DS" || log_warn "Kodiak: post-switch FW swap/reload failed (continuing)" + fi + fi + + if [ "$want" = "upstream" ]; then + if video_validate_upstream_loaded "$plat"; then + printf '%s\n' "upstream" + return 0 + fi + else + if video_validate_downstream_loaded "$plat"; then + printf '%s\n' "downstream" + return 0 + fi + fi + + printf '%s\n' "unknown" + return 1 +} + +video_block_upstream_strict() { + video_block_mod_now "$IRIS_UP_MOD" + + if [ -w /etc/modprobe.d ]; then + if ! grep -q "install[[:space:]]\+${IRIS_UP_MOD}[[:space:]]\+/bin/false" /etc/modprobe.d/qcom_iris-block.conf 2>/dev/null; then + printf 'install %s /bin/false\n' "$IRIS_UP_MOD" >> /etc/modprobe.d/qcom_iris-block.conf 2>/dev/null || true + fi + + depmod -a 2>/dev/null || true + + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules 2>/dev/null || true + udevadm settle 2>/dev/null || true + fi + + video_usleep "${MOD_SETTLE_SLEEP}" + fi +} + +# ----------------------------------------------------------------------------- +# udev refresh + prune stale /dev/video* and /dev/media* nodes +# ----------------------------------------------------------------------------- +video_clean_and_refresh_v4l() { + if video_exist_cmd udevadm; then + log_info "udev trigger: video4linux/media" + udevadm trigger --subsystem-match=video4linux --action=change 2>/dev/null || true + udevadm trigger --subsystem-match=media --action=change 2>/dev/null || true + udevadm settle 2>/dev/null || true + fi + + for n in /dev/video*; do + [ -e "$n" ] || continue + bn=$(basename "$n") + if [ ! -e "/sys/class/video4linux/$bn" ]; then + log_info "Pruning stale node: $n" + rm -f "$n" 2>/dev/null || true + fi + done + + for n in /dev/media*; do + [ -e "$n" ] || continue + bn=$(basename "$n") + if [ ! -e "/sys/class/media/$bn" ]; then + log_info "Pruning stale node: $n" + rm -f "$n" 2>/dev/null || true + fi + done +} + +# ----------------------------------------------------------------------------- +# DMESG triage +# ----------------------------------------------------------------------------- +video_scan_dmesg_if_enabled() { + dm="$1" + logdir="$2" + if [ "$dm" -ne 1 ] 2>/dev/null; then + return 2 + fi + MODS='oom|memory|BUG|hung task|soft lockup|hard lockup|rcu|page allocation failure|I/O error' + EXCL='using dummy regulator|not found|EEXIST|probe deferred' + if scan_dmesg_errors "$logdir" "$MODS" "$EXCL"; then + return 0 + fi + return 1 +} + +# ----------------------------------------------------------------------------- +# JSON helpers (jq-free) — robust multi-key scan +# ----------------------------------------------------------------------------- +video_is_decode_cfg() { + cfg="$1" + b=$(basename "$cfg" | tr '[:upper:]' '[:lower:]') + + case "$b" in + *dec*.json) + return 0 + ;; + *enc*.json) + return 1 + ;; + esac + + dom=$(sed -n 's/.*"Domain"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$cfg" 2>/dev/null | head -n 1) + dom_l=$(printf '%s' "$dom" | tr '[:upper:]' '[:lower:]') + + case "$dom_l" in + decoder|decode) + return 0 + ;; + encoder|encode) + return 1 + ;; + esac + + return 0 +} + +video_extract_scalar() { + k="$1" + cfg="$2" + sed -n "s/.*\"$k\"[[:space:]]*:[[:space:]]*\"\\([^\"\r\n]*\\)\".*/\\1/p" "$cfg" +} + +video_extract_array() { + k="$1" + cfg="$2" + sed -n "s/.*\"$k\"[[:space:]]*:\\s*\\[\\(.*\\)\\].*/\\1/p" "$cfg" \ + | tr ',' '\n' \ + | sed -n 's/.*"\([^"]*\)".*/\1/p' +} + +video_extract_array_ml() { + k="$1" + cfg="$2" + awk -v k="$k" ' + $0 ~ "\""k"\"[[:space:]]*:" {in=1} + in {print; if ($0 ~ /\]/) exit} + ' "$cfg" \ + | sed -n 's/.*"\([^"]*\)".*/\1/p' \ + | grep -vx "$k" +} + +video_strings_from_array_key() { + k="$1" + cfg="$2" + { + video_extract_array_ml "$k" "$cfg" + video_extract_array "$k" "$cfg" + } 2>/dev/null \ + | sed '/^$/d' \ + | awk '!seen[$0]++' +} + +video_extract_base_dirs() { + cfg="$1" + for k in InputDir InputDirectory InputFolder BasePath BaseDir; do + video_extract_scalar "$k" "$cfg" + done \ + | sed '/^$/d' \ + | head -n 1 +} + +video_guess_codec_from_cfg() { + cfg="$1" + + for k in Codec codec CodecName codecName VideoCodec videoCodec DecoderName EncoderName Name name; do + v=$(video_extract_scalar "$k" "$cfg" | head -n 1) + if [ -n "$v" ]; then + printf '%s\n' "$v" + return 0 + fi + done + + for tok in hevc h265 h264 av1 vp9 vp8 mpeg4 mpeg2 h263 avc; do + if grep -qiE "(^|[^A-Za-z0-9])${tok}([^A-Za-z0-9]|$)" "$cfg" 2>/dev/null; then + printf '%s\n' "$tok" + return 0 + fi + done + + b=$(basename "$cfg" | tr '[:upper:]' '[:lower:]') + for tok in hevc h265 h264 av1 vp9 vp8 mpeg4 mpeg2 h263 avc; do + case "$b" in + *"$tok"*) + printf '%s\n' "$tok" + return 0 + ;; + esac + done + + printf '%s\n' "unknown" + return 0 +} + +video_canon_codec() { + c=$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]') + case "$c" in + h265|hevc) + printf '%s\n' "hevc" + ;; + h264|avc) + printf '%s\n' "h264" + ;; + vp9) + printf '%s\n' "vp9" + ;; + vp8) + printf '%s\n' "vp8" + ;; + av1) + printf '%s\n' "av1" + ;; + mpeg4) + printf '%s\n' "mpeg4" + ;; + mpeg2) + printf '%s\n' "mpeg2" + ;; + h263) + printf '%s\n' "h263" + ;; + *) + printf '%s\n' "$c" + ;; + esac +} + +video_pretty_name_from_cfg() { + cfg="$1" + base=$(basename "$cfg" .json) + nm=$(video_extract_scalar "name" "$cfg") + if [ -z "$nm" ]; then + nm=$(video_extract_scalar "Name" "$cfg") + fi + if video_is_decode_cfg "$cfg"; then + cd_op="Decode" + else + cd_op="Encode" + fi + codec=$(video_extract_scalar "codec" "$cfg") + if [ -z "$codec" ]; then + codec=$(video_extract_scalar "Codec" "$cfg") + fi + nice="$cd_op:$base" + if [ -n "$nm" ]; then + nice="$nm" + elif [ -n "$codec" ]; then + nice="$cd_op:$codec ($base)" + fi + safe=$(printf '%s' "$nice" | tr ' ' '_' | tr -cd 'A-Za-z0-9._-') + printf '%s|%s\n' "$nice" "$safe" +} + +# Collect input clip paths from varied schemas (expanded keys) +video_extract_input_clips() { + cfg="$1" + + { + for k in \ + InputPath inputPath Inputpath input InputFile input_file infile InFile InFileName \ + FilePath Source Clip File Bitstream BitstreamFile YUV YUVFile YuvFileName Path RawFile RawInput RawYUV \ + Sequence + do + video_extract_scalar "$k" "$cfg" + done + } 2>/dev/null | sed '/^$/d' + + { + for k in Inputs InputFiles input_files Clips Files FileList Streams Bitstreams FileNames InputStreams; do + video_strings_from_array_key "$k" "$cfg" + done + } 2>/dev/null | sed '/^$/d' + + basedir=$(video_extract_base_dirs "$cfg") + if [ -n "$basedir" ]; then + for arr in Files Inputs Clips InputFiles FileNames; do + video_strings_from_array_key "$arr" "$cfg" | sed '/^\//! s_^_'"$basedir"'/_' + done + fi +} + +# ----------------------------------------------------------------------------- +# Network-aware clip ensure/fetch +# ----------------------------------------------------------------------------- +video_ensure_clips_present_or_fetch() { + cfg="$1" + tu="$2" + + clips=$(video_extract_input_clips "$cfg") + if [ -z "$clips" ]; then + return 0 + fi + + tmp_list="${LOG_DIR:-.}/.video_missing.$$" + : > "$tmp_list" + + printf '%s\n' "$clips" | while IFS= read -r p; do + [ -z "$p" ] && continue + case "$p" in + /*) + abs="$p" + ;; + *) + abs=$(cd "$(dirname "$cfg")" 2>/dev/null && pwd)/$p + ;; + esac + [ -f "$abs" ] || printf '%s\n' "$abs" >> "$tmp_list" + done + + if [ ! -s "$tmp_list" ]; then + rm -f "$tmp_list" 2>/dev/null || true + return 0 + fi + + log_warn "Some input clips are missing (list: $tmp_list)" + + if [ -z "$tu" ]; then + tu="$TAR_URL" + fi + + if command -v ensure_network_online >/dev/null 2>&1; then + if ! ensure_network_online; then + log_warn "Network offline/limited; cannot fetch media bundle" + rm -f "$tmp_list" 2>/dev/null || true + return 2 + fi + fi + + if [ -n "$tu" ]; then + log_info "Attempting fetch via TAR_URL=$tu" + if extract_tar_from_url "$tu"; then + rm -f "$tmp_list" 2>/dev/null || true + return 0 + fi + log_warn "Fetch/extract failed for TAR_URL" + rm -f "$tmp_list" 2>/dev/null || true + return 1 + fi + + log_warn "No TAR_URL provided; cannot fetch media bundle." + rm -f "$tmp_list" 2>/dev/null || true + return 1 +} + +# ----------------------------------------------------------------------------- +# JUnit helper +# ----------------------------------------------------------------------------- +video_junit_append_case() { + of="$1" + class="$2" + name="$3" + t="$4" + st="$5" + logf="$6" + + if [ -z "$of" ]; then + return 0 + fi + + tailtxt="" + if [ -f "$logf" ]; then + tailtxt=$(tail -n 50 "$logf" 2>/dev/null | sed 's/&/\&/g;s//\>/g') + fi + + { + printf ' \n' "$class" "$name" "$t" + case "$st" in + PASS) + : + ;; + SKIP) + printf ' \n' + ;; + FAIL) + printf ' \n' "failed" + printf '%s\n' "$tailtxt" + printf ' \n' + ;; + esac + printf ' \n' + } >> "$of" +} + +# ----------------------------------------------------------------------------- +# Timeout wrapper availability + single run +# ----------------------------------------------------------------------------- +video_have_run_with_timeout() { + video_exist_cmd run_with_timeout +} + +video_prepare_app() { + app="${VIDEO_APP:-/usr/bin/iris_v4l2_test}" + + if [ -z "$app" ] || [ ! -e "$app" ]; then + log_fail "App not found: $app" + return 1 + fi + + if [ -x "$app" ]; then + return 0 + fi + + if chmod +x "$app" 2>/dev/null; then + log_info "Set executable bit on $app" + return 0 + fi + + tmp="/tmp/$(basename "$app").$$" + if cp -f "$app" "$tmp" 2>/dev/null && chmod +x "$tmp" 2>/dev/null; then + VIDEO_APP="$tmp" + log_info "Using temp executable copy: $VIDEO_APP" + return 0 + fi + + log_warn "Could not make app executable (chmod/copy failed). Execution may fail." + return 1 +} + +video_run_once() { + cfg="$1" + logf="$2" + tmo="$3" + suc="$4" + lvl="$5" + + video_prepare_app || true + + : > "$logf" + + { + iso_now="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo now)" + printf 'BEGIN-RUN %s\n' "$iso_now" + printf 'APP=%s\n' "$VIDEO_APP" + printf 'CFG=%s\n' "$cfg" + printf 'LOGLEVEL=%s TIMEOUT=%s\n' "$lvl" "${tmo:-none}" + printf 'CMD=%s %s %s %s\n' "$VIDEO_APP" "--config" "$cfg" "--loglevel $lvl" + } >>"$logf" 2>&1 + + if video_have_run_with_timeout; then + if run_with_timeout "$tmo" "$VIDEO_APP" --config "$cfg" --loglevel "$lvl" >>"$logf" 2>&1; then + : + else + rc=$? + if [ "$rc" -eq 124 ] 2>/dev/null; then + log_fail "[run] timeout after ${tmo}s" + else + log_fail "[run] $VIDEO_APP exited rc=$rc" + fi + printf 'END-RUN rc=%s\n' "$rc" >>"$logf" + grep -Eq "$suc" "$logf" + return $? + fi + else + if "$VIDEO_APP" --config "$cfg" --loglevel "$lvl" >>"$logf" 2>&1; then + : + else + rc=$? + log_fail "[run] $VIDEO_APP exited rc=$rc (no timeout enforced)" + printf 'END-RUN rc=%s\n' "$rc" >>"$logf" + grep -Eq "$suc" "$logf" + return $? + fi + fi + + printf 'END-RUN rc=0\n' >>"$logf" + grep -Eq "$suc" "$logf" +} + +# ----------------------------------------------------------------------------- +# Kodiak firmware swap + live reload (no reboot) +# ----------------------------------------------------------------------------- +video_kodiak_fw_basename() { + printf '%s\n' "vpu20_p1_gen2.mbn" +} + +video_kodiak_install_fw() { + # usage: video_kodiak_install_fw /path/to/vpuw20_1v.mbn + src="$1" + + if [ -z "$src" ] || [ ! -f "$src" ]; then + log_warn "Kodiak FW src missing: $src" + return 1 + fi + + dst="$FW_PATH_KODIAK" + tmp="${dst}.new.$$" + + mkdir -p "$(dirname "$dst")" 2>/dev/null || true + + if [ -f "$dst" ]; then + mkdir -p "$FW_BACKUP_DIR" 2>/dev/null || true + ts=$(date +%Y%m%d%H%M%S 2>/dev/null || printf '%s' "now") + cp -f "$dst" "$FW_BACKUP_DIR/vpu20_p1_gen2.mbn.$ts.bak" 2>/dev/null || true + fi + + if ! cp -f "$src" "$tmp" 2>/dev/null; then + log_warn "FW copy to temp failed: $tmp" + return 1 + fi + + chmod 0644 "$tmp" 2>/dev/null || true + chown root:root "$tmp" 2>/dev/null || true + + if ! mv -f "$tmp" "$dst" 2>/dev/null; then + log_warn "FW mv into place failed: $dst" + rm -f "$tmp" 2>/dev/null || true + return 1 + fi + + sync || true + + if command -v restorecon >/dev/null 2>&1; then + restorecon "$dst" 2>/dev/null || true + fi + + log_info "Kodiak FW installed at: $dst (from: $(basename "$src"))" + return 0 +} + +video_kodiak_install_firmware() { + src_dir="${VIDEO_FW_BACKUP_DIR:-/opt}" + dest="/lib/firmware/qcom/vpu/vpu20_p1_gen2.mbn" + + if [ ! -d "$src_dir" ]; then + log_warn "Backup dir not found: $src_dir" + return 0 + fi + + mkdir -p "$(dirname "$dest")" 2>/dev/null || true + + candidates="" + for g in \ + "$src_dir"/vpu20_p1_gen2_*.mbn \ + "$src_dir"/vpu20_p1_gen2.mbn.* \ + "$src_dir"/vpu20_p1_gen2*.mbn.bak \ + "$src_dir"/vpu20_p1_gen2*.bak \ + "$src_dir"/vpu20_p1_gen2*.mbn.* \ + ; do + for f in $g; do + if [ -f "$f" ]; then + candidates="$candidates +$f" + fi + done + done + + if [ -z "$candidates" ]; then + log_warn "No backup firmware found under $src_dir (tried patterns: vpu20_p1_gen2_*.mbn, vpu20_p1_gen2.mbn.*, *.mbn.bak, *.bak)" + return 0 + fi + + newest="" + for f in $candidates; do + [ -f "$f" ] || continue + if [ -z "$newest" ] || [ -n "$(find "$f" -prune -newer "$newest" -print -quit 2>/dev/null)" ]; then + newest="$f" + fi + done + + if [ -z "$newest" ]; then + newest="$(printf '%s\n' "$candidates" | head -n1)" + fi + + log_info "Using backup firmware: $newest → $dest" + + if cp -f "$newest" "$dest" 2>/dev/null; then + chmod 0644 "$dest" 2>/dev/null || true + log_pass "Installed Kodiak upstream firmware to $dest" + return 0 + fi + + log_warn "cp failed; trying install -D" + + if install -m 0644 -D "$newest" "$dest" 2>/dev/null; then + log_pass "Installed Kodiak upstream firmware to $dest" + return 0 + fi + + log_error "Failed to install firmware from $newest to $dest" + return 1 +} + +# remoteproc reload must reference the *destination* basename +video_kodiak_fw_basename() { + printf '%s\n' "vpu20_p1_gen2.mbn" +} + +video_kodiak_try_remoteproc_reload() { + bn="$(video_kodiak_fw_basename)" + did=0 + + for rp in /sys/class/remoteproc/remoteproc*; do + [ -d "$rp" ] || continue + name="$(tr '[:upper:]' '[:lower:]' < "$rp/name" 2>/dev/null)" + case "$name" in + *vpu*|*video*|*iris*) + log_info "remoteproc: $rp (name=$name)" + if [ -r "$rp/state" ]; then + log_info "remoteproc state (pre): $(cat "$rp/state" 2>/dev/null)" + fi + if [ -w "$rp/state" ]; then + echo stop > "$rp/state" 2>/dev/null || true + fi + if [ -w "$rp/firmware" ]; then + printf '%s' "$bn" > "$rp/firmware" 2>/dev/null || true + fi + if [ -w "$rp/state" ]; then + echo start > "$rp/state" 2>/dev/null || true + fi + video_usleep 1 + if [ -r "$rp/state" ]; then + log_info "remoteproc state (post): $(cat "$rp/state" 2>/dev/null)" + fi + did=1 + ;; + esac + done + + if [ $did -eq 1 ]; then + log_info "remoteproc reload attempted with $bn" + return 0 + fi + + return 1 +} + +video_kodiak_try_module_reload() { + rc=1 + + if video_has_module_loaded "$IRIS_VPU_MOD"; then + log_info "module reload: rmmod $IRIS_VPU_MOD" + "$MODPROBE" -r "$IRIS_VPU_MOD" 2>/dev/null || true + video_usleep 1 + fi + + log_info "module reload: modprobe $IRIS_VPU_MOD" + + if "$MODPROBE" "$IRIS_VPU_MOD" 2>/dev/null; then + rc=0 + else + ko=$("$MODPROBE" -n -v "$IRIS_VPU_MOD" 2>/dev/null | awk '/(^| )insmod( |$)/ {print $2; exit}') + if [ -n "$ko" ] && [ -f "$ko" ]; then + log_info "module reload: insmod $ko" + if insmod "$ko" 2>/dev/null; then + rc=0 + else + rc=1 + fi + fi + fi + + if [ $rc -eq 0 ]; then + log_info "iris_vpu module reloaded" + fi + + return $rc +} + +video_kodiak_try_unbind_bind() { + did=0 + + for drv in /sys/bus/platform/drivers/*; do + [ -d "$drv" ] || continue + case "$(basename "$drv")" in + *iris*|*vpu*|*video*) + for dev in "$drv"/*; do + [ -L "$dev" ] || continue + dn="$(basename "$dev")" + log_info "platform: unbind $dn from $(basename "$drv")" + if [ -w "$drv/unbind" ]; then + echo "$dn" > "$drv/unbind" 2>/dev/null || true + fi + video_usleep 1 + log_info "platform: bind $dn to $(basename "$drv")" + if [ -w "$drv/bind" ]; then + echo "$dn" > "$drv/bind" 2>/dev/null || true + fi + did=1 + done + ;; + esac + done + + if [ $did -eq 1 ]; then + log_info "platform unbind/bind attempted" + return 0 + fi + + return 1 +} + +video_kodiak_swap_and_reload() { + # usage: video_kodiak_swap_and_reload /path/to/newfw.mbn + newsrc="$1" + + if [ -z "$newsrc" ] || [ ! -f "$newsrc" ]; then + log_warn "No FW source to install (VIDEO_FW_DS unset or missing)" + return 1 + fi + + video_kodiak_install_fw "$newsrc" || return 1 + + if video_kodiak_try_remoteproc_reload; then + video_clean_and_refresh_v4l + return 0 + fi + + if video_kodiak_try_module_reload; then + video_clean_and_refresh_v4l + return 0 + fi + + if video_kodiak_try_unbind_bind; then + video_clean_and_refresh_v4l + return 0 + fi + + log_warn "FW reload attempts did not confirm success (remoteproc/module/unbind)." + return 1 +} + +if ! command -v video_try_modprobe_then_insmod >/dev/null 2>&1; then +video_try_modprobe_then_insmod() { + m="$1" + + if "$MODPROBE" "$m" 2>/dev/null; then + return 0 + fi + + p="$("$MODPROBE" -n -v "$m" 2>/dev/null | awk '/(^| )insmod( |$)/ {print $2; exit}')" + if [ -n "$p" ] && [ -f "$p" ]; then + if insmod "$p" 2>/dev/null; then + return 0 + fi + fi + + return 1 +} +fi + +# --- Persistent blacklist storage (if not already defined) --- +: "${BLACKLIST_DIR:=/etc/modprobe.d}" +: "${BLACKLIST_FILE:=$BLACKLIST_DIR/blacklist.conf}" + +video_is_blacklisted() { + tok="$1" + if [ ! -f "$BLACKLIST_FILE" ]; then + return 1 + fi + grep -q "^blacklist[[:space:]]\+$tok$" "$BLACKLIST_FILE" 2>/dev/null +} + +video_ensure_blacklist() { + tok="$1" + mkdir -p "$BLACKLIST_DIR" 2>/dev/null || true + if ! video_is_blacklisted "$tok"; then + printf 'blacklist %s\n' "$tok" >>"$BLACKLIST_FILE" + log_info "Added persistent blacklist for: $tok" + fi +} + +video_remove_blacklist() { + tok="$1" + if [ ! -f "$BLACKLIST_FILE" ]; then + return 0 + fi + tmp="$BLACKLIST_FILE.tmp.$$" + sed "/^[[:space:]]*blacklist[[:space:]]\+${tok}[[:space:]]*$/d" \ + "$BLACKLIST_FILE" >"$tmp" 2>/dev/null && mv "$tmp" "$BLACKLIST_FILE" + log_info "Removed persistent blacklist for: $tok" +} + +# ----------------------------------------------------------------------------- +# Blacklist desired stack (persistent, cross-boot) +# ----------------------------------------------------------------------------- +video_apply_blacklist_for_stack() { + plat="$1" + stack="$2" + + case "$plat" in + lemans|monaco) + if [ "$stack" = "downstream" ]; then + video_ensure_blacklist "qcom-iris" + video_ensure_blacklist "qcom_iris" + video_remove_blacklist "iris-vpu" + video_remove_blacklist "iris_vpu" + else + video_remove_blacklist "qcom-iris" + video_remove_blacklist "qcom_iris" + video_remove_blacklist "iris-vpu" + video_remove_blacklist "iris_vpu" + fi + ;; + kodiak) + if [ "$stack" = "downstream" ]; then + video_ensure_blacklist "venus-core" + video_ensure_blacklist "venus_core" + video_ensure_blacklist "venus-dec" + video_ensure_blacklist "venus_dec" + video_ensure_blacklist "venus-enc" + video_ensure_blacklist "venus_enc" + video_remove_blacklist "iris-vpu" + video_remove_blacklist "iris_vpu" + else + video_remove_blacklist "venus-core" + video_remove_blacklist "venus_core" + video_remove_blacklist "venus-dec" + video_remove_blacklist "venus_dec" + video_remove_blacklist "venus-enc" + video_remove_blacklist "venus_enc" + video_remove_blacklist "iris-vpu" + video_remove_blacklist "iris_vpu" + fi + ;; + *) + return 1 + ;; + esac + + depmod -a 2>/dev/null || true + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules 2>/dev/null || true + udevadm settle 2>/dev/null || true + fi + video_usleep "${MOD_SETTLE_SLEEP}" + + return 0 +} + +# ----------------------------------------------------------------------------- +# AUTO preference helper using persistent blacklists +# ----------------------------------------------------------------------------- +video_auto_preference_from_blacklist() { + plat="$1" + + case "$plat" in + lemans|monaco) + if video_is_blacklisted "qcom-iris" || video_is_blacklisted "qcom_iris"; then + printf '%s\n' "downstream" + return 0 + fi + ;; + kodiak) + if video_is_blacklisted "venus-core" || video_is_blacklisted "venus_core" \ + || video_is_blacklisted "venus-dec" || video_is_blacklisted "venus_dec" \ + || video_is_blacklisted "venus-enc" || video_is_blacklisted "venus_enc"; then + printf '%s\n' "downstream" + return 0 + fi + ;; + esac + + printf '%s\n' "unknown" + return 0 +}