#!/usr/bin/env bash
set -u
DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
RT=$(cd "$DIR/.." && pwd)
BIN="${BIN_DIR:-$RT/build/bin}"
SERVER="$BIN/sensevoice-server"
GOLDEN="$DIR/golden/sensevoice.txt"
SAMPLE="$DIR/sample.wav"
HOST=127.0.0.1
PORT="${PORT:-8041}"
MODEL="${MODEL_GGUF:-}"
if [ -z "$MODEL" ]; then
for c in "$RT/../../model/sensevoice-small-q8.gguf" \
"$RT/../model/sensevoice-small-q8.gguf" \
"$DIR/models/sensevoice-small-q8.gguf" \
"$DIR/models/sensevoice-small-f16.gguf"; do
[ -f "$c" ] && { MODEL="$c"; break; }
done
fi
VAD="${VAD_GGUF:-$DIR/models/fsmn-vad.gguf}"
[ -f "$VAD" ] || VAD="$RT/../../model/fsmn-vad.gguf"
[ -x "$SERVER" ] || { echo " FAIL server-smoke (no binary: $SERVER)"; exit 1; }
[ -f "$MODEL" ] || { echo " SKIP server-smoke (model missing: set MODEL_GGUF)"; exit 0; }
[ -f "$VAD" ] || { echo " SKIP server-smoke (vad missing: $VAD)"; exit 0; }
[ -f "$GOLDEN" ] || { echo " SKIP server-smoke (no golden)"; exit 0; }
"$SERVER" -m "$MODEL" -vad "$VAD" --max-connections 4 --max-audio-seconds 10 \
"$HOST" "$PORT" > "$DIR/server.log" 2>&1 &
SPID=$!
trap 'kill "$SPID" 2>/dev/null; wait "$SPID" 2>/dev/null' EXIT
up=0
for _ in $(seq 1 100); do
if curl -s -o /dev/null "http://$HOST:$PORT/health"; then up=1; break; fi
sleep 0.1
done
if [ "$up" = 0 ]; then
echo " FAIL server-smoke (server did not start; see tests/server.log)"; exit 1
fi
pass=0; fail=0
check(){ local name="$1" expected="$2" got="$3"
if [ "$got" = "$expected" ]; then echo " PASS $name"; pass=$((pass+1))
else echo " FAIL $name"; echo " expected: $expected"; echo " got: $got"; fail=$((fail+1)); fi
}
EXP=$(tr -d '\n' < "$GOLDEN")
norm(){
printf '%s' "$1" | python3 -c "import sys,re;p=re.compile(r'^[.\s\u3002\uff01\uff1f\uff0c\u3001\u300b\u300a\u201d\u201c]+|[.\s\u3002\uff01\uff1f\uff0c\u3001\u300b\u300a\u201d\u201c]+$');sys.stdout.write(p.sub('',sys.stdin.read()))"
}
textof(){
printf '%s' "$1" | python3 -c "import sys,json;sys.stdout.write(json.load(sys.stdin).get('text',''))"
}
GOT=$(curl -s -F file=@"$SAMPLE" "http://$HOST:$PORT/v1/audio/transcriptions")
check "server rest-json" "$(norm "$EXP")" "$(norm "$(textof "$GOT")")"
GOT=$(curl -s -F file=@"$SAMPLE" -F response_format=text "http://$HOST:$PORT/v1/audio/transcriptions")
check "server rest-text" "$(norm "$EXP")" "$(norm "$GOT")"
GOT=$(curl -s -F file=@"$SAMPLE" -F response_format=verbose_json "http://$HOST:$PORT/v1/audio/transcriptions")
echo "$GOT" | grep -q "segments" && echo " PASS server rest-verbose_json" && pass=$((pass+1)) \
|| { echo " FAIL server rest-verbose_json"; echo " got: $GOT"; fail=$((fail+1)); }
STATUS=$(curl -s -o /dev/null -w '%{http_code}' -F file=@"$SAMPLE" -F response_format=invalid \
"http://$HOST:$PORT/v1/audio/transcriptions")
check "server rejects response-format" "400" "$STATUS"
LONG_WAV=$(mktemp "${TMPDIR:-/tmp}/sensevoice-long.XXXXXX.wav")
python3 - "$LONG_WAV" <<'PY'
import sys
import wave
with wave.open(sys.argv[1], "wb") as wav:
wav.setnchannels(1)
wav.setsampwidth(2)
wav.setframerate(16000)
wav.writeframes(b"\0\0" * 16000 * 11)
PY
STATUS=$(curl -s -o /dev/null -w '%{http_code}' -F file=@"$LONG_WAV" \
"http://$HOST:$PORT/v1/audio/transcriptions")
rm -f "$LONG_WAV"
check "server rest-audio-limit" "413" "$STATUS"
GOT=$(curl -s -N -F file=@"$SAMPLE" -F stream=true "http://$HOST:$PORT/v1/audio/transcriptions")
echo "$GOT" | grep -q "transcript.text.done" && echo " PASS server rest-sse" && pass=$((pass+1)) \
|| { echo " FAIL server rest-sse"; echo " got: $GOT"; fail=$((fail+1)); }
GOT=$(curl -s -F file=@"$SAMPLE" -F response_format=srt "http://$HOST:$PORT/v1/audio/transcriptions")
if echo "$GOT" | grep -qx '1' && echo "$GOT" | grep -Eq '^[0-9]{2}:[0-9]{2}:[0-9]{2},[0-9]{3} --> [0-9]{2}:[0-9]{2}:[0-9]{2},[0-9]{3}$'; then
echo " PASS server rest-srt"; pass=$((pass+1))
else
echo " FAIL server rest-srt"; echo " got: $GOT"; fail=$((fail+1))
fi
GOT=$(curl -s -F file=@"$SAMPLE" -F response_format=vtt "http://$HOST:$PORT/v1/audio/transcriptions")
if echo "$GOT" | grep -qx 'WEBVTT' && echo "$GOT" | grep -Eq '^[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3} --> [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}$'; then
echo " PASS server rest-vtt"; pass=$((pass+1))
else
echo " FAIL server rest-vtt"; echo " got: $GOT"; fail=$((fail+1))
fi
if PYTHONPATH="$DIR" python3 - "$HOST" "$PORT" <<'PY'
import sys
import time
from stream_client import WSClient
ws = WSClient(sys.argv[1], int(sys.argv[2]), "/v1/realtime?intent=transcription")
ws.next_event(2.0)
ws.send_text('{"type":')
time.sleep(0.1)
ws.send_text('{"type":"input_audio_buffer.clear"}')
time.sleep(0.1)
ws.close()
PY
then
curl -fsS "http://$HOST:$PORT/health" >/dev/null \
&& { echo " PASS server malformed-ws-survives"; pass=$((pass+1)); } \
|| { echo " FAIL server malformed-ws-survives"; fail=$((fail+1)); }
else
echo " FAIL server malformed-ws-survives"; fail=$((fail+1))
fi
if PYTHONPATH="$DIR" python3 - "$HOST" "$PORT" <<'PY'
import sys
import time
from stream_client import OP_TEXT, WSClient
clients = [WSClient(sys.argv[1], int(sys.argv[2]), "/v1/realtime?intent=transcription") for _ in range(5)]
for client in clients[:4]:
event = client.next_event(2.0)
assert event and event[0] == OP_TEXT and b'"session.created"' in event[1]
event = clients[4].next_event(2.0)
assert event and event[0] == OP_TEXT and b'connection limit exceeded' in event[1]
for client in clients:
client.close()
time.sleep(0.2)
replacement = WSClient(sys.argv[1], int(sys.argv[2]), "/v1/realtime?intent=transcription")
event = replacement.next_event(2.0)
assert event and event[0] == OP_TEXT and b'"session.created"' in event[1]
replacement.close()
PY
then
echo " PASS server ws-connection-limit"; pass=$((pass+1))
else
echo " FAIL server ws-connection-limit"; fail=$((fail+1))
fi
if PYTHONPATH="$DIR" python3 - "$HOST" "$PORT" <<'PY'
import sys
from stream_client import OP_TEXT, WSClient
ws = WSClient(sys.argv[1], int(sys.argv[2]), "/v1/realtime?intent=transcription")
ws.next_event(2.0)
ws.send_binary(b"\0" * (16000 * 2 * 11))
event = ws.next_event(2.0)
assert event and event[0] == OP_TEXT and b'session audio limit exceeded' in event[1]
ws.close()
PY
then
echo " PASS server ws-session-audio-limit"; pass=$((pass+1))
else
echo " FAIL server ws-session-audio-limit"; fail=$((fail+1))
fi
GOT=$(python3 "$DIR/stream_client.py" "$HOST" "$PORT" "$SAMPLE" 200)
check "server ws-stream" "$(norm "$EXP")" "$(norm "$GOT")"
echo " server-smoke: $pass passed, $fail failed"
[ "$fail" = 0 ]