Experimental: add speech selector to startup.
This commit is contained in:
1 parent
7ee829d56c
commit
a8d4fc1fc1
10 files changed
+898
-10
No files matched your search
Executable
+184
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repoRoot="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
selector="$repoRoot/pi4/files/usr/local/lib/stormux/select-speech-module"
|
||||
tempDir="$(mktemp -d)"
|
||||
mockBin="$tempDir/bin"
|
||||
testCount=0
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$tempDir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
mkdir -p "$mockBin" "$tempDir/runtime"
|
||||
|
||||
cat > "$mockBin/sudo" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
if [[ "${1:-}" == "-A" ]]; then
|
||||
shift
|
||||
fi
|
||||
if [[ "${MOCK_SUDO_FAIL_WRITE:-no}" == "yes" && "$*" == *"--write-selection"* ]]; then
|
||||
exit 1
|
||||
fi
|
||||
exec "$@"
|
||||
EOF
|
||||
|
||||
cat > "$mockBin/spd-say" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
printf '%s\n' "$*" >> "$MOCK_LOG"
|
||||
if [[ "${1:-}" == "-O" ]]; then
|
||||
cat "$MOCK_MODULES"
|
||||
exit "${MOCK_DISCOVERY_STATUS:-0}"
|
||||
fi
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
cat > "$mockBin/killall" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
printf 'killall %s\n' "$*" >> "$MOCK_LOG"
|
||||
if [[ "${MOCK_KILLALL_FAIL:-no}" == "yes" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
EOF
|
||||
|
||||
cat > "$mockBin/systemctl" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
printf 'systemctl %s\n' "$*" >> "$MOCK_LOG"
|
||||
if [[ "${1:-}" == "is-active" ]]; then
|
||||
[[ "${MOCK_FENRIR_ACTIVE:-no}" == "yes" ]]
|
||||
exit
|
||||
fi
|
||||
exit 0
|
||||
EOF
|
||||
chmod 755 "$mockBin"/*
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
assert_equals() {
|
||||
local expected="$1"
|
||||
local actual="$2"
|
||||
local description="$3"
|
||||
|
||||
[[ "$actual" == "$expected" ]] || fail "$description (expected '$expected', got '$actual')"
|
||||
}
|
||||
|
||||
assert_file_contains() {
|
||||
local filePath="$1"
|
||||
local pattern="$2"
|
||||
local description="$3"
|
||||
|
||||
grep -qE -- "$pattern" "$filePath" || fail "$description"
|
||||
}
|
||||
|
||||
assert_file_absent() {
|
||||
local filePath="$1"
|
||||
local description="$2"
|
||||
|
||||
[[ ! -e "$filePath" ]] || fail "$description"
|
||||
}
|
||||
|
||||
reset_case() {
|
||||
caseDir="$tempDir/case-$testCount"
|
||||
testCount=$((testCount + 1))
|
||||
mkdir -p "$caseDir/runtime"
|
||||
configFile="$caseDir/speechd.conf"
|
||||
selectionFile="$caseDir/selected"
|
||||
modulesFile="$caseDir/modules"
|
||||
mockLog="$caseDir/mock.log"
|
||||
: > "$mockLog"
|
||||
printf 'OUTPUT MODULES\nrhvoice\nopenevv\nespeak-ng\n' > "$modulesFile"
|
||||
printf '# Speech Dispatcher\nDefaultModule rhvoice\n' > "$configFile"
|
||||
}
|
||||
|
||||
run_selector() {
|
||||
local input="$1"
|
||||
|
||||
printf '%b' "$input" | env \
|
||||
PATH="$mockBin:$PATH" \
|
||||
MOCK_LOG="$mockLog" \
|
||||
MOCK_MODULES="$modulesFile" \
|
||||
MOCK_DISCOVERY_STATUS="${MOCK_DISCOVERY_STATUS:-0}" \
|
||||
MOCK_KILLALL_FAIL="${MOCK_KILLALL_FAIL:-no}" \
|
||||
MOCK_FENRIR_ACTIVE="${MOCK_FENRIR_ACTIVE:-no}" \
|
||||
MOCK_SUDO_FAIL_WRITE="${MOCK_SUDO_FAIL_WRITE:-no}" \
|
||||
STORMUX_SPEECH_CONFIG="$configFile" \
|
||||
STORMUX_RUNTIME_DIR="$caseDir/runtime" \
|
||||
STORMUX_SPD_SAY=spd-say \
|
||||
STORMUX_KILLALL=killall \
|
||||
STORMUX_SYSTEMCTL=systemctl \
|
||||
"$selector" --selection-file "$selectionFile" >/dev/null
|
||||
}
|
||||
|
||||
# Enter accepts the configured initial module and collapses duplicate directives.
|
||||
reset_case
|
||||
printf 'DefaultModule rhvoice\n# DefaultModule espeak-ng\nDefaultModule openevv\n' > "$configFile"
|
||||
MOCK_FENRIR_ACTIVE=yes run_selector '\n'
|
||||
assert_equals 1 "$(grep -c '^DefaultModule ' "$configFile")" "initial selection should leave one active default"
|
||||
assert_file_contains "$configFile" '^DefaultModule rhvoice$' "initial selection should retain rhvoice"
|
||||
assert_equals rhvoice "$(< "$selectionFile")" "initial selection should be recorded"
|
||||
assert_file_contains "$mockLog" '^systemctl restart fenrirscreenreader\.service$' "active Fenrir should restart"
|
||||
|
||||
# Down previews and selects the next module.
|
||||
reset_case
|
||||
run_selector 'x\033[B\n'
|
||||
assert_file_contains "$mockLog" '^-o openevv This is openevv\.' "Down should preview OpenEVV"
|
||||
assert_file_contains "$configFile" '^DefaultModule openevv$' "Down then Enter should select OpenEVV"
|
||||
|
||||
# Up wraps from the first module to the last.
|
||||
reset_case
|
||||
run_selector '\033[A\n'
|
||||
assert_file_contains "$mockLog" '^-o espeak-ng This is espeak-ng\.' "Up should wrap and preview eSpeak NG"
|
||||
assert_file_contains "$configFile" '^DefaultModule espeak-ng$' "wrapped module should persist"
|
||||
|
||||
# Escape retains the prior file and does not create an installer selection.
|
||||
reset_case
|
||||
cp "$configFile" "$caseDir/original.conf"
|
||||
run_selector '\033'
|
||||
cmp -s "$configFile" "$caseDir/original.conf" || fail "Escape should retain the configuration"
|
||||
assert_file_absent "$selectionFile" "Escape should not record an installer selection"
|
||||
|
||||
# Empty discovery fails safely.
|
||||
reset_case
|
||||
printf 'OUTPUT MODULES\n' > "$modulesFile"
|
||||
cp "$configFile" "$caseDir/original.conf"
|
||||
run_selector '\n'
|
||||
cmp -s "$configFile" "$caseDir/original.conf" || fail "empty discovery should retain the configuration"
|
||||
assert_file_absent "$selectionFile" "empty discovery should not record a selection"
|
||||
|
||||
# Unsafe names are rejected rather than passed to spd-say or configuration.
|
||||
reset_case
|
||||
printf 'OUTPUT MODULES\nbad name\n;command\nopenevv\n' > "$modulesFile"
|
||||
run_selector '\n'
|
||||
assert_file_contains "$configFile" '^DefaultModule openevv$' "the remaining safe module should be selected"
|
||||
if grep -qE 'bad name|;command' "$mockLog"; then
|
||||
fail "unsafe module names should not reach spd-say"
|
||||
fi
|
||||
|
||||
# A missing configuration simulates failed persistence and remains nonfatal.
|
||||
reset_case
|
||||
rm -f "$configFile"
|
||||
run_selector '\n'
|
||||
assert_file_absent "$selectionFile" "failed persistence should not record a selection"
|
||||
assert_file_contains "$mockLog" 'selection failed' "failed persistence should be announced"
|
||||
|
||||
# A restart failure restores the byte-for-byte original configuration.
|
||||
reset_case
|
||||
cp "$configFile" "$caseDir/original.conf"
|
||||
MOCK_KILLALL_FAIL=yes run_selector '\033[B\n'
|
||||
cmp -s "$configFile" "$caseDir/original.conf" || fail "restart failure should roll back the configuration"
|
||||
assert_file_absent "$selectionFile" "restart failure should not record a selection"
|
||||
|
||||
# Failure to record the live choice also rolls the system configuration back.
|
||||
reset_case
|
||||
cp "$configFile" "$caseDir/original.conf"
|
||||
MOCK_SUDO_FAIL_WRITE=yes run_selector '\033[B\n'
|
||||
cmp -s "$configFile" "$caseDir/original.conf" || fail "selection-file failure should roll back the configuration"
|
||||
assert_file_absent "$selectionFile" "selection-file failure should leave no selection"
|
||||
|
||||
printf 'PASS: %s speech selector cases\n' "$testCount"
|
||||
Reference in new issue
Block a user