* fix(perf): reduce startup and inference overhead * fix(perf): report effective whisper.cpp fallback settings * fix(injection): add opt-in X11 paste for long dictations * perf(vad): lower default silence timeout to 1.2s * fix(types): resolve mypy errors across application source * ci: run mypy in the lint job * ci: include mypy in lint dependency group * fix(types): narrow evdev timestamp comparison * refactor(recognition): add SpeechEngine protocol and engine dispatch table * refactor(settings): move About section and engine tables out of settings_dialog * docs: add ARCHITECTURE.md and refresh docs index and AGENTS layout * fix(settings): preserve GTK dialog compatibility after extraction * style: format touched Python modules * fix(types): keep local typecheck from syncing dependencies * test(vad): align main startup expectation with new default * style: sort engine registry and protocol test imports * test(settings): follow extracted About builder attribute alias * test(settings): isolate GTK dialog base in About test * test(settings): isolate About color scheme build test * test(settings): restore GTK base after About scheme test * test(settings): isolate About scheme check from GTK mocks * test(settings): support extracted dialog mixin methods * docs: add before/after performance benchmark charts * fix(injection): type instead of pasting when the clipboard holds non-text data
44 lines
1.8 KiB
Python
44 lines
1.8 KiB
Python
"""Tests for optional speech-engine availability checks."""
|
|
|
|
import sys
|
|
import unittest
|
|
from importlib.machinery import ModuleSpec
|
|
from unittest.mock import patch
|
|
|
|
from vocalinux.speech_recognition import engines
|
|
|
|
|
|
class TestEngineAvailability(unittest.TestCase):
|
|
def test_module_specs_control_availability_without_importing(self) -> None:
|
|
module_names = ("vosk", "whisper", "faster_whisper")
|
|
for available_modules in (set(module_names), set()):
|
|
with self.subTest(available_modules=available_modules):
|
|
with patch.dict(sys.modules):
|
|
for module_name in module_names:
|
|
sys.modules.pop(module_name, None)
|
|
|
|
def find_spec(name: str) -> ModuleSpec | None:
|
|
if name in available_modules:
|
|
return ModuleSpec(name, loader=None)
|
|
return None
|
|
|
|
with patch.object(
|
|
engines.importlib.util,
|
|
"find_spec",
|
|
side_effect=find_spec,
|
|
):
|
|
available = engines.available_engines()
|
|
|
|
for module_name in module_names:
|
|
self.assertEqual(
|
|
module_name in available,
|
|
module_name in available_modules,
|
|
)
|
|
self.assertNotIn(module_name, sys.modules)
|
|
|
|
def test_module_availability_handles_spec_errors(self) -> None:
|
|
for error in (ImportError, ValueError):
|
|
with self.subTest(error=error):
|
|
with patch.object(engines.importlib.util, "find_spec", side_effect=error):
|
|
self.assertFalse(engines._module_available("missing"))
|