Files
AstrBot/tests/unit/test_internal/test_lsp_client.py
2026-03-26 02:48:16 +09:00

96 lines
3.3 KiB
Python

from __future__ import annotations
import asyncio
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from astrbot._internal.protocols.lsp.client import AstrbotLspClient
@pytest.mark.asyncio
async def test_lsp_reader_task_failure_marks_client_disconnected_and_logs():
"""Test reader task failures update connection state and are logged."""
client = AstrbotLspClient()
fake_process = SimpleNamespace(stdout=MagicMock(), stdin=MagicMock())
with (
patch(
"astrbot._internal.protocols.lsp.client.anyio.open_process",
AsyncMock(return_value=fake_process),
),
patch.object(
client,
"_read_responses",
AsyncMock(side_effect=RuntimeError("reader crashed")),
),
patch.object(client, "send_request", AsyncMock(return_value={})),
patch.object(client, "send_notification", AsyncMock()),
patch("astrbot._internal.protocols.lsp.client.log") as mock_log,
):
await client.connect_to_server(["python", "fake_lsp.py"], "file:///tmp")
await asyncio.sleep(0)
reader_task = client._reader_task
assert reader_task is not None
_ = reader_task.exception()
await asyncio.sleep(0)
assert client.connected is False
mock_log.error.assert_called_once()
@pytest.mark.asyncio
async def test_lsp_connect_to_server_cancels_previous_reader_task_before_restart():
"""Test reconnect tears down an existing reader task before replacing it."""
client = AstrbotLspClient()
fake_process = SimpleNamespace(stdout=MagicMock(), stdin=MagicMock())
first_reader_cancelled = asyncio.Event()
first_reader_started = asyncio.Event()
async def first_reader() -> None:
first_reader_started.set()
try:
await asyncio.Event().wait()
except asyncio.CancelledError:
first_reader_cancelled.set()
raise
with (
patch(
"astrbot._internal.protocols.lsp.client.anyio.open_process",
AsyncMock(return_value=fake_process),
),
patch.object(client, "send_request", AsyncMock(return_value={})),
patch.object(client, "send_notification", AsyncMock()),
):
client._read_responses = first_reader # type: ignore[method-assign]
await client.connect_to_server(["python", "first_lsp.py"], "file:///tmp")
await asyncio.wait_for(first_reader_started.wait(), timeout=1)
assert client.connected is True
second_reader = AsyncMock(return_value=None)
client._read_responses = second_reader # type: ignore[method-assign]
await client.connect_to_server(["python", "second_lsp.py"], "file:///tmp")
await asyncio.sleep(0)
assert first_reader_cancelled.is_set() is True
assert client.connected is True
@pytest.mark.asyncio
async def test_lsp_stop_reader_task_does_not_await_current_task():
"""Test stopping the reader from within itself does not self-await."""
client = AstrbotLspClient()
done = asyncio.Event()
async def stop_self() -> None:
client._reader_task = asyncio.current_task()
await client._stop_reader_task()
done.set()
task = asyncio.create_task(stop_self())
await asyncio.wait_for(done.wait(), timeout=1)
await task