Files
MoviePilot/tests/test_plugin_catalog_concurrency.py
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

127 lines
3.3 KiB
Python

"""
插件市场目录读取并发控制测试。
"""
import asyncio
import threading
import time
from collections.abc import Callable
import pytest
from app.application.plugin.catalog import PluginCatalogService
def _catalog(
max_concurrency: int = 2,
error: Callable[[str], None] | None = None,
) -> PluginCatalogService:
"""
构造不依赖真实插件目录和网络的目录服务。
"""
return PluginCatalogService(
market_loader=lambda *_args: {},
async_market_loader=lambda *_args: {},
installed_plugins_provider=lambda: [],
plugin_mapper=lambda *_args, **_kwargs: None,
is_local_repo=lambda _repo_url: False,
version_compare=lambda *_args: False,
warning=lambda *_args: None,
error=error if error is not None else lambda *_args: None,
max_concurrency=max_concurrency,
)
def test_sync_collect_limits_market_fetch_concurrency() -> None:
"""
同步目录刷新不能因市场数量增长而创建无限线程。
"""
lock = threading.Lock()
active = 0
peak = 0
calls: list[tuple[str, str | None, bool]] = []
def loader(market: str, package_version: str | None, force: bool):
nonlocal active, peak
with lock:
active += 1
peak = max(peak, active)
calls.append((market, package_version, force))
time.sleep(0.01)
with lock:
active -= 1
return []
result = _catalog(max_concurrency=2).collect(
markets=["market-a", "market-b", "market-c"],
compatible_flags=["v3"],
force=True,
loader=loader,
)
assert result == []
assert len(calls) == 6
assert peak <= 2
def test_sync_collect_isolates_market_failure() -> None:
"""
同步目录刷新遇到单个市场异常时仍保留其他市场结果。
"""
errors: list[str] = []
def loader(
market: str,
package_version: str | None,
_force: bool,
):
if market == "market-b" and package_version == "v3":
raise RuntimeError("market unavailable")
return []
result = _catalog(max_concurrency=2, error=errors.append).collect(
markets=["market-a", "market-b"],
compatible_flags=["v3"],
force=True,
loader=loader,
)
assert result == []
assert len(errors) == 1
assert "market unavailable" in errors[0]
@pytest.mark.asyncio
async def test_async_collect_limits_market_fetch_concurrency() -> None:
"""
异步目录刷新必须用信号量限制同时在途的市场请求。
"""
active = 0
peak = 0
async def loader(_market: str, _package_version: str | None, _force: bool):
nonlocal active, peak
active += 1
peak = max(peak, active)
await asyncio.sleep(0.01)
active -= 1
return []
result = await _catalog(max_concurrency=2).async_collect(
markets=["market-a", "market-b", "market-c"],
compatible_flags=["v3"],
force=True,
loader=loader,
)
assert result == []
assert peak <= 2
def test_market_fetch_concurrency_must_be_positive() -> None:
"""
无效并发值应在服务构造时快速失败。
"""
with pytest.raises(ValueError, match="大于 0"):
_catalog(max_concurrency=0)