1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
#!/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" </dev/null >"${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"
|