test: increased cb test coverage
This commit is contained in:
committed by
Luijkx,S.O.H. (Storm)
parent
de2e56ffce
commit
7f7c658901
@@ -120,3 +120,83 @@ def test_mlx_recognizer():
|
||||
mlx_mock.transcribe.return_value = {"text": "Hi"}
|
||||
res = rec.recognize_speech(np.zeros(10))
|
||||
assert res == "Hi"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_transcription_loop_continues_after_error(mock_zmq_context):
|
||||
mock_sub = MagicMock()
|
||||
mock_sub.recv = AsyncMock()
|
||||
mock_zmq_context.instance.return_value.socket.return_value = mock_sub
|
||||
|
||||
fake_audio = np.zeros(16000, dtype=np.float32).tobytes()
|
||||
|
||||
mock_sub.recv.side_effect = [
|
||||
fake_audio, # first iteration → recognizer fails
|
||||
asyncio.CancelledError(), # second iteration → stop loop
|
||||
]
|
||||
|
||||
with patch.object(SpeechRecognizer, "best_type") as mock_best:
|
||||
mock_recognizer = MagicMock()
|
||||
mock_recognizer.recognize_speech.side_effect = RuntimeError("fail")
|
||||
mock_best.return_value = mock_recognizer
|
||||
|
||||
agent = TranscriptionAgent("tcp://in")
|
||||
agent._running = True # ← REQUIRED to enter the loop
|
||||
agent.send = AsyncMock() # should never be called
|
||||
agent.add_behavior = AsyncMock() # match other tests
|
||||
|
||||
await agent.setup()
|
||||
|
||||
try:
|
||||
await agent._transcribing_loop()
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
# recognizer failed, so we should never send anything
|
||||
agent.send.assert_not_called()
|
||||
|
||||
# recv must have been called twice (audio then CancelledError)
|
||||
assert mock_sub.recv.call_count == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_transcription_continue_branch_when_empty(mock_zmq_context):
|
||||
mock_sub = MagicMock()
|
||||
mock_sub.recv = AsyncMock()
|
||||
mock_zmq_context.instance.return_value.socket.return_value = mock_sub
|
||||
|
||||
# First recv → audio chunk
|
||||
# Second recv → Cancel loop → stop iteration
|
||||
fake_audio = np.zeros(16000, dtype=np.float32).tobytes()
|
||||
mock_sub.recv.side_effect = [fake_audio, asyncio.CancelledError()]
|
||||
|
||||
with patch.object(SpeechRecognizer, "best_type") as mock_best:
|
||||
mock_recognizer = MagicMock()
|
||||
mock_recognizer.recognize_speech.return_value = "" # <— triggers the continue branch
|
||||
mock_best.return_value = mock_recognizer
|
||||
|
||||
agent = TranscriptionAgent("tcp://in")
|
||||
|
||||
# Make loop runnable
|
||||
agent._running = True
|
||||
agent.send = AsyncMock()
|
||||
agent.add_behavior = AsyncMock()
|
||||
|
||||
await agent.setup()
|
||||
|
||||
# Execute loop manually
|
||||
try:
|
||||
await agent._transcribing_loop()
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
# → Because of "continue", NO sending should occur
|
||||
agent.send.assert_not_called()
|
||||
|
||||
# → Continue was hit, so we must have read exactly 2 times:
|
||||
# - first audio
|
||||
# - second CancelledError
|
||||
assert mock_sub.recv.call_count == 2
|
||||
|
||||
# → recognizer was called once (first iteration)
|
||||
assert mock_recognizer.recognize_speech.call_count == 1
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
import zmq
|
||||
|
||||
from control_backend.agents.perception.vad_agent import VADAgent
|
||||
|
||||
@@ -123,3 +124,44 @@ async def test_no_data(audio_out_socket, vad_agent):
|
||||
|
||||
audio_out_socket.send.assert_not_called()
|
||||
assert len(vad_agent.audio_buffer) == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_vad_model_load_failure_stops_agent(vad_agent):
|
||||
"""
|
||||
Test that if loading the VAD model raises an Exception, it is caught,
|
||||
the agent logs an exception, stops itself, and setup returns.
|
||||
"""
|
||||
# Patch torch.hub.load to raise an exception
|
||||
with patch(
|
||||
"control_backend.agents.perception.vad_agent.torch.hub.load",
|
||||
side_effect=Exception("model fail"),
|
||||
):
|
||||
# Patch stop to an AsyncMock so we can check it was awaited
|
||||
vad_agent.stop = AsyncMock()
|
||||
|
||||
result = await vad_agent.setup()
|
||||
|
||||
# Assert stop was called
|
||||
vad_agent.stop.assert_awaited_once()
|
||||
# Assert setup returned None
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_audio_out_bind_failure_sets_none_and_logs(vad_agent, caplog):
|
||||
"""
|
||||
Test that if binding the output socket raises ZMQBindError,
|
||||
audio_out_socket is set to None, None is returned, and an error is logged.
|
||||
"""
|
||||
mock_socket = MagicMock()
|
||||
mock_socket.bind_to_random_port.side_effect = zmq.ZMQBindError()
|
||||
with patch("control_backend.agents.perception.vad_agent.azmq.Context.instance") as mock_ctx:
|
||||
mock_ctx.return_value.socket.return_value = mock_socket
|
||||
|
||||
with caplog.at_level("ERROR"):
|
||||
port = vad_agent._connect_audio_out_socket()
|
||||
|
||||
assert port is None
|
||||
assert vad_agent.audio_out_socket is None
|
||||
assert caplog.text is not None
|
||||
|
||||
Reference in New Issue
Block a user