mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 23:47:41 +08:00
fix(scheduler): close async task lifecycle
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import asyncio
|
||||
import threading
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
from app import scheduler as scheduler_module
|
||||
from app.scheduler import Scheduler
|
||||
@@ -46,6 +47,32 @@ def test_scheduler_initializer_starts_background_jobs(monkeypatch):
|
||||
scheduler.init.assert_called_once_with()
|
||||
|
||||
|
||||
def test_scheduler_initializer_stop_preserves_sync_abi(monkeypatch):
|
||||
"""同步调用停止入口仍应立即关闭 Scheduler,不返回 coroutine。"""
|
||||
scheduler = Mock()
|
||||
monkeypatch.setattr(scheduler_initializer, "Scheduler", Mock(return_value=scheduler))
|
||||
|
||||
assert scheduler_initializer.stop_scheduler() is None
|
||||
scheduler.stop.assert_called_once_with()
|
||||
scheduler.async_stop.assert_not_called()
|
||||
|
||||
|
||||
def test_scheduler_initializer_stop_awaits_in_running_loop(monkeypatch):
|
||||
"""生命周期事件循环中的停止入口应返回可等待的异步收口。"""
|
||||
scheduler = Mock()
|
||||
scheduler.async_stop = AsyncMock()
|
||||
monkeypatch.setattr(scheduler_initializer, "Scheduler", Mock(return_value=scheduler))
|
||||
|
||||
async def scenario():
|
||||
result = scheduler_initializer.stop_scheduler()
|
||||
assert result is not None
|
||||
await result
|
||||
|
||||
asyncio.run(scenario())
|
||||
scheduler.async_stop.assert_awaited_once_with()
|
||||
scheduler.stop.assert_not_called()
|
||||
|
||||
|
||||
def test_clear_cache_is_manual_only(monkeypatch):
|
||||
"""缓存清理任务应仅手动执行,不注册到调度器自动运行。"""
|
||||
background_scheduler = _BackgroundSchedulerStub()
|
||||
|
||||
@@ -11,7 +11,11 @@ from app.scheduler import Scheduler
|
||||
def _build_scheduler(job_id, func):
|
||||
"""构造不启动 APScheduler 的定时服务测试对象。"""
|
||||
scheduler = object.__new__(Scheduler)
|
||||
scheduler._scheduler = None
|
||||
scheduler._event = threading.Event()
|
||||
scheduler._lock = threading.RLock()
|
||||
scheduler._async_tasks = set()
|
||||
scheduler._accepting_async_tasks = True
|
||||
scheduler._jobs = {
|
||||
job_id: {
|
||||
"name": "测试定时服务",
|
||||
@@ -169,6 +173,36 @@ def test_scheduler_records_cancelled_async_job_as_failed():
|
||||
assert progress.error == "任务已取消"
|
||||
|
||||
|
||||
def test_scheduler_async_stop_cancels_owned_async_jobs():
|
||||
"""Scheduler 关停应取消并等待自身登记的异步作业。"""
|
||||
job_id = f"test-owned-task-{uuid4()}"
|
||||
started = asyncio.Event()
|
||||
cancelled = asyncio.Event()
|
||||
|
||||
async def task():
|
||||
"""等待关停信号,验证任务确实由 Scheduler 持有。"""
|
||||
started.set()
|
||||
try:
|
||||
await asyncio.Event().wait()
|
||||
except asyncio.CancelledError:
|
||||
cancelled.set()
|
||||
raise
|
||||
|
||||
scheduler = _build_scheduler(job_id, task)
|
||||
|
||||
async def run_task():
|
||||
"""在当前事件循环启动并收口异步作业。"""
|
||||
scheduler.start(job_id)
|
||||
await started.wait()
|
||||
assert len(scheduler._async_tasks) == 1
|
||||
await scheduler.async_stop(timeout_seconds=1)
|
||||
|
||||
asyncio.run(run_task())
|
||||
|
||||
assert cancelled.is_set()
|
||||
assert scheduler._async_tasks == set()
|
||||
|
||||
|
||||
def test_scheduler_returns_none_for_unknown_job():
|
||||
"""未注册且无历史进度的定时服务应返回空。"""
|
||||
job_id = f"test-unknown-{uuid4()}"
|
||||
|
||||
Reference in New Issue
Block a user