#!/usr/bin/env bash # test-wizard.sh — non-interactive smoke test for max25-lite-setup.sh. # # Drives the wizard through all three modem paths (serial, baycom, crdop) # using a mock `whiptail` on PATH that answers canned responses, then checks # the generated max25d.ini / crdop.ini / max25-bcpr.ini files are well formed. # No sudo, no hardware, no PTT — local ./local/ destination only. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" WIZARD="${ROOT}/scripts/max25-lite-setup.sh" WORKDIR="$(mktemp -d)" ERR=0 ok() { echo "PASS: $*"; } fail() { echo "FAIL: $*" >&2; ERR=1; } cleanup() { rm -rf "$WORKDIR"; } trap cleanup EXIT mock_bin="${WORKDIR}/bin" mkdir -p "$mock_bin" # Mock whiptail: reads canned answers from $WHIPTAIL_ANSWERS (newline list, one # per --menu/--radiolist/--inputbox/--yesno call, in call order). --msgbox is a # no-op (informational only). Mirrors the 3>&1 1>&2 2>&3 capture convention: # the "answer" goes to this script's stderr; exit 0 means OK/Yes. cat >"${mock_bin}/whiptail" <<'MOCK' #!/usr/bin/env bash mode="" for a in "$@"; do case "$a" in --msgbox) mode=msgbox ;; --yesno) mode=yesno ;; --inputbox) mode=inputbox ;; --menu) mode=menu ;; --radiolist) mode=radiolist ;; esac done [[ "$mode" == "msgbox" ]] && exit 0 answers_file="${WHIPTAIL_ANSWERS:?WHIPTAIL_ANSWERS not set}" answer="$(head -n1 "$answers_file")" tail -n +2 "$answers_file" > "${answers_file}.next" mv "${answers_file}.next" "$answers_file" if [[ "$mode" == "yesno" ]]; then [[ "$answer" == "yes" ]] && exit 0 || exit 1 fi echo "$answer" >&2 exit 0 MOCK chmod +x "${mock_bin}/whiptail" # Mock arecord/aplay: force the "no ALSA cards" branch deterministically, # independent of the host's actual sound hardware, so the CRDOP path always # takes the same code path in this smoke test. for tool in arecord aplay; do cat >"${mock_bin}/${tool}" <<'MOCK' #!/usr/bin/env bash exit 1 MOCK chmod +x "${mock_bin}/${tool}" done run_path() { local label="$1" answers="$2" ini_glob="$3" want_grep="$4" local answers_file="${WORKDIR}/answers-${label}" printf '%s\n' "$answers" > "$answers_file" rm -rf "${ROOT}/local/max25d.ini" "${ROOT}/local/crdop.ini" "${ROOT}/local/max25-bcpr.ini" if PATH="${mock_bin}:${PATH}" WHIPTAIL_ANSWERS="$answers_file" \ bash "$WIZARD" "${WORKDIR}/out-${label}.log" 2>&1; then if [[ -f "${ROOT}/local/max25d.ini" ]] && grep -q "$want_grep" "${ROOT}/local/max25d.ini"; then ok "wizard ${label} path: max25d.ini written (${want_grep})" else fail "wizard ${label} path: max25d.ini missing or unexpected content" cat "${WORKDIR}/out-${label}.log" >&2 fi else fail "wizard ${label} path: exited non-zero" cat "${WORKDIR}/out-${label}.log" >&2 fi rm -rf "${ROOT}/local/max25d.ini" "${ROOT}/local/crdop.ini" "${ROOT}/local/max25-bcpr.ini" } # serial: menu=serial, radiolist=/dev/ttyUSB0, callerid, callid, save-location=no (local) run_path "serial" "serial /dev/ttyUSB0 CB-0 QST no" "local/max25d.ini" "device = tnc2c" # baycom: menu=baycom, radiolist=/dev/ttyS0 (or whatever first port is), callerid, callid, no run_path "baycom" "baycom $(bash -c ' shopt -s nullglob ports=(/dev/ttyUSB* /dev/ttyACM* /dev/ttyS*) shopt -u nullglob echo "${ports[0]:-/dev/ttyUSB0}" ') CB-0 QST no" "local/max25d.ini" "device = max25e0" # crdop: menu=crdop, [radiolist ALSA device only if arecord/aplay found any — assume # none in CI so wizard shows an informational msgbox and uses hw:0,0 defaults], callerid, callid, no run_path "crdop" "crdop CB-0 QST no" "local/max25d.ini" "device = soft-crdop" if [[ "$ERR" -eq 0 ]]; then echo "OK: wizard non-interactive smoke (serial / baycom / crdop)" fi exit "$ERR"