Minor on the stable line. Faster Whisper and Parakeet extras, Speech Model simple setup, Snap uinput, Flatpak bundles on the tag, and QA fixes from the release branch. Fixes tracked in the PR. Post-release follow-ups: #834.
204 lines
7.7 KiB
Python
204 lines
7.7 KiB
Python
"""The recommendation must be applicable, and must not ignore the disk (#778)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import sys
|
|
from collections.abc import Iterator
|
|
from typing import Any
|
|
from unittest.mock import MagicMock, Mock, call, patch
|
|
|
|
import pytest
|
|
|
|
import vocalinux.ui
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def settings_dialog() -> Iterator[Any]:
|
|
"""Import settings_dialog with real base classes for its GTK subclasses.
|
|
|
|
Same reasoning as tests/test_settings_model_state.py; both sys.modules and the
|
|
package attribute are restored so later tests patching the module by name do
|
|
not reach a second module object (#686).
|
|
"""
|
|
repository = sys.modules["gi.repository"]
|
|
bases = {name: type(name, (), {}) for name in ("Box", "ListBoxRow", "Dialog")}
|
|
saved_module = sys.modules.pop("vocalinux.ui.settings_dialog", None)
|
|
saved_attribute = getattr(vocalinux.ui, "settings_dialog", None)
|
|
try:
|
|
with patch.object(repository, "Gtk", MagicMock(**bases)):
|
|
module = importlib.import_module("vocalinux.ui.settings_dialog")
|
|
yield module
|
|
finally:
|
|
sys.modules.pop("vocalinux.ui.settings_dialog", None)
|
|
if saved_module is not None:
|
|
sys.modules["vocalinux.ui.settings_dialog"] = saved_module
|
|
if saved_attribute is not None:
|
|
vocalinux.ui.settings_dialog = saved_attribute
|
|
elif hasattr(vocalinux.ui, "settings_dialog"):
|
|
del vocalinux.ui.settings_dialog
|
|
|
|
|
|
@pytest.fixture
|
|
def dialog_class(settings_dialog: Any) -> Any:
|
|
return settings_dialog.SettingsDialog
|
|
|
|
|
|
def _dialog_stub(language: str = "en-us") -> Mock:
|
|
dialog = Mock()
|
|
dialog.language = language
|
|
dialog.language_combo.get_active_id.return_value = language
|
|
dialog._populating_models = False
|
|
dialog._processing_language_change = False
|
|
# Plain Mock attrs are truthy; these guards must be real False or the
|
|
# language-changed handler returns before updating dialog.language.
|
|
dialog._initializing = False
|
|
dialog._applying_settings = False
|
|
dialog._simple_driving = False
|
|
return dialog
|
|
|
|
|
|
def _with_disk(settings_dialog: Any, downloaded: list[str]) -> tuple[Any, Any]:
|
|
"""Patch the disk so only ``downloaded`` counts as present."""
|
|
return (
|
|
patch.object(settings_dialog, "list_downloaded_whispercpp_models", return_value=downloaded),
|
|
patch.object(
|
|
settings_dialog,
|
|
"is_whispercpp_model_downloaded",
|
|
side_effect=lambda name: name in downloaded,
|
|
),
|
|
)
|
|
|
|
|
|
def test_a_bigger_model_on_disk_is_offered_instead_of_a_download(
|
|
settings_dialog: Any, dialog_class: Any
|
|
) -> None:
|
|
"""The reported case: recommended small.en, medium.en already paid for."""
|
|
dialog = _dialog_stub()
|
|
listed, downloaded = _with_disk(settings_dialog, ["medium.en", "small", "tiny"])
|
|
|
|
with listed, downloaded:
|
|
alternative = dialog_class._downloaded_alternative_for(dialog, "small.en")
|
|
|
|
assert alternative == "medium.en"
|
|
|
|
|
|
def test_nothing_is_offered_when_the_recommendation_is_already_on_disk(
|
|
settings_dialog: Any, dialog_class: Any
|
|
) -> None:
|
|
dialog = _dialog_stub()
|
|
listed, downloaded = _with_disk(settings_dialog, ["small.en", "medium.en"])
|
|
|
|
with listed, downloaded:
|
|
assert dialog_class._downloaded_alternative_for(dialog, "small.en") is None
|
|
|
|
|
|
def test_a_smaller_model_is_never_offered(settings_dialog: Any, dialog_class: Any) -> None:
|
|
"""Reusing a download must not quietly cost accuracy."""
|
|
dialog = _dialog_stub()
|
|
listed, downloaded = _with_disk(settings_dialog, ["tiny", "tiny.en"])
|
|
|
|
with listed, downloaded:
|
|
assert dialog_class._downloaded_alternative_for(dialog, "medium.en") is None
|
|
|
|
|
|
def test_english_only_weights_are_not_offered_for_another_language(
|
|
settings_dialog: Any, dialog_class: Any
|
|
) -> None:
|
|
dialog = _dialog_stub(language="pl")
|
|
listed, downloaded = _with_disk(settings_dialog, ["medium.en"])
|
|
|
|
with listed, downloaded:
|
|
assert dialog_class._downloaded_alternative_for(dialog, "small") is None
|
|
|
|
|
|
def test_the_smallest_qualifying_model_wins(settings_dialog: Any, dialog_class: Any) -> None:
|
|
"""Between two usable downloads, take the cheaper one to run."""
|
|
dialog = _dialog_stub()
|
|
listed, downloaded = _with_disk(settings_dialog, ["large", "medium.en"])
|
|
|
|
with listed, downloaded:
|
|
assert dialog_class._downloaded_alternative_for(dialog, "small.en") == "medium.en"
|
|
|
|
|
|
def test_applying_the_recommendation_sets_both_pickers(
|
|
settings_dialog: Any, dialog_class: Any
|
|
) -> None:
|
|
"""Clicking must move size and specialization together, not just one."""
|
|
dialog = _dialog_stub()
|
|
dialog._recommended_target_model = "small.en"
|
|
|
|
dialog_class._on_apply_recommendation(dialog, None)
|
|
|
|
dialog.model_combo.set_active_id.assert_called_once_with("small")
|
|
dialog._populate_whispercpp_variant_options.assert_called_once_with("small", "small.en")
|
|
dialog.model_variant_combo.set_active_id.assert_called_once_with("small.en")
|
|
dialog._sync_language_options_for_selected_model.assert_called_once_with()
|
|
dialog._update_model_info.assert_called_once_with()
|
|
dialog._refresh_unused_downloads.assert_called_once_with()
|
|
dialog._auto_apply_settings.assert_called_once_with()
|
|
assert dialog._populating_models is False
|
|
|
|
|
|
def test_applying_does_nothing_without_a_target(settings_dialog: Any, dialog_class: Any) -> None:
|
|
dialog = _dialog_stub()
|
|
dialog._recommended_target_model = None
|
|
|
|
dialog_class._on_apply_recommendation(dialog, None)
|
|
|
|
dialog.model_combo.set_active_id.assert_not_called()
|
|
dialog._auto_apply_settings.assert_not_called()
|
|
|
|
|
|
def test_applying_recommendation_suppresses_model_changed_auto_apply(
|
|
settings_dialog: Any, dialog_class: Any
|
|
) -> None:
|
|
"""Size change must not briefly auto-apply the default size before the target.
|
|
|
|
Without ``_populating_models``, ``set_active_id(large)`` would fire
|
|
``_on_model_changed`` and download full large before turbo-q5_0 lands.
|
|
"""
|
|
dialog = _dialog_stub()
|
|
dialog._recommended_target_model = "large-v3-turbo-q5_0"
|
|
seen_while_setting_size: list[bool] = []
|
|
|
|
def capture_flag(_model_size: str) -> bool:
|
|
seen_while_setting_size.append(dialog._populating_models)
|
|
return True
|
|
|
|
dialog.model_combo.set_active_id.side_effect = capture_flag
|
|
|
|
dialog_class._on_apply_recommendation(dialog, None)
|
|
|
|
assert seen_while_setting_size == [True]
|
|
dialog._populate_whispercpp_variant_options.assert_called_once_with(
|
|
"large", "large-v3-turbo-q5_0"
|
|
)
|
|
dialog.model_variant_combo.set_active_id.assert_called_once_with("large-v3-turbo-q5_0")
|
|
# One apply after both pickers settle — not during the size change.
|
|
dialog._auto_apply_settings.assert_called_once_with()
|
|
assert dialog._populating_models is False
|
|
|
|
|
|
def test_language_change_refreshes_recommendation_ui(
|
|
settings_dialog: Any, dialog_class: Any
|
|
) -> None:
|
|
"""EN→PL must refresh recommendation target / label so Use it is not stale."""
|
|
dialog = _dialog_stub(language="en-us")
|
|
dialog.language_combo.get_active_id.return_value = "pl"
|
|
dialog.engine_combo.get_active_text.return_value = "whisper.cpp"
|
|
dialog._recommended_target_model = "small.en"
|
|
|
|
dialog_class._on_language_changed(dialog, None)
|
|
|
|
assert dialog.language == "pl"
|
|
dialog._populate_model_options.assert_called_once_with()
|
|
dialog._update_language_warning.assert_called_once_with()
|
|
dialog._update_model_info.assert_called_once_with()
|
|
dialog._auto_apply_settings.assert_called_once_with()
|
|
# Refresh happens before auto-apply so the card is current when settings land.
|
|
assert dialog.mock_calls.index(call._update_model_info()) < dialog.mock_calls.index(
|
|
call._auto_apply_settings()
|
|
)
|
|
assert dialog._processing_language_change is False
|