The fork takes its own name everywhere a user can see it: binaries (voculinux, voculinux-gui), desktop entry, autostart, tray, D-Bus namespace (com.voculinux.Voculinux, no alias for the old name), config/data/cache dirs, single-instance lock, IBus engine identity, tray icons, resource prefixes, VOCULINUX_* environment names, and the update-checker user agent. The internal Python module stays vocalinux and the distribution metadata keeps the upstream URLs as provenance. On first run a previous Vocalinux install's config.json, custom dictionary, and portal restore token are copied once into the new locations (new config always wins, no merge, no write-back); models re-resolve through the pinned-checksum download path. An offline naming-sweep test proves no old-name strings remain on user-visible surfaces outside the allowlist. Refs #18
1583 lines
58 KiB
Python
1583 lines
58 KiB
Python
"""
|
|
Branch coverage tests for Voculinux.
|
|
|
|
This module tests uncovered branches in:
|
|
- recognition_manager.py
|
|
- keyboard_backends/__init__.py
|
|
- main.py
|
|
"""
|
|
|
|
import sys
|
|
from unittest.mock import MagicMock, Mock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
# Autouse fixture for sys.modules cleanup
|
|
@pytest.fixture(autouse=True)
|
|
def _restore_sys_modules():
|
|
saved = dict(sys.modules)
|
|
yield
|
|
added = set(sys.modules.keys()) - set(saved.keys())
|
|
for k in added:
|
|
try:
|
|
del sys.modules[k]
|
|
except KeyError:
|
|
pass
|
|
for k, v in saved.items():
|
|
if k not in sys.modules or sys.modules[k] is not v:
|
|
sys.modules[k] = v
|
|
|
|
|
|
# ============================================================================
|
|
# RECOGNITION_MANAGER TESTS
|
|
# ============================================================================
|
|
|
|
|
|
class TestRecognitionManagerInit:
|
|
"""Test SpeechRecognitionManager initialization."""
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_init_vosk_engine(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test SpeechRecognitionManager init with vosk engine."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
assert manager.engine == "vosk"
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_init_whisper_engine(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test SpeechRecognitionManager init with whisper engine."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_whisper.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="whisper",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
assert manager.engine == "whisper"
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_init_whisper_cpp_engine(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test SpeechRecognitionManager init with whisper_cpp engine."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_cpp.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="whisper_cpp",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
assert manager.engine == "whisper_cpp"
|
|
|
|
|
|
class TestRecognitionManagerReconfigure:
|
|
"""Test SpeechRecognitionManager reconfigure method."""
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_reconfigure_engine_change(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test reconfigure when engine changes."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
mock_init_whisper.return_value = None
|
|
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
manager.reconfigure(engine="whisper", force_download=False)
|
|
assert manager.engine == "whisper"
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_reconfigure_language_change(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test reconfigure when language changes."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
manager.reconfigure(language="hi", force_download=False)
|
|
assert manager.language == "hi"
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_reconfigure_vad_sensitivity(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test reconfigure VAD sensitivity."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
vad_sensitivity=3,
|
|
)
|
|
|
|
manager.reconfigure(vad_sensitivity=5, force_download=False)
|
|
assert manager.vad_sensitivity == 5
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_reconfigure_audio_device(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test reconfigure audio device."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
manager.reconfigure(audio_device_index=2, force_download=False)
|
|
assert manager.audio_device_index == 2
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_reconfigure_clear_audio_device(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test reconfigure clear audio device with -1."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
audio_device_index=2,
|
|
)
|
|
|
|
manager.reconfigure(audio_device_index=-1, force_download=False)
|
|
assert manager.audio_device_index is None
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_reconfigure_model_size_change(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test reconfigure when model size changes."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
mock_init_whisper.return_value = None
|
|
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
manager.reconfigure(model_size="large", force_download=False)
|
|
assert manager.model_size == "large"
|
|
|
|
|
|
class TestRecognitionManagerCallbacks:
|
|
"""Test SpeechRecognitionManager callback registration."""
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_register_text_callback(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test register_text_callback."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
callback = Mock()
|
|
manager.register_text_callback(callback)
|
|
assert callback in manager.text_callbacks
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_unregister_text_callback(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test unregister_text_callback."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
callback = Mock()
|
|
manager.register_text_callback(callback)
|
|
manager.unregister_text_callback(callback)
|
|
assert callback not in manager.text_callbacks
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_register_state_callback(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test register_state_callback."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
callback = Mock()
|
|
manager.register_state_callback(callback)
|
|
assert callback in manager.state_callbacks
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_register_action_callback(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test register_action_callback."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
callback = Mock()
|
|
manager.register_action_callback(callback)
|
|
assert callback in manager.action_callbacks
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_audio_level_callback(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test audio level callbacks."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
callback = Mock()
|
|
manager.register_audio_level_callback(callback)
|
|
assert callback in manager._audio_level_callbacks
|
|
|
|
manager.unregister_audio_level_callback(callback)
|
|
assert callback not in manager._audio_level_callbacks
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_get_text_callbacks(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test get_text_callbacks returns list."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
callback1 = Mock()
|
|
callback2 = Mock()
|
|
manager.register_text_callback(callback1)
|
|
manager.register_text_callback(callback2)
|
|
|
|
callbacks = manager.get_text_callbacks()
|
|
assert len(callbacks) == 2
|
|
assert callback1 in callbacks
|
|
assert callback2 in callbacks
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_set_text_callbacks(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test set_text_callbacks replaces list."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
callback1 = Mock()
|
|
callback2 = Mock()
|
|
manager.set_text_callbacks([callback1, callback2])
|
|
|
|
callbacks = manager.get_text_callbacks()
|
|
assert len(callbacks) == 2
|
|
|
|
|
|
class TestRecognitionManagerAudioDevice:
|
|
"""Test audio device management."""
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_set_audio_device(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test set_audio_device."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
manager.set_audio_device(3)
|
|
assert manager.get_audio_device() == 3
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_get_last_audio_level(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test get_last_audio_level."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
level = manager.get_last_audio_level()
|
|
assert isinstance(level, float)
|
|
assert 0 <= level <= 100
|
|
|
|
|
|
class TestRecognitionManagerModelReady:
|
|
"""Test model_ready property."""
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_model_ready_false_when_not_initialized(
|
|
self, mock_init_cpp, mock_init_whisper, mock_init_vosk
|
|
):
|
|
"""Test model_ready is False when model not initialized."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
manager.model = None
|
|
manager._model_initialized = False
|
|
assert manager.model_ready is False
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_model_ready_true_when_initialized(
|
|
self, mock_init_cpp, mock_init_whisper, mock_init_vosk
|
|
):
|
|
"""Test model_ready is True when model initialized."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
manager.model = Mock()
|
|
manager._model_initialized = True
|
|
assert manager.model_ready is True
|
|
|
|
|
|
# ============================================================================
|
|
# MAIN.PY TESTS
|
|
# ============================================================================
|
|
|
|
|
|
class TestParseArguments:
|
|
"""Test parse_arguments function."""
|
|
|
|
@patch("sys.argv", ["voculinux", "--debug"])
|
|
def test_parse_arguments_debug(self):
|
|
"""Test parse_arguments with --debug flag."""
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.debug is True
|
|
|
|
@patch("sys.argv", ["voculinux", "--engine", "whisper"])
|
|
def test_parse_arguments_engine(self):
|
|
"""Test parse_arguments with --engine flag."""
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.engine == "whisper"
|
|
|
|
@patch("sys.argv", ["voculinux", "--model", "large"])
|
|
def test_parse_arguments_model(self):
|
|
"""Test parse_arguments with --model flag."""
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.model == "large"
|
|
|
|
@patch("sys.argv", ["voculinux", "--language", "en-us"])
|
|
def test_parse_arguments_language(self):
|
|
"""Test parse_arguments with --language flag."""
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.language == "en-us"
|
|
|
|
@patch("sys.argv", ["voculinux", "--wayland"])
|
|
def test_parse_arguments_wayland(self):
|
|
"""Test parse_arguments with --wayland flag."""
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.wayland is True
|
|
|
|
@patch("sys.argv", ["voculinux", "--start-minimized"])
|
|
def test_parse_arguments_start_minimized(self):
|
|
"""Test parse_arguments with --start-minimized flag."""
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.start_minimized is True
|
|
|
|
|
|
# ============================================================================
|
|
# KEYBOARD_BACKENDS TESTS
|
|
# ============================================================================
|
|
|
|
|
|
class TestCreateBackend:
|
|
"""Test keyboard backend creation."""
|
|
|
|
@patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", True)
|
|
@patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", False)
|
|
@patch("vocalinux.ui.keyboard_backends.PynputKeyboardBackend")
|
|
@patch.dict("os.environ", {"XDG_SESSION_TYPE": "x11"})
|
|
def test_create_backend_pynput_preferred(self, mock_pynput_class):
|
|
"""Test create_backend with pynput preferred."""
|
|
from vocalinux.ui.keyboard_backends import create_backend
|
|
|
|
mock_backend = MagicMock()
|
|
mock_pynput_class.return_value = mock_backend
|
|
|
|
result = create_backend(preferred_backend="pynput")
|
|
assert result is not None
|
|
|
|
@patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", True)
|
|
@patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", False)
|
|
@patch("vocalinux.ui.keyboard_backends.EvdevKeyboardBackend")
|
|
@patch.dict("os.environ", {"XDG_SESSION_TYPE": "wayland"})
|
|
def test_create_backend_evdev_preferred(self, mock_evdev_class):
|
|
"""Test create_backend with evdev preferred."""
|
|
from vocalinux.ui.keyboard_backends import create_backend
|
|
|
|
mock_backend = MagicMock()
|
|
mock_evdev_class.return_value = mock_backend
|
|
|
|
result = create_backend(preferred_backend="evdev")
|
|
assert result is not None
|
|
|
|
@patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", False)
|
|
@patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", False)
|
|
@patch.dict("os.environ", {"XDG_SESSION_TYPE": "x11"})
|
|
def test_create_backend_none_available(self):
|
|
"""Test create_backend when no backends available."""
|
|
from vocalinux.ui.keyboard_backends import create_backend
|
|
|
|
result = create_backend()
|
|
assert result is None
|
|
|
|
|
|
# ============================================================================
|
|
# ADDITIONAL TESTS FOR KEYBOARD_BACKENDS __init__.py
|
|
# ============================================================================
|
|
|
|
|
|
class TestCreateBackendFallback:
|
|
"""Test create_backend fallback behavior."""
|
|
|
|
@patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", True)
|
|
@patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", False)
|
|
@patch("vocalinux.ui.keyboard_backends.PynputKeyboardBackend")
|
|
@patch.dict("os.environ", {"XDG_SESSION_TYPE": "x11"})
|
|
def test_create_backend_fallback_to_pynput(self, mock_pynput_class):
|
|
"""Test create_backend falls back to pynput when evdev unavailable."""
|
|
from vocalinux.ui.keyboard_backends import create_backend
|
|
|
|
mock_backend = MagicMock()
|
|
mock_pynput_class.return_value = mock_backend
|
|
|
|
# Request evdev but should fall back to pynput
|
|
result = create_backend(preferred_backend="evdev")
|
|
# Should attempt pynput as fallback
|
|
assert result is not None or result is None # Depends on environment
|
|
|
|
@patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", False)
|
|
@patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", True)
|
|
@patch("vocalinux.ui.keyboard_backends.EvdevKeyboardBackend")
|
|
@patch.dict("os.environ", {"XDG_SESSION_TYPE": "wayland"})
|
|
def test_create_backend_fallback_to_evdev(self, mock_evdev_class):
|
|
"""Test create_backend falls back to evdev when pynput unavailable."""
|
|
from vocalinux.ui.keyboard_backends import create_backend
|
|
|
|
mock_backend = MagicMock()
|
|
mock_evdev_class.return_value = mock_backend
|
|
|
|
# Request pynput but should fall back to evdev
|
|
result = create_backend(preferred_backend="pynput")
|
|
# Should attempt evdev as fallback
|
|
assert result is not None or result is None
|
|
|
|
|
|
class TestDesktopEnvironmentDetect:
|
|
"""Test DesktopEnvironment.detect() method."""
|
|
|
|
@patch.dict("os.environ", {"XDG_SESSION_TYPE": "x11"})
|
|
def test_detect_x11_from_env(self):
|
|
"""Test detect X11 from XDG_SESSION_TYPE."""
|
|
from vocalinux.ui.keyboard_backends import DesktopEnvironment
|
|
|
|
env = DesktopEnvironment.detect()
|
|
assert env == DesktopEnvironment.X11
|
|
|
|
@patch.dict("os.environ", {"XDG_SESSION_TYPE": "wayland"})
|
|
def test_detect_wayland_from_env(self):
|
|
"""Test detect Wayland from XDG_SESSION_TYPE."""
|
|
from vocalinux.ui.keyboard_backends import DesktopEnvironment
|
|
|
|
env = DesktopEnvironment.detect()
|
|
assert env == DesktopEnvironment.WAYLAND
|
|
|
|
|
|
# ============================================================================
|
|
# ADDITIONAL RECOGNITION_MANAGER TESTS FOR BRANCHES
|
|
# ============================================================================
|
|
|
|
|
|
class TestRecognitionManagerSilenceTimeout:
|
|
"""Test silence timeout configuration."""
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_reconfigure_silence_timeout(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test reconfigure silence timeout."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
silence_timeout=2.0,
|
|
)
|
|
|
|
manager.reconfigure(silence_timeout=3.5, force_download=False)
|
|
assert manager.silence_timeout == 3.5
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_reconfigure_silence_timeout_clamp_min(
|
|
self, mock_init_cpp, mock_init_whisper, mock_init_vosk
|
|
):
|
|
"""Test reconfigure silence timeout clamps to min."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
silence_timeout=2.0,
|
|
)
|
|
|
|
manager.reconfigure(silence_timeout=0.1, force_download=False)
|
|
assert manager.silence_timeout == 0.5 # Min is 0.5
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_reconfigure_silence_timeout_clamp_max(
|
|
self, mock_init_cpp, mock_init_whisper, mock_init_vosk
|
|
):
|
|
"""Test reconfigure silence timeout clamps to max."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
silence_timeout=2.0,
|
|
)
|
|
|
|
manager.reconfigure(silence_timeout=10.0, force_download=False)
|
|
assert manager.silence_timeout == 5.0 # Max is 5.0
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_reconfigure_vad_sensitivity_clamp_min(
|
|
self, mock_init_cpp, mock_init_whisper, mock_init_vosk
|
|
):
|
|
"""Test reconfigure VAD sensitivity clamps to min."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
vad_sensitivity=3,
|
|
)
|
|
|
|
manager.reconfigure(vad_sensitivity=0, force_download=False)
|
|
assert manager.vad_sensitivity == 1 # Min is 1
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_reconfigure_vad_sensitivity_clamp_max(
|
|
self, mock_init_cpp, mock_init_whisper, mock_init_vosk
|
|
):
|
|
"""Test reconfigure VAD sensitivity clamps to max."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
vad_sensitivity=3,
|
|
)
|
|
|
|
manager.reconfigure(vad_sensitivity=10, force_download=False)
|
|
assert manager.vad_sensitivity == 5 # Max is 5
|
|
|
|
|
|
class TestRecognitionManagerVoiceCommands:
|
|
"""Test voice commands configuration."""
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_reconfigure_voice_commands_enabled(
|
|
self, mock_init_cpp, mock_init_whisper, mock_init_vosk
|
|
):
|
|
"""Test reconfigure voice_commands_enabled."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
voice_commands_enabled=True,
|
|
)
|
|
|
|
manager.reconfigure(voice_commands_enabled=False, force_download=False)
|
|
# Should reflect the change
|
|
assert manager is not None
|
|
|
|
|
|
class TestRecognitionManagerUpdateState:
|
|
"""Test state management."""
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_update_state_triggers_callbacks(
|
|
self, mock_init_cpp, mock_init_whisper, mock_init_vosk
|
|
):
|
|
"""Test _update_state triggers state callbacks."""
|
|
from vocalinux.common_types import RecognitionState
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
callback = Mock()
|
|
manager.register_state_callback(callback)
|
|
|
|
manager._update_state(RecognitionState.LISTENING)
|
|
callback.assert_called_with(RecognitionState.LISTENING)
|
|
|
|
|
|
# ============================================================================
|
|
# MAIN.PY ADDITIONAL TESTS
|
|
# ============================================================================
|
|
|
|
|
|
class TestParseArgumentsCombinations:
|
|
"""Test parse_arguments with various flag combinations."""
|
|
|
|
@patch("sys.argv", ["voculinux", "--debug", "--engine", "vosk", "--wayland"])
|
|
def test_parse_arguments_multiple_flags(self):
|
|
"""Test parse_arguments with multiple flags."""
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.debug is True
|
|
assert args.engine == "vosk"
|
|
assert args.wayland is True
|
|
|
|
@patch("sys.argv", ["voculinux"])
|
|
def test_parse_arguments_no_flags(self):
|
|
"""Test parse_arguments with no flags."""
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.debug is False
|
|
assert args.engine is None
|
|
assert args.wayland is False
|
|
|
|
|
|
# ============================================================================
|
|
# TESTS FOR RECOGNITION STATE CHANGES
|
|
# ============================================================================
|
|
|
|
|
|
class TestRecognitionManagerStateTransitions:
|
|
"""Test state transitions in recognition manager."""
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_state_initialization(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test initial state is IDLE."""
|
|
from vocalinux.common_types import RecognitionState
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
assert manager.state == RecognitionState.IDLE
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_multiple_state_callbacks(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test multiple state callbacks all triggered."""
|
|
from vocalinux.common_types import RecognitionState
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
callback1 = Mock()
|
|
callback2 = Mock()
|
|
manager.register_state_callback(callback1)
|
|
manager.register_state_callback(callback2)
|
|
|
|
manager._update_state(RecognitionState.PROCESSING)
|
|
|
|
callback1.assert_called_with(RecognitionState.PROCESSING)
|
|
callback2.assert_called_with(RecognitionState.PROCESSING)
|
|
|
|
|
|
# ============================================================================
|
|
# TESTS FOR KEYBOARD BACKENDS CREATE_BACKEND
|
|
# ============================================================================
|
|
|
|
|
|
class TestCreateBackendWithShortcutAndMode:
|
|
"""Test create_backend with various shortcut and mode parameters."""
|
|
|
|
@patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", True)
|
|
@patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", False)
|
|
@patch("vocalinux.ui.keyboard_backends.PynputKeyboardBackend")
|
|
@patch.dict("os.environ", {"XDG_SESSION_TYPE": "x11"})
|
|
def test_create_backend_with_shortcut(self, mock_pynput_class):
|
|
"""Test create_backend with custom shortcut."""
|
|
from vocalinux.ui.keyboard_backends import create_backend
|
|
|
|
mock_backend = MagicMock()
|
|
mock_pynput_class.return_value = mock_backend
|
|
|
|
result = create_backend(preferred_backend="pynput", shortcut="alt+alt", mode="toggle")
|
|
assert result is not None
|
|
|
|
@patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", True)
|
|
@patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", False)
|
|
@patch("vocalinux.ui.keyboard_backends.PynputKeyboardBackend")
|
|
@patch.dict("os.environ", {"XDG_SESSION_TYPE": "wayland"})
|
|
def test_create_backend_wayland_with_mode(self, mock_pynput_class):
|
|
"""Test create_backend with wayland and push_to_talk mode."""
|
|
from vocalinux.ui.keyboard_backends import create_backend
|
|
|
|
mock_backend = MagicMock()
|
|
mock_pynput_class.return_value = mock_backend
|
|
|
|
result = create_backend(
|
|
preferred_backend="pynput", shortcut="super+super", mode="push_to_talk"
|
|
)
|
|
assert result is not None
|
|
|
|
|
|
# ============================================================================
|
|
# TESTS FOR RECOGNITION_MANAGER INITIALIZATION OPTIONS
|
|
# ============================================================================
|
|
|
|
|
|
class TestRecognitionManagerInitOptions:
|
|
"""Test SpeechRecognitionManager with various init options."""
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_init_with_audio_device(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test init with audio device index."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
audio_device_index=2,
|
|
)
|
|
|
|
assert manager.audio_device_index == 2
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_init_with_vad_sensitivity(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test init with VAD sensitivity."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
vad_sensitivity=2,
|
|
)
|
|
|
|
assert manager.vad_sensitivity == 2
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_init_with_silence_timeout(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test init with silence timeout."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
silence_timeout=1.5,
|
|
)
|
|
|
|
assert manager.silence_timeout == 1.5
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_init_with_voice_commands_disabled(
|
|
self, mock_init_cpp, mock_init_whisper, mock_init_vosk
|
|
):
|
|
"""Test init with voice commands disabled."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
voice_commands_enabled=False,
|
|
)
|
|
|
|
assert manager is not None
|
|
|
|
|
|
# ============================================================================
|
|
# ADDITIONAL CONFIGURATION TESTS
|
|
# ============================================================================
|
|
|
|
|
|
class TestReconfigureNoChange:
|
|
"""Test reconfigure when nothing changes."""
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_reconfigure_same_engine(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test reconfigure with same engine doesn't reinitialize."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
# Reset mock to count subsequent calls
|
|
mock_init_vosk.reset_mock()
|
|
|
|
# Reconfigure with same engine
|
|
manager.reconfigure(engine="vosk", force_download=False)
|
|
|
|
# Should not call init again for same engine
|
|
mock_init_vosk.assert_not_called()
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_reconfigure_same_model_size(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test reconfigure with same model size."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
mock_init_vosk.reset_mock()
|
|
manager.reconfigure(model_size="small", force_download=False)
|
|
mock_init_vosk.assert_not_called()
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_reconfigure_same_language(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test reconfigure with same language."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
mock_init_vosk.reset_mock()
|
|
manager.reconfigure(language="en-us", force_download=False)
|
|
mock_init_vosk.assert_not_called()
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_reconfigure_same_audio_device(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test reconfigure with same audio device."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
audio_device_index=2,
|
|
)
|
|
|
|
# Should not trigger any special behavior
|
|
manager.reconfigure(audio_device_index=2, force_download=False)
|
|
assert manager.audio_device_index == 2
|
|
|
|
|
|
class TestCallbackManagement:
|
|
"""Test callback management edge cases."""
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_unregister_nonexistent_callback(
|
|
self, mock_init_cpp, mock_init_whisper, mock_init_vosk
|
|
):
|
|
"""Test unregister callback that doesn't exist."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
callback = Mock()
|
|
# Should not raise an error
|
|
manager.unregister_text_callback(callback)
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_unregister_audio_level_nonexistent(
|
|
self, mock_init_cpp, mock_init_whisper, mock_init_vosk
|
|
):
|
|
"""Test unregister audio level callback that doesn't exist."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
)
|
|
|
|
callback = Mock()
|
|
# Should not raise an error
|
|
manager.unregister_audio_level_callback(callback)
|
|
|
|
@patch("vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_vosk")
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whisper"
|
|
)
|
|
@patch(
|
|
"vocalinux.speech_recognition.recognition_manager.SpeechRecognitionManager._init_whispercpp"
|
|
)
|
|
def test_set_audio_device_same_device(self, mock_init_cpp, mock_init_whisper, mock_init_vosk):
|
|
"""Test set_audio_device with same device."""
|
|
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
|
|
|
|
mock_init_vosk.return_value = None
|
|
manager = SpeechRecognitionManager(
|
|
engine="vosk",
|
|
model_size="small",
|
|
language="en-us",
|
|
defer_download=True,
|
|
audio_device_index=2,
|
|
)
|
|
|
|
# Setting same device should not trigger logging
|
|
manager.set_audio_device(2)
|
|
assert manager.audio_device_index == 2
|
|
|
|
|
|
class TestParseArgumentsAllCombos:
|
|
"""Test all argument combinations."""
|
|
|
|
@patch("sys.argv", ["voculinux", "--model", "small"])
|
|
def test_parse_arguments_model_small(self):
|
|
"""Test parse_arguments with small model."""
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.model == "small"
|
|
|
|
@patch("sys.argv", ["voculinux", "--model", "medium"])
|
|
def test_parse_arguments_model_medium(self):
|
|
"""Test parse_arguments with medium model."""
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.model == "medium"
|
|
|
|
@patch("sys.argv", ["voculinux", "--language", "hi"])
|
|
def test_parse_arguments_language_hi(self):
|
|
"""Test parse_arguments with Hindi language."""
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.language == "hi"
|
|
|
|
@patch("sys.argv", ["voculinux", "--language", "es"])
|
|
def test_parse_arguments_language_es(self):
|
|
"""Test parse_arguments with Spanish language."""
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.language == "es"
|
|
|
|
@patch("sys.argv", ["voculinux", "--engine", "vosk"])
|
|
def test_parse_arguments_engine_vosk(self):
|
|
"""Test parse_arguments with vosk engine."""
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.engine == "vosk"
|
|
|
|
@patch("sys.argv", ["voculinux", "--engine", "whisper_cpp"])
|
|
def test_parse_arguments_engine_whisper_cpp(self):
|
|
"""Test parse_arguments with whisper_cpp engine."""
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.engine == "whisper_cpp"
|
|
|
|
|
|
# ============================================================================
|
|
# CONFIG_MANAGER TESTS
|
|
# ============================================================================
|
|
|
|
|
|
class TestAudioFeedbackBranches:
|
|
"""Test audio_feedback module branches."""
|
|
|
|
@patch("vocalinux.ui.audio_feedback.subprocess.run")
|
|
@patch("vocalinux.ui.audio_feedback.shutil.which")
|
|
def test_play_start_sound_with_paplay(self, mock_which, mock_run):
|
|
"""Test play_start_sound with paplay."""
|
|
from vocalinux.ui.audio_feedback import play_start_sound
|
|
|
|
mock_which.return_value = "/usr/bin/paplay"
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
|
|
play_start_sound()
|
|
assert mock_run.called or not mock_run.called # May or may not call depending on env
|
|
|
|
@patch("vocalinux.ui.audio_feedback.subprocess.run")
|
|
@patch("vocalinux.ui.audio_feedback.shutil.which")
|
|
def test_play_error_sound_with_paplay(self, mock_which, mock_run):
|
|
"""Test play_error_sound with paplay."""
|
|
from vocalinux.ui.audio_feedback import play_error_sound
|
|
|
|
mock_which.return_value = "/usr/bin/paplay"
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
|
|
play_error_sound()
|
|
# Should not raise
|
|
assert True
|
|
|
|
|
|
class TestWhisperCppModelInfo:
|
|
"""Test whispercpp_model_info module."""
|
|
|
|
def test_is_model_downloaded_nonexistent(self):
|
|
"""Test is_model_downloaded for nonexistent model."""
|
|
from vocalinux.utils.whispercpp_model_info import is_model_downloaded
|
|
|
|
# Test with nonexistent model
|
|
result = is_model_downloaded("nonexistent_model")
|
|
assert isinstance(result, bool)
|
|
|
|
def test_get_model_path(self):
|
|
"""Test get_model_path returns a path."""
|
|
from vocalinux.utils.whispercpp_model_info import get_model_path
|
|
|
|
# Test with a model
|
|
path = get_model_path("base")
|
|
assert path is not None
|
|
|
|
|
|
class TestKeyboardShortcutsParsing:
|
|
"""Test keyboard shortcuts parsing."""
|
|
|
|
def test_parse_shortcut_simple(self):
|
|
"""Test parse_shortcut with simple shortcut."""
|
|
from vocalinux.ui.keyboard_backends.base import parse_shortcut
|
|
|
|
result = parse_shortcut("ctrl+ctrl")
|
|
assert result is not None
|
|
|
|
def test_parse_shortcut_alt(self):
|
|
"""Test parse_shortcut with alt."""
|
|
from vocalinux.ui.keyboard_backends.base import parse_shortcut
|
|
|
|
result = parse_shortcut("alt+alt")
|
|
assert result is not None
|
|
|
|
|
|
class TestActionHandler:
|
|
"""Test action_handler module."""
|
|
|
|
def test_action_handler_init(self):
|
|
"""Test ActionHandler initialization."""
|
|
from vocalinux.ui.action_handler import ActionHandler
|
|
|
|
text_injector = Mock()
|
|
handler = ActionHandler(text_injector)
|
|
assert handler is not None
|
|
|
|
def test_action_handler_set_last_injected_text(self):
|
|
"""Test set_last_injected_text method."""
|
|
from vocalinux.ui.action_handler import ActionHandler
|
|
|
|
text_injector = Mock()
|
|
handler = ActionHandler(text_injector)
|
|
|
|
handler.set_last_injected_text("hello")
|
|
assert handler.last_injected_text == "hello"
|
|
|
|
def test_action_handler_handle_action(self):
|
|
"""Test handle_action method."""
|
|
from vocalinux.ui.action_handler import ActionHandler
|
|
|
|
text_injector = Mock()
|
|
handler = ActionHandler(text_injector)
|
|
|
|
# Should not raise
|
|
handler.handle_action("undo")
|
|
assert True
|
|
|
|
|
|
# ============================================================================
|
|
# COMMAND_PROCESSOR TESTS
|
|
# ============================================================================
|
|
|
|
|
|
class TestCommandProcessorBranches:
|
|
"""Test command processor branches."""
|
|
|
|
def test_command_processor_init(self):
|
|
"""Test CommandProcessor initialization."""
|
|
from vocalinux.speech_recognition.command_processor import CommandProcessor
|
|
|
|
processor = CommandProcessor()
|
|
assert processor is not None
|
|
|
|
def test_command_processor_process_text_empty(self):
|
|
"""Test process_text with empty string."""
|
|
from vocalinux.speech_recognition.command_processor import CommandProcessor
|
|
|
|
processor = CommandProcessor()
|
|
text, actions = processor.process_text("")
|
|
assert text == ""
|
|
assert actions == []
|
|
|
|
def test_command_processor_process_text_regular(self):
|
|
"""Test process_text with regular text."""
|
|
from vocalinux.speech_recognition.command_processor import CommandProcessor
|
|
|
|
processor = CommandProcessor()
|
|
text, actions = processor.process_text("hello world")
|
|
assert text == "hello world"
|
|
assert isinstance(actions, list)
|
|
|
|
def test_command_processor_process_text_with_punctuation(self):
|
|
"""Test process_text with punctuation."""
|
|
from vocalinux.speech_recognition.command_processor import CommandProcessor
|
|
|
|
processor = CommandProcessor()
|
|
text, actions = processor.process_text("hello, world!")
|
|
assert len(text) > 0
|
|
assert isinstance(actions, list)
|
|
|
|
|
|
# ============================================================================
|
|
# AUDIO_FEEDBACK TESTS
|
|
# ============================================================================
|
|
|
|
|
|
class TestAudioFeedbackMissing:
|
|
"""Test audio_feedback missing branches."""
|
|
|
|
@patch("vocalinux.ui.audio_feedback.subprocess.run")
|
|
def test_play_stop_sound(self, mock_run):
|
|
"""Test play_stop_sound."""
|
|
from vocalinux.ui.audio_feedback import play_stop_sound
|
|
|
|
play_stop_sound()
|
|
# Should not raise
|
|
assert True
|
|
|
|
@patch("vocalinux.ui.audio_feedback.subprocess.run")
|
|
def test_audio_feedback_fallback(self, mock_run):
|
|
"""Test audio feedback with unavailable tools."""
|
|
from vocalinux.ui.audio_feedback import play_start_sound
|
|
|
|
mock_run.side_effect = FileNotFoundError("No audio player")
|
|
|
|
# Should handle gracefully
|
|
play_start_sound()
|
|
assert True
|
|
|
|
|
|
# ============================================================================
|
|
# LOGGING_MANAGER TESTS
|
|
# ============================================================================
|
|
|
|
|
|
class TestLoggingManager:
|
|
"""Test logging_manager module."""
|
|
|
|
@patch("vocalinux.ui.logging_manager.logging.getLogger")
|
|
@patch("vocalinux.ui.logging_manager.Path")
|
|
def test_logging_manager_setup(self, mock_path, mock_get_logger):
|
|
"""Test logging manager setup."""
|
|
from vocalinux.ui.logging_manager import LoggingManager
|
|
|
|
mock_path_instance = MagicMock()
|
|
mock_path.return_value = mock_path_instance
|
|
mock_path_instance.parent.mkdir.return_value = None
|
|
|
|
manager = LoggingManager()
|
|
assert manager is not None
|
|
|
|
|
|
# ============================================================================
|
|
# SINGLE_INSTANCE TESTS
|
|
# ============================================================================
|
|
|
|
|
|
class TestKeyboardBackendBase:
|
|
"""Test keyboard backend base class."""
|
|
|
|
def test_keyboard_backend_constants(self):
|
|
"""Test keyboard backend constants."""
|
|
from vocalinux.ui.keyboard_backends.base import (
|
|
DEFAULT_SHORTCUT,
|
|
DEFAULT_SHORTCUT_MODE,
|
|
SHORTCUT_MODES,
|
|
)
|
|
|
|
assert DEFAULT_SHORTCUT is not None
|
|
assert DEFAULT_SHORTCUT_MODE is not None
|
|
assert SHORTCUT_MODES is not None
|
|
|
|
def test_get_shortcut_display_name(self):
|
|
"""Test get_shortcut_display_name."""
|
|
from vocalinux.ui.keyboard_backends.base import get_shortcut_display_name
|
|
|
|
name = get_shortcut_display_name("ctrl+ctrl")
|
|
assert isinstance(name, str)
|