117 lines
3.5 KiB
Python
117 lines
3.5 KiB
Python
"""浏览器池主动回收逻辑的单元测试(不启动真实 Chromium)。"""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
import pytest
|
|
|
|
from app.core.config import Settings
|
|
from app.services.browser_pool import BrowserPool
|
|
|
|
|
|
class _FakeBrowser:
|
|
"""最小化的浏览器替身,仅实现连接状态查询。"""
|
|
|
|
def __init__(self, connected: bool = True):
|
|
self._connected = connected
|
|
|
|
def is_connected(self) -> bool:
|
|
return self._connected
|
|
|
|
|
|
def _make_pool(**overrides) -> BrowserPool:
|
|
settings = Settings(**overrides)
|
|
pool = BrowserPool(settings)
|
|
pool._browser = _FakeBrowser()
|
|
pool._started = True
|
|
return pool
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_recycle_due_by_context_count():
|
|
pool = _make_pool(browser_recycle_after_contexts=10, browser_recycle_after_seconds=0)
|
|
pool._browser_started_at = asyncio.get_running_loop().time()
|
|
|
|
pool._contexts_since_start = 9
|
|
assert pool._recycle_due() is False
|
|
|
|
pool._contexts_since_start = 10
|
|
assert pool._recycle_due() is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_recycle_due_by_age():
|
|
pool = _make_pool(browser_recycle_after_contexts=0, browser_recycle_after_seconds=100)
|
|
now = asyncio.get_running_loop().time()
|
|
|
|
pool._browser_started_at = now - 99
|
|
assert pool._recycle_due() is False
|
|
|
|
pool._browser_started_at = now - 101
|
|
assert pool._recycle_due() is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_recycle_disabled_when_thresholds_zero():
|
|
pool = _make_pool(browser_recycle_after_contexts=0, browser_recycle_after_seconds=0)
|
|
pool._browser_started_at = asyncio.get_running_loop().time() - 99999
|
|
pool._contexts_since_start = 99999
|
|
assert pool._recycle_due() is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_maybe_recycle_rebuilds_when_idle(monkeypatch):
|
|
pool = _make_pool(browser_recycle_after_contexts=10, browser_recycle_after_seconds=0)
|
|
pool._browser_started_at = asyncio.get_running_loop().time()
|
|
pool._contexts_since_start = 10
|
|
pool._active_contexts = 0
|
|
|
|
calls: list[str] = []
|
|
|
|
async def fake_close():
|
|
calls.append("close")
|
|
|
|
async def fake_start():
|
|
calls.append("start")
|
|
pool._browser = _FakeBrowser()
|
|
pool._started = True
|
|
pool._contexts_since_start = 0 # start() 正常会重置计数
|
|
|
|
monkeypatch.setattr(pool, "close", fake_close)
|
|
monkeypatch.setattr(pool, "start", fake_start)
|
|
|
|
await pool._maybe_recycle()
|
|
|
|
assert calls == ["close", "start"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_maybe_recycle_skips_when_busy(monkeypatch):
|
|
pool = _make_pool(browser_recycle_after_contexts=10, browser_recycle_after_seconds=0)
|
|
pool._browser_started_at = asyncio.get_running_loop().time()
|
|
pool._contexts_since_start = 10
|
|
pool._active_contexts = 1 # 仍有会话在用,不应回收
|
|
|
|
calls: list[str] = []
|
|
monkeypatch.setattr(pool, "close", lambda: calls.append("close"))
|
|
monkeypatch.setattr(pool, "start", lambda: calls.append("start"))
|
|
|
|
await pool._maybe_recycle()
|
|
|
|
assert calls == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_maybe_recycle_noop_when_not_due(monkeypatch):
|
|
pool = _make_pool(browser_recycle_after_contexts=10, browser_recycle_after_seconds=0)
|
|
pool._browser_started_at = asyncio.get_running_loop().time()
|
|
pool._contexts_since_start = 3
|
|
|
|
calls: list[str] = []
|
|
monkeypatch.setattr(pool, "close", lambda: calls.append("close"))
|
|
monkeypatch.setattr(pool, "start", lambda: calls.append("start"))
|
|
|
|
await pool._maybe_recycle()
|
|
|
|
assert calls == []
|