Import the vocalinux source tree (tray app, Next.js site in web/, packaging, installers, docs and tests) on top of the Gitea template commit. The project README replaces the template README, and the template CI files (.gitea/workflows/ci.yml, .jscpd.json, CI.md) are kept.
164 lines
5.5 KiB
Python
164 lines
5.5 KiB
Python
"""Final coverage tests targeting the last few uncovered lines."""
|
|
|
|
import sys
|
|
import unittest
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
@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:
|
|
del sys.modules[k]
|
|
for k, v in saved.items():
|
|
if k not in sys.modules or sys.modules[k] is not v:
|
|
sys.modules[k] = v
|
|
|
|
|
|
class TestCommandProcessorEdgeCases(unittest.TestCase):
|
|
"""Cover edge case lines in command_processor.py."""
|
|
|
|
def test_capitalize_command(self):
|
|
from vocalinux.speech_recognition.command_processor import CommandProcessor
|
|
|
|
cp = CommandProcessor()
|
|
result = cp.process_text("capitalize")
|
|
self.assertIsInstance(result, tuple)
|
|
|
|
def test_format_with_no_target(self):
|
|
from vocalinux.speech_recognition.command_processor import CommandProcessor
|
|
|
|
cp = CommandProcessor()
|
|
result = cp.process_text("format with no target word")
|
|
self.assertIsInstance(result, tuple)
|
|
|
|
def test_multiple_format_modifiers(self):
|
|
from vocalinux.speech_recognition.command_processor import CommandProcessor
|
|
|
|
cp = CommandProcessor()
|
|
result = cp.process_text("multiple format modifiers")
|
|
self.assertIsInstance(result, tuple)
|
|
|
|
|
|
class TestResourceManagerFallback(unittest.TestCase):
|
|
"""Cover resource_manager.py fallback lines."""
|
|
|
|
def test_resources_dir_fallback(self):
|
|
from vocalinux.utils.resource_manager import ResourceManager
|
|
|
|
rm = ResourceManager()
|
|
# Just access the property - it should return a valid path
|
|
path = rm.resources_dir
|
|
self.assertIsInstance(path, str)
|
|
|
|
|
|
class TestKeyboardBackendImportFallbacks(unittest.TestCase):
|
|
"""Cover import fallback lines in keyboard_backends/__init__.py."""
|
|
|
|
def test_evdev_import_failure(self):
|
|
"""Test that EVDEV_AVAILABLE is False when evdev isn't installed."""
|
|
import importlib
|
|
|
|
saved = dict(sys.modules)
|
|
|
|
# Remove evdev modules to force ImportError
|
|
for key in list(sys.modules.keys()):
|
|
if "evdev" in key:
|
|
del sys.modules[key]
|
|
|
|
# Create a module that will raise ImportError on evdev import
|
|
mock_evdev = MagicMock()
|
|
mock_evdev.side_effect = ImportError("No evdev")
|
|
|
|
import vocalinux.ui.keyboard_backends as kb_mod
|
|
|
|
# The module already imported - we can check the constants
|
|
# EVDEV_AVAILABLE and PYNPUT_AVAILABLE should be booleans
|
|
self.assertIsInstance(kb_mod.EVDEV_AVAILABLE, bool)
|
|
self.assertIsInstance(kb_mod.PYNPUT_AVAILABLE, bool)
|
|
|
|
# Restore
|
|
for key, val in saved.items():
|
|
sys.modules[key] = val
|
|
|
|
def test_desktop_environment_detect_unknown(self):
|
|
"""Test DesktopEnvironment detection with unknown session."""
|
|
from vocalinux.ui.keyboard_backends import DesktopEnvironment
|
|
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "mir"}, clear=False):
|
|
de = DesktopEnvironment.detect()
|
|
self.assertIsNotNone(de)
|
|
|
|
def test_desktop_environment_detect_wayland(self):
|
|
from vocalinux.ui.keyboard_backends import DesktopEnvironment
|
|
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "wayland"}, clear=False):
|
|
de = DesktopEnvironment.detect()
|
|
self.assertEqual(de, "wayland")
|
|
|
|
def test_desktop_environment_detect_x11(self):
|
|
from vocalinux.ui.keyboard_backends import DesktopEnvironment
|
|
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "x11"}, clear=False):
|
|
de = DesktopEnvironment.detect()
|
|
self.assertEqual(de, "x11")
|
|
|
|
|
|
class TestKeyboardBaseAbstractMethods(unittest.TestCase):
|
|
"""Cover abstract method stubs in keyboard_backends/base.py."""
|
|
|
|
def test_abstract_start(self):
|
|
from vocalinux.ui.keyboard_backends.base import KeyboardBackend
|
|
|
|
class TestBackend(KeyboardBackend):
|
|
def start(self):
|
|
return super().start()
|
|
|
|
def stop(self):
|
|
return super().stop()
|
|
|
|
def is_available(self):
|
|
return super().is_available()
|
|
|
|
def get_permission_hint(self):
|
|
return super().get_permission_hint()
|
|
|
|
b = TestBackend.__new__(TestBackend)
|
|
# Call the abstract methods through super() - they just return pass/None
|
|
b.start()
|
|
b.stop()
|
|
b.is_available()
|
|
b.get_permission_hint()
|
|
|
|
|
|
class TestWhispercppModelInfoExtra(unittest.TestCase):
|
|
"""Cover a few more lines in whispercpp_model_info.py."""
|
|
|
|
def test_detect_compute_backend_returns_tuple(self):
|
|
from vocalinux.utils.whispercpp_model_info import detect_compute_backend
|
|
|
|
backend, name = detect_compute_backend()
|
|
self.assertIsInstance(backend, str)
|
|
self.assertIsInstance(name, str)
|
|
self.assertIn(backend, ["cuda", "rocm", "vulkan", "metal", "cpu"])
|
|
|
|
def test_detect_compute_backend_cpu_fallback(self):
|
|
from vocalinux.utils.whispercpp_model_info import detect_compute_backend
|
|
|
|
# When all GPU checks fail, should fall back to cpu
|
|
with patch("subprocess.run", side_effect=FileNotFoundError("not found")):
|
|
with patch("platform.system", return_value="Linux"):
|
|
with patch("shutil.which", return_value=None):
|
|
backend, name = detect_compute_backend()
|
|
self.assertIsInstance(backend, str)
|
|
|
|
|
|
import os # noqa: E402 - needed for os.environ patches
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|