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.
157 lines
4.8 KiB
Python
157 lines
4.8 KiB
Python
"""Tests for model keep-alive idle unload helper."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import unittest
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from vocalinux.model_keepalive import (
|
|
DEFAULT_IDLE_TIMEOUT_SECONDS,
|
|
ModelKeepAlive,
|
|
clamp_idle_timeout,
|
|
)
|
|
from vocalinux.ui.config_manager import DEFAULT_CONFIG
|
|
|
|
|
|
class TestClampIdleTimeout(unittest.TestCase):
|
|
def test_clamps_low_and_high(self):
|
|
self.assertEqual(clamp_idle_timeout(10), 60)
|
|
self.assertEqual(clamp_idle_timeout(99999), 3600)
|
|
self.assertEqual(clamp_idle_timeout(300), 300)
|
|
|
|
def test_invalid_falls_back_to_default(self):
|
|
self.assertEqual(clamp_idle_timeout("nope"), float(DEFAULT_IDLE_TIMEOUT_SECONDS))
|
|
self.assertEqual(clamp_idle_timeout(None), float(DEFAULT_IDLE_TIMEOUT_SECONDS))
|
|
|
|
|
|
class TestModelKeepAlive(unittest.TestCase):
|
|
def test_bump_when_disabled_cancels(self):
|
|
fired = []
|
|
ka = ModelKeepAlive(
|
|
get_config=lambda: (False, 300),
|
|
on_idle_unload=lambda: fired.append(1),
|
|
use_glib=False,
|
|
)
|
|
ka.start()
|
|
ka.bump()
|
|
self.assertFalse(ka.armed)
|
|
self.assertFalse(ka.fire_if_due())
|
|
self.assertEqual(fired, [])
|
|
|
|
def test_bump_arms_and_fire_unloads(self):
|
|
fired = []
|
|
ka = ModelKeepAlive(
|
|
get_config=lambda: (True, 300),
|
|
on_idle_unload=lambda: fired.append(1),
|
|
is_safe_to_unload=lambda: True,
|
|
use_glib=False,
|
|
)
|
|
ka.start()
|
|
ka.bump()
|
|
self.assertTrue(ka.armed)
|
|
self.assertTrue(ka.fire_if_due())
|
|
self.assertEqual(fired, [1])
|
|
self.assertFalse(ka.armed)
|
|
# Second fire without re-arm is a no-op
|
|
self.assertFalse(ka.fire_if_due())
|
|
|
|
def test_cancel_prevents_fire(self):
|
|
fired = []
|
|
ka = ModelKeepAlive(
|
|
get_config=lambda: (True, 120),
|
|
on_idle_unload=lambda: fired.append(1),
|
|
use_glib=False,
|
|
)
|
|
ka.start()
|
|
ka.bump()
|
|
ka.cancel()
|
|
self.assertFalse(ka.armed)
|
|
self.assertFalse(ka.fire_if_due())
|
|
self.assertEqual(fired, [])
|
|
|
|
def test_unsafe_predicate_skips_unload(self):
|
|
fired = []
|
|
ka = ModelKeepAlive(
|
|
get_config=lambda: (True, 120),
|
|
on_idle_unload=lambda: fired.append(1),
|
|
is_safe_to_unload=lambda: False,
|
|
use_glib=False,
|
|
)
|
|
ka.start()
|
|
ka.bump()
|
|
self.assertFalse(ka.fire_if_due())
|
|
self.assertEqual(fired, [])
|
|
|
|
def test_callback_error_is_swallowed(self):
|
|
def boom():
|
|
raise RuntimeError("unload failed")
|
|
|
|
ka = ModelKeepAlive(
|
|
get_config=lambda: (True, 120),
|
|
on_idle_unload=boom,
|
|
is_safe_to_unload=lambda: True,
|
|
use_glib=False,
|
|
)
|
|
ka.start()
|
|
ka.bump()
|
|
self.assertFalse(ka.fire_if_due())
|
|
|
|
def test_config_error_disables(self):
|
|
def bad_config():
|
|
raise RuntimeError("config boom")
|
|
|
|
ka = ModelKeepAlive(get_config=bad_config, use_glib=False)
|
|
ka.start()
|
|
ka.bump()
|
|
self.assertFalse(ka.armed)
|
|
|
|
def test_stop_and_shutdown(self):
|
|
ka = ModelKeepAlive(get_config=lambda: (True, 120), use_glib=False)
|
|
ka.start()
|
|
ka.bump()
|
|
ka.shutdown()
|
|
self.assertFalse(ka.active)
|
|
self.assertFalse(ka.armed)
|
|
|
|
def test_start_idempotent(self):
|
|
ka = ModelKeepAlive(get_config=lambda: (True, 120), use_glib=False)
|
|
ka.start()
|
|
ka.start()
|
|
self.assertTrue(ka.active)
|
|
|
|
def test_glib_schedule_and_timeout(self):
|
|
"""Drive GLib scheduling via mocked gi.repository (same pattern as auto-pause)."""
|
|
mock_glib = MagicMock()
|
|
mock_glib.timeout_add_seconds.return_value = 42
|
|
fired = []
|
|
|
|
with patch.dict(
|
|
"sys.modules",
|
|
{"gi": MagicMock(), "gi.repository": MagicMock(GLib=mock_glib)},
|
|
):
|
|
ka = ModelKeepAlive(
|
|
get_config=lambda: (True, 90),
|
|
on_idle_unload=lambda: fired.append(1),
|
|
is_safe_to_unload=lambda: True,
|
|
use_glib=True,
|
|
)
|
|
ka.start()
|
|
ka.bump()
|
|
mock_glib.timeout_add_seconds.assert_called()
|
|
# Invoke the scheduled callback
|
|
cb = mock_glib.timeout_add_seconds.call_args[0][1]
|
|
self.assertFalse(cb())
|
|
self.assertEqual(fired, [1])
|
|
# Callback clears _timeout_id; shutdown should be a no-op cancel
|
|
ka.shutdown()
|
|
self.assertFalse(ka.active)
|
|
self.assertFalse(ka.armed)
|
|
|
|
def test_default_config_section(self):
|
|
self.assertIn("model_keepalive", DEFAULT_CONFIG)
|
|
self.assertFalse(DEFAULT_CONFIG["model_keepalive"]["enabled"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|