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.
650 lines
24 KiB
Python
650 lines
24 KiB
Python
"""
|
|
Additional coverage tests for main.py module.
|
|
|
|
Tests for parse_args(), main(), and VocalinuxApp class (if present).
|
|
Covers normal startup, debug mode, version flag, keyboard interrupt, and exception handling.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import unittest
|
|
from contextlib import redirect_stdout
|
|
from io import StringIO
|
|
from unittest.mock import MagicMock, call, patch
|
|
|
|
import pytest
|
|
|
|
|
|
# Autouse fixture to prevent sys.modules pollution between tests
|
|
@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]
|
|
# Also drop the stale attribute from the parent package so that
|
|
# mock.patch and a later re-import resolve to the same fresh module
|
|
parent, _, child = k.rpartition(".")
|
|
if parent and parent in sys.modules:
|
|
try:
|
|
delattr(sys.modules[parent], child)
|
|
except AttributeError:
|
|
pass
|
|
for k, v in saved.items():
|
|
if k not in sys.modules or sys.modules[k] is not v:
|
|
sys.modules[k] = v
|
|
|
|
|
|
class TestParseArguments(unittest.TestCase):
|
|
"""Tests for parse_arguments() function."""
|
|
|
|
def test_parse_args_defaults(self):
|
|
"""Test parsing arguments with defaults."""
|
|
with patch.object(sys, "argv", ["vocalinux"]):
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.debug is False
|
|
assert args.model is None
|
|
assert args.language is None
|
|
assert args.engine is None
|
|
assert args.wayland is False
|
|
assert args.start_minimized is False
|
|
assert args.transcribe_file is None
|
|
|
|
def test_parse_args_debug_flag(self):
|
|
"""Test parsing with debug flag."""
|
|
with patch.object(sys, "argv", ["vocalinux", "--debug"]):
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.debug is True
|
|
|
|
def test_parse_args_version_flag(self):
|
|
"""Test that --version prints the package version and exits."""
|
|
from vocalinux.version import __version__
|
|
|
|
output = StringIO()
|
|
with patch.object(sys, "argv", ["vocalinux", "--version"]):
|
|
from vocalinux.main import parse_arguments
|
|
|
|
with redirect_stdout(output), self.assertRaises(SystemExit) as exit_context:
|
|
parse_arguments()
|
|
|
|
assert exit_context.exception.code == 0
|
|
assert output.getvalue() == f"vocalinux {__version__}\n"
|
|
|
|
def test_parse_args_model_argument(self):
|
|
"""Test parsing with model argument."""
|
|
with patch.object(sys, "argv", ["vocalinux", "--model", "small"]):
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.model == "small"
|
|
|
|
def test_parse_args_language_argument(self):
|
|
"""Test parsing with language argument."""
|
|
with patch.object(sys, "argv", ["vocalinux", "--language", "en-us"]):
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.language == "en-us"
|
|
|
|
def test_parse_args_engine_argument(self):
|
|
"""Test parsing with engine argument."""
|
|
with patch.object(sys, "argv", ["vocalinux", "--engine", "whisper_cpp"]):
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.engine == "whisper_cpp"
|
|
|
|
def test_parse_args_wayland_flag(self):
|
|
"""Test parsing with wayland flag."""
|
|
with patch.object(sys, "argv", ["vocalinux", "--wayland"]):
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.wayland is True
|
|
|
|
def test_parse_args_start_minimized_flag(self):
|
|
"""Test parsing with start-minimized flag."""
|
|
with patch.object(sys, "argv", ["vocalinux", "--start-minimized"]):
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.start_minimized is True
|
|
|
|
def test_parse_args_transcribe_file(self):
|
|
"""Test parsing with the headless --transcribe-file flag."""
|
|
with patch.object(sys, "argv", ["vocalinux", "--transcribe-file", "/tmp/a.wav"]):
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.transcribe_file == "/tmp/a.wav"
|
|
|
|
def test_run_file_transcription_prints_transcript(self):
|
|
"""The headless path prints formatted blocks and skips GTK entirely."""
|
|
from vocalinux.main import _run_file_transcription
|
|
from vocalinux.speech_recognition.diarization import TranscriptBlock
|
|
|
|
blocks = [TranscriptBlock(0.0, 1, "Hello there.")]
|
|
with patch(
|
|
"vocalinux.speech_recognition.diarization.transcribe_audio_file",
|
|
return_value=blocks,
|
|
):
|
|
buffer = StringIO()
|
|
with redirect_stdout(buffer):
|
|
assert _run_file_transcription("/tmp/a.wav") == 0
|
|
assert "Speaker 1: Hello there." in buffer.getvalue()
|
|
|
|
def test_run_file_transcription_reports_errors_on_stderr(self):
|
|
"""Decode/load failures exit non-zero with the error on stderr."""
|
|
from vocalinux.main import _run_file_transcription
|
|
|
|
stderr_buffer = StringIO()
|
|
with patch(
|
|
"vocalinux.speech_recognition.diarization.transcribe_audio_file",
|
|
side_effect=RuntimeError("ffmpeg missing"),
|
|
):
|
|
with patch.object(sys, "stderr", stderr_buffer):
|
|
assert _run_file_transcription("/tmp/a.wav") == 1
|
|
assert "ffmpeg missing" in stderr_buffer.getvalue()
|
|
|
|
def test_parse_args_multiple_arguments(self):
|
|
"""Test parsing with multiple arguments."""
|
|
with patch.object(
|
|
sys,
|
|
"argv",
|
|
[
|
|
"vocalinux",
|
|
"--debug",
|
|
"--model",
|
|
"large",
|
|
"--language",
|
|
"es",
|
|
"--engine",
|
|
"vosk",
|
|
"--wayland",
|
|
],
|
|
):
|
|
from vocalinux.main import parse_arguments
|
|
|
|
args = parse_arguments()
|
|
assert args.debug is True
|
|
assert args.model == "large"
|
|
assert args.language == "es"
|
|
assert args.engine == "vosk"
|
|
assert args.wayland is True
|
|
|
|
|
|
class TestCheckDependencies(unittest.TestCase):
|
|
"""Tests for check_dependencies() function."""
|
|
|
|
def test_check_dependencies_success(self):
|
|
"""Test successful dependency check with all deps mocked as available."""
|
|
from vocalinux.main import check_dependencies
|
|
|
|
mock_gi = MagicMock()
|
|
mock_gi_repo = MagicMock()
|
|
|
|
with patch("vocalinux.main.logging"):
|
|
with patch.dict(
|
|
sys.modules,
|
|
{
|
|
"gi": mock_gi,
|
|
"gi.repository": mock_gi_repo,
|
|
"pynput": MagicMock(),
|
|
"requests": MagicMock(),
|
|
},
|
|
):
|
|
result = check_dependencies()
|
|
assert result is True
|
|
|
|
def test_check_dependencies_missing_gtk(self):
|
|
"""Test dependency check with missing GTK."""
|
|
from vocalinux.main import check_dependencies
|
|
|
|
with patch("vocalinux.main.logging"):
|
|
with patch("builtins.__import__", side_effect=ImportError("No module")):
|
|
# Just verify the function doesn't crash
|
|
result = check_dependencies()
|
|
assert result is False
|
|
|
|
def test_check_dependencies_logs_gnome_extension_guidance(self):
|
|
from vocalinux.main import check_dependencies
|
|
|
|
with patch("vocalinux.main.logger") as mock_logger:
|
|
with patch("builtins.__import__", side_effect=ImportError("No module")):
|
|
result = check_dependencies()
|
|
|
|
assert result is False
|
|
mock_logger.error.assert_any_call(
|
|
" NOTE: On GNOME Shell (default on Debian), you also need:"
|
|
)
|
|
mock_logger.error.assert_any_call(" sudo apt install gnome-shell-extension-appindicator")
|
|
|
|
|
|
class TestCheckDisplayAvailable(unittest.TestCase):
|
|
"""Tests for check_display_available() function."""
|
|
|
|
@patch("vocalinux.main.logging")
|
|
def test_check_display_available_success(self, mock_logging):
|
|
"""Test successful display check - mocks gi to avoid real Gdk calls."""
|
|
from vocalinux.main import check_display_available
|
|
|
|
mock_gi = MagicMock()
|
|
mock_gdk = MagicMock()
|
|
mock_gdk.Display.get_default.return_value = MagicMock() # display exists
|
|
mock_gi_repo = MagicMock(Gdk=mock_gdk)
|
|
|
|
with patch.dict(sys.modules, {"gi": mock_gi, "gi.repository": mock_gi_repo}):
|
|
result = check_display_available()
|
|
assert result is True
|
|
|
|
@patch("vocalinux.main.logging")
|
|
def test_check_display_available_no_display(self, mock_logging):
|
|
"""Test display check when no display is available."""
|
|
from vocalinux.main import check_display_available
|
|
|
|
mock_gi = MagicMock()
|
|
mock_gdk = MagicMock()
|
|
mock_gdk.Display.get_default.return_value = None # no display
|
|
mock_gi_repo = MagicMock(Gdk=mock_gdk)
|
|
|
|
with patch.dict(sys.modules, {"gi": mock_gi, "gi.repository": mock_gi_repo}):
|
|
result = check_display_available()
|
|
assert result is False
|
|
|
|
|
|
class TestCheckAppIndicatorSupport(unittest.TestCase):
|
|
def test_check_appindicator_support_true_when_watcher_present(self):
|
|
from vocalinux.main import check_appindicator_support
|
|
|
|
mock_proxy = MagicMock()
|
|
mock_names_variant = MagicMock()
|
|
mock_names_variant.unpack.return_value = (
|
|
["org.freedesktop.DBus", "org.kde.StatusNotifierWatcher"],
|
|
)
|
|
mock_proxy.call_sync.return_value = mock_names_variant
|
|
|
|
mock_gio = MagicMock()
|
|
mock_gio.DBusProxy.new_for_bus_sync.return_value = mock_proxy
|
|
|
|
with patch.dict(
|
|
sys.modules,
|
|
{
|
|
"gi": MagicMock(),
|
|
"gi.repository": MagicMock(Gio=mock_gio),
|
|
},
|
|
):
|
|
assert check_appindicator_support() is True
|
|
|
|
def test_check_appindicator_support_false_when_watcher_missing(self):
|
|
from vocalinux.main import check_appindicator_support
|
|
|
|
mock_proxy = MagicMock()
|
|
mock_names_variant = MagicMock()
|
|
mock_names_variant.unpack.return_value = (["org.freedesktop.DBus"],)
|
|
mock_proxy.call_sync.return_value = mock_names_variant
|
|
|
|
mock_gio = MagicMock()
|
|
mock_gio.DBusProxy.new_for_bus_sync.return_value = mock_proxy
|
|
|
|
with patch.dict(
|
|
sys.modules,
|
|
{
|
|
"gi": MagicMock(),
|
|
"gi.repository": MagicMock(Gio=mock_gio),
|
|
},
|
|
):
|
|
assert check_appindicator_support() is False
|
|
|
|
def test_check_appindicator_support_true_on_exception(self):
|
|
from vocalinux.main import check_appindicator_support
|
|
|
|
mock_gio = MagicMock()
|
|
mock_gio.DBusProxy.new_for_bus_sync.side_effect = RuntimeError("dbus unavailable")
|
|
|
|
with patch.dict(
|
|
sys.modules,
|
|
{
|
|
"gi": MagicMock(),
|
|
"gi.repository": MagicMock(Gio=mock_gio),
|
|
},
|
|
):
|
|
assert check_appindicator_support() is True
|
|
|
|
|
|
class TestMainFunction(unittest.TestCase):
|
|
"""Tests for the main() function."""
|
|
|
|
@patch("vocalinux.main.logging")
|
|
@patch("vocalinux.main.check_dependencies")
|
|
@patch("vocalinux.main.check_display_available")
|
|
@patch("vocalinux.main.parse_arguments")
|
|
@patch("vocalinux.main.atexit")
|
|
def test_main_single_instance_already_running(
|
|
self,
|
|
mock_atexit,
|
|
mock_parse_args,
|
|
mock_check_display,
|
|
mock_check_deps,
|
|
mock_logging,
|
|
):
|
|
"""Test main() when another instance is already running."""
|
|
from vocalinux.main import main
|
|
|
|
# Patch the function itself: main() binds the module via the package
|
|
# attribute, so a patch.dict(sys.modules, ...) entry is ignored whenever
|
|
# an earlier test already imported the real vocalinux.single_instance.
|
|
with patch("vocalinux.single_instance.acquire_lock", return_value=False):
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main()
|
|
assert exc_info.value.code == 1
|
|
|
|
def test_main_version_flag_with_lock_held(self):
|
|
"""Test that --version works even when another instance holds the lock."""
|
|
from vocalinux.main import main
|
|
from vocalinux.version import __version__
|
|
|
|
mock_single_instance = MagicMock()
|
|
mock_single_instance.acquire_lock.return_value = False
|
|
|
|
output = StringIO()
|
|
with patch.dict(sys.modules, {"vocalinux.single_instance": mock_single_instance}):
|
|
with patch.object(sys, "argv", ["vocalinux", "--version"]):
|
|
with redirect_stdout(output):
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main()
|
|
|
|
assert exc_info.value.code == 0
|
|
assert output.getvalue() == f"vocalinux {__version__}\n"
|
|
mock_single_instance.acquire_lock.assert_not_called()
|
|
|
|
@patch("vocalinux.main.check_display_available")
|
|
@patch("vocalinux.main.logging")
|
|
@patch("vocalinux.main.check_dependencies")
|
|
@patch("vocalinux.main.parse_arguments")
|
|
@patch("vocalinux.main.atexit")
|
|
def test_main_missing_dependencies(
|
|
self, mock_atexit, mock_parse_args, mock_check_deps, mock_logging, mock_check_display
|
|
):
|
|
"""Test main() when dependencies are missing."""
|
|
from vocalinux.main import main
|
|
|
|
# Set up mocks
|
|
mock_check_deps.return_value = False
|
|
mock_parse_args.return_value = MagicMock(debug=False)
|
|
|
|
# Mock single_instance module since it's imported inside main()
|
|
mock_single_instance = MagicMock()
|
|
mock_single_instance.acquire_lock.return_value = True
|
|
|
|
with patch.dict(sys.modules, {"vocalinux.single_instance": mock_single_instance}):
|
|
# Should exit with status 1
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main()
|
|
assert exc_info.value.code == 1
|
|
|
|
@patch("vocalinux.main.logging")
|
|
@patch("vocalinux.main.check_dependencies")
|
|
@patch("vocalinux.main.check_display_available")
|
|
@patch("vocalinux.main.parse_arguments")
|
|
@patch("vocalinux.main.atexit")
|
|
def test_main_no_display_available(
|
|
self, mock_atexit, mock_parse_args, mock_check_display, mock_check_deps, mock_logging
|
|
):
|
|
"""Test main() when no display is available."""
|
|
from vocalinux.main import main
|
|
|
|
# Set up mocks
|
|
mock_check_deps.return_value = True
|
|
mock_check_display.return_value = False
|
|
mock_parse_args.return_value = MagicMock(debug=False)
|
|
|
|
# Mock single_instance module since it's imported inside main()
|
|
mock_single_instance = MagicMock()
|
|
mock_single_instance.acquire_lock.return_value = True
|
|
|
|
with patch.dict(sys.modules, {"vocalinux.single_instance": mock_single_instance}):
|
|
# Should exit with status 1
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main()
|
|
assert exc_info.value.code == 1
|
|
|
|
@patch("vocalinux.main.logging")
|
|
@patch("vocalinux.main.check_dependencies")
|
|
@patch("vocalinux.main.check_display_available")
|
|
@patch("vocalinux.main.parse_arguments")
|
|
@patch("vocalinux.main.atexit")
|
|
def test_main_debug_mode_enabled(
|
|
self,
|
|
mock_atexit,
|
|
mock_parse_args,
|
|
mock_check_display,
|
|
mock_check_deps,
|
|
mock_logging,
|
|
):
|
|
"""Test main() with debug mode enabled."""
|
|
from vocalinux.main import main
|
|
|
|
# Set up mocks
|
|
mock_check_deps.return_value = True
|
|
mock_check_display.return_value = True
|
|
|
|
mock_args = MagicMock()
|
|
mock_args.debug = True
|
|
mock_args.wayland = False
|
|
mock_args.start_minimized = False
|
|
mock_parse_args.return_value = mock_args
|
|
|
|
# Mock single_instance module since it's imported inside main()
|
|
mock_single_instance = MagicMock()
|
|
mock_single_instance.acquire_lock.return_value = True
|
|
|
|
# Create proper mock for ConfigManager with get_settings() returning valid data
|
|
mock_config_manager = MagicMock()
|
|
mock_config_manager.return_value.get_settings.return_value = {
|
|
"speech_recognition": {
|
|
"engine": "whisper",
|
|
"model_size": "medium",
|
|
"language": "en-us",
|
|
},
|
|
"audio": {},
|
|
"general": {"first_run": False},
|
|
}
|
|
|
|
# Mock the imported modules
|
|
with patch.dict(
|
|
sys.modules,
|
|
{
|
|
"vocalinux.single_instance": mock_single_instance,
|
|
"vocalinux.common_types": MagicMock(),
|
|
"vocalinux.speech_recognition": MagicMock(
|
|
recognition_manager=MagicMock(
|
|
SpeechRecognitionManager=MagicMock(
|
|
side_effect=Exception("Test exception to stop execution")
|
|
)
|
|
)
|
|
),
|
|
"vocalinux.text_injection.text_injector": MagicMock(),
|
|
"vocalinux.ui.tray_indicator": MagicMock(),
|
|
"vocalinux.ui.action_handler": MagicMock(),
|
|
"vocalinux.ui.config_manager": mock_config_manager,
|
|
"vocalinux.ui.logging_manager": MagicMock(),
|
|
"vocalinux.text_injection": MagicMock(),
|
|
"vocalinux.ui.first_run_dialog": MagicMock(),
|
|
"vocalinux.ui.autostart_manager": MagicMock(),
|
|
"vocalinux.text_injection.start_ibus_daemon": MagicMock(),
|
|
},
|
|
):
|
|
# Mock the config manager and other components
|
|
with patch("vocalinux.main.logging.getLogger") as mock_get_logger:
|
|
mock_logger = MagicMock()
|
|
mock_get_logger.return_value = mock_logger
|
|
|
|
# This would normally start the GTK main loop, which we want to avoid
|
|
# The initialization should fail as we've set SpeechRecognitionManager to raise an exception
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main()
|
|
assert exc_info.value.code == 1
|
|
|
|
@patch("vocalinux.main.logging")
|
|
@patch("vocalinux.main.check_dependencies")
|
|
@patch("vocalinux.main.check_display_available")
|
|
@patch("vocalinux.main.check_appindicator_support")
|
|
@patch("vocalinux.main.parse_arguments")
|
|
@patch("vocalinux.main.atexit")
|
|
def test_main_logs_warning_when_appindicator_support_missing(
|
|
self,
|
|
mock_atexit,
|
|
mock_parse_args,
|
|
mock_check_appindicator,
|
|
mock_check_display,
|
|
mock_check_deps,
|
|
mock_logging,
|
|
):
|
|
from vocalinux.main import main
|
|
|
|
mock_check_deps.return_value = True
|
|
mock_check_display.return_value = True
|
|
mock_check_appindicator.return_value = False
|
|
|
|
mock_args = MagicMock()
|
|
mock_args.debug = False
|
|
mock_args.wayland = False
|
|
mock_args.start_minimized = False
|
|
mock_args.engine = None
|
|
mock_args.model = None
|
|
mock_args.language = None
|
|
mock_parse_args.return_value = mock_args
|
|
|
|
mock_single_instance = MagicMock()
|
|
mock_single_instance.acquire_lock.return_value = True
|
|
|
|
mock_config_manager = MagicMock()
|
|
mock_config_manager.return_value.get_settings.return_value = {
|
|
"speech_recognition": {},
|
|
"audio": {},
|
|
"general": {"first_run": False},
|
|
}
|
|
|
|
mock_speech_manager_ctor = MagicMock(side_effect=Exception("stop"))
|
|
|
|
with patch.dict(
|
|
sys.modules,
|
|
{
|
|
"vocalinux.single_instance": mock_single_instance,
|
|
"vocalinux.common_types": MagicMock(),
|
|
"vocalinux.speech_recognition": MagicMock(
|
|
recognition_manager=MagicMock(SpeechRecognitionManager=mock_speech_manager_ctor)
|
|
),
|
|
"vocalinux.text_injection.text_injector": MagicMock(),
|
|
"vocalinux.ui.tray_indicator": MagicMock(),
|
|
"vocalinux.ui.action_handler": MagicMock(),
|
|
"vocalinux.ui.config_manager": mock_config_manager,
|
|
"vocalinux.ui.logging_manager": MagicMock(),
|
|
"vocalinux.text_injection": MagicMock(start_ibus_daemon=MagicMock()),
|
|
"vocalinux.ui.first_run_dialog": MagicMock(),
|
|
"vocalinux.ui.autostart_manager": MagicMock(),
|
|
},
|
|
):
|
|
with patch("vocalinux.main.logger") as mock_logger:
|
|
with pytest.raises(SystemExit):
|
|
main()
|
|
|
|
mock_logger.warning.assert_any_call(
|
|
"No StatusNotifierWatcher found on D-Bus session bus."
|
|
)
|
|
mock_logger.warning.assert_any_call("The system tray icon may not appear.")
|
|
mock_speech_manager_ctor.assert_called_once()
|
|
|
|
@patch("vocalinux.main.logging")
|
|
@patch("vocalinux.main.check_dependencies")
|
|
@patch("vocalinux.main.check_display_available")
|
|
@patch("vocalinux.main.parse_arguments")
|
|
@patch("vocalinux.main.atexit")
|
|
def test_main_initialization_error(
|
|
self,
|
|
mock_atexit,
|
|
mock_parse_args,
|
|
mock_check_display,
|
|
mock_check_deps,
|
|
mock_logging,
|
|
):
|
|
"""Test main() when initialization fails."""
|
|
from vocalinux.main import main
|
|
|
|
# Set up mocks
|
|
mock_check_deps.return_value = True
|
|
mock_check_display.return_value = True
|
|
|
|
mock_args = MagicMock()
|
|
mock_args.debug = False
|
|
mock_args.wayland = False
|
|
mock_args.start_minimized = False
|
|
mock_parse_args.return_value = mock_args
|
|
|
|
# Mock single_instance module since it's imported inside main()
|
|
mock_single_instance = MagicMock()
|
|
mock_single_instance.acquire_lock.return_value = True
|
|
|
|
# Mock components that will raise an exception
|
|
with patch("vocalinux.main.logging.getLogger") as mock_get_logger:
|
|
mock_logger = MagicMock()
|
|
mock_get_logger.return_value = mock_logger
|
|
|
|
with patch.dict(
|
|
sys.modules,
|
|
{
|
|
"vocalinux.single_instance": mock_single_instance,
|
|
"vocalinux.common_types": MagicMock(),
|
|
"vocalinux.speech_recognition": MagicMock(
|
|
recognition_manager=MagicMock(
|
|
SpeechRecognitionManager=MagicMock(side_effect=Exception("Init error"))
|
|
)
|
|
),
|
|
"vocalinux.text_injection.text_injector": MagicMock(),
|
|
"vocalinux.ui.tray_indicator": MagicMock(),
|
|
"vocalinux.ui.action_handler": MagicMock(),
|
|
"vocalinux.ui.config_manager": MagicMock(),
|
|
"vocalinux.ui.logging_manager": MagicMock(),
|
|
"vocalinux.text_injection": MagicMock(),
|
|
"vocalinux.ui.first_run_dialog": MagicMock(),
|
|
"vocalinux.ui.autostart_manager": MagicMock(),
|
|
"vocalinux.text_injection.start_ibus_daemon": MagicMock(),
|
|
},
|
|
):
|
|
# Should exit with status 1
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main()
|
|
assert exc_info.value.code == 1
|
|
|
|
|
|
class TestMainEntry(unittest.TestCase):
|
|
"""Tests for the main entry point and script execution."""
|
|
|
|
@patch("vocalinux.main.main")
|
|
def test_main_entry_point(self, mock_main):
|
|
"""Test that the script entry point calls main()."""
|
|
# Import the module and verify the entry point
|
|
import vocalinux.main as main_module
|
|
|
|
# Check that main function exists
|
|
assert hasattr(main_module, "main")
|
|
assert callable(main_module.main)
|
|
|
|
def test_module_can_be_imported(self):
|
|
"""Test that the main module can be imported successfully."""
|
|
try:
|
|
import vocalinux.main # noqa: F401
|
|
|
|
assert True
|
|
except Exception as e:
|
|
pytest.fail(f"Failed to import vocalinux.main: {e}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|