105 lines
2.8 KiB
Python
105 lines
2.8 KiB
Python
from pathlib import Path
|
|
|
|
from watchfiles import Change
|
|
|
|
import asyncio
|
|
import pytest
|
|
|
|
from src.config.config import ConfigManager
|
|
from src.config.file_watcher import FileChange, FileWatcherStats
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_handle_file_changes_throttles_reload():
|
|
manager = ConfigManager()
|
|
manager._hot_reload_min_interval_s = 100.0
|
|
|
|
called = 0
|
|
|
|
async def reload_stub(changed_scopes=None) -> bool:
|
|
nonlocal called
|
|
called += 1
|
|
return True
|
|
|
|
manager.reload_config = reload_stub # type: ignore[method-assign]
|
|
changes = [FileChange(change_type=Change.modified, path=Path("/tmp/bot_config.toml"))]
|
|
|
|
await manager._handle_file_changes(changes)
|
|
await manager._handle_file_changes(changes)
|
|
|
|
assert called == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_handle_file_changes_timeout_logged(caplog):
|
|
manager = ConfigManager()
|
|
manager._hot_reload_min_interval_s = 0.0
|
|
manager._hot_reload_timeout_s = 0.01
|
|
|
|
async def reload_stub(changed_scopes=None) -> bool:
|
|
await asyncio.sleep(0.05)
|
|
return True
|
|
|
|
manager.reload_config = reload_stub # type: ignore[method-assign]
|
|
changes = [FileChange(change_type=Change.modified, path=Path("/tmp/model_config.toml"))]
|
|
|
|
with caplog.at_level("ERROR"):
|
|
await manager._handle_file_changes(changes)
|
|
|
|
assert "配置热重载超时" in caplog.text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_handle_file_changes_empty_skips_reload():
|
|
manager = ConfigManager()
|
|
|
|
called = 0
|
|
|
|
async def reload_stub(changed_scopes=None) -> bool:
|
|
nonlocal called
|
|
called += 1
|
|
return True
|
|
|
|
manager.reload_config = reload_stub # type: ignore[method-assign]
|
|
|
|
await manager._handle_file_changes([])
|
|
|
|
assert called == 0
|
|
|
|
|
|
class _FakeWatcher:
|
|
def __init__(self):
|
|
self.unsubscribe_called_with: str | None = None
|
|
self.stop_called = False
|
|
self.stats = FileWatcherStats(
|
|
batches_seen=1,
|
|
changes_seen=2,
|
|
callbacks_succeeded=3,
|
|
callbacks_failed=4,
|
|
callbacks_timed_out=5,
|
|
callbacks_skipped_cooldown=6,
|
|
restart_count=7,
|
|
)
|
|
|
|
def unsubscribe(self, subscription_id: str) -> bool:
|
|
self.unsubscribe_called_with = subscription_id
|
|
return True
|
|
|
|
async def stop(self) -> None:
|
|
self.stop_called = True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stop_file_watcher_cleans_state():
|
|
manager = ConfigManager()
|
|
fake_watcher = _FakeWatcher()
|
|
manager._file_watcher = fake_watcher # type: ignore[assignment]
|
|
manager._file_watcher_subscription_id = "sub-1"
|
|
|
|
await manager.stop_file_watcher()
|
|
|
|
assert fake_watcher.unsubscribe_called_with == "sub-1"
|
|
assert fake_watcher.stop_called is True
|
|
assert manager._file_watcher is None
|
|
assert manager._file_watcher_subscription_id is None
|