fix:conftest和helpers添加注释

This commit is contained in:
Lishiling
2026-03-13 11:46:33 +08:00
parent 945bc3bc34
commit 19064ef8eb
2 changed files with 224 additions and 56 deletions

View File

@@ -1,8 +1,8 @@
"""Pytest shared fixtures and test bootstrap helpers."""
"""Pytest共享的fixture和测试引导辅助函数。"""
# ruff: noqa: E402
# ruff: noqa: E402 # 忽略E402模块导入顺序警告
# Test configuration
# 测试配置
import asyncio
import sys
import tempfile
@@ -13,89 +13,144 @@ from typing import Any
import pytest
# 将 src-new 加入路径 - 这使得测试可以运行,但不算"已安装"
# 将src-new目录添加到Python路径 - 这使得测试可以运行,但不算"已安装"的包
SRC_NEW_PATH = str(Path(__file__).parent.parent / "src-new")
sys.path.insert(0, SRC_NEW_PATH)
# ============================================================
# Async Configuration
# 异步测试配置
# ============================================================
@pytest.fixture(scope="session")
def event_loop() -> Generator[asyncio.AbstractEventLoop, None, None]:
"""Create an event loop for async tests."""
policy = asyncio.get_event_loop_policy()
loop = policy.new_event_loop()
yield loop
loop.close()
"""为异步测试创建事件循环。
这是一个会话级别的fixture在整个测试会话期间只创建一次事件循环。
"""
policy = asyncio.get_event_loop_policy() # 获取当前事件循环策略
loop = policy.new_event_loop() # 创建新的事件循环
yield loop # 提供事件循环给测试使用
loop.close() # 测试结束后关闭事件循环
# ============================================================
# Transport Fixtures
# 传输层Fixture用于模拟网络通信
# ============================================================
class MemoryTransport:
"""In-memory transport for testing peer communication."""
"""用于测试对等通信的内存传输模拟。
这个类模拟了两个对等方之间的通信通道,所有消息都在内存中传递,
无需实际网络连接。
"""
def __init__(self) -> None:
self._closed = asyncio.Event()
self._message_handler = None
self.partner: MemoryTransport | None = None
self._closed = asyncio.Event() # 用于跟踪传输是否已关闭
self._message_handler = None # 消息处理函数
self.partner: MemoryTransport | None = None # 通信伙伴
def set_message_handler(self, handler) -> None:
"""设置消息处理函数。
Args:
handler: 接收消息的异步函数
"""
self._message_handler = handler
async def start(self) -> None:
"""启动传输。
清除关闭状态,使传输可用。
"""
self._closed.clear()
async def stop(self) -> None:
"""停止传输。
设置关闭事件,表示传输已停止。
"""
self._closed.set()
async def wait_closed(self) -> None:
"""等待传输关闭。
阻塞直到传输完全关闭。
"""
await self._closed.wait()
async def send(self, payload: str) -> None:
"""发送消息给伙伴。
Args:
payload: 要发送的消息内容
Raises:
RuntimeError: 如果没有设置伙伴传输
"""
if self.partner is None:
raise RuntimeError("MemoryTransport 未连接 partner")
if self.partner._message_handler is not None:
await self.partner._message_handler(payload)
await self.partner._message_handler(payload) # 将消息分发给伙伴的处理函数
async def _dispatch(self, payload: str) -> None:
"""内部方法:将消息分发给本地处理函数。
Args:
payload: 接收到的消息内容
"""
if self._message_handler is not None:
await self._message_handler(payload)
@pytest.fixture
def transport_pair() -> tuple[MemoryTransport, MemoryTransport]:
"""Create a connected pair of in-memory transports."""
"""创建一对相互连接的内存传输实例。
返回的左右两个传输实例互为伙伴,可用于模拟两个对等方之间的通信。
Returns:
(left_transport, right_transport) 的元组
"""
left = MemoryTransport()
right = MemoryTransport()
left.partner = right
right.partner = left
left.partner = right # 左传输的伙伴是右传输
right.partner = left # 右传输的伙伴是左传输
return left, right
# ============================================================
# Mock/Fake Fixtures
# 模拟/Fake Fixture(用于测试的假对象)
# ============================================================
class FakeEnvManager:
"""Fake environment manager for testing."""
"""用于测试的虚假环境管理器。
模拟真实的环境管理器行为,但不执行实际的环境准备操作。
"""
def prepare_environment(self, _plugin: Any) -> Path:
"""模拟准备插件环境。
Args:
_plugin: 插件对象(未使用)
Returns:
返回当前Python解释器路径作为模拟的环境路径
"""
return Path(sys.executable)
@pytest.fixture
def fake_env_manager() -> FakeEnvManager:
"""Provide a fake environment manager."""
"""提供一个虚假的环境管理器fixture。"""
return FakeEnvManager()
# 导入需要使用的类型
from astrbot_sdk.protocol.messages import InitializeOutput, PeerInfo
from astrbot_sdk.runtime.capability_router import CapabilityRouter
from astrbot_sdk.runtime.peer import Peer
@@ -103,66 +158,111 @@ from astrbot_sdk.runtime.peer import Peer
@pytest.fixture
async def core_peer(transport_pair: tuple[MemoryTransport, MemoryTransport]) -> Peer:
"""Create a core peer with default handlers."""
left, _ = transport_pair
router = CapabilityRouter()
"""创建一个配置了默认处理函数的核心对等方。
这个fixture创建并启动一个核心角色的对等方设置了初始化和调用处理函数。
Args:
transport_pair: 传输对fixture
Returns:
已启动的核心对等方实例
"""
left, _ = transport_pair # 使用传输对中的左传输
router = CapabilityRouter() # 创建能力路由器
# 创建核心对等方
peer = Peer(
transport=left,
peer_info=PeerInfo(name="core", role="core", version="v4"),
)
# 定义初始化处理函数
async def init_handler(_message) -> InitializeOutput:
return InitializeOutput(
peer=PeerInfo(name="core", role="core", version="v4"),
capabilities=router.descriptors(),
capabilities=router.descriptors(), # 获取路由器描述的能力列表
metadata={},
)
# 定义调用处理函数
async def invoke_handler(message, token):
return await router.execute(
message.capability,
message.input,
stream=message.stream,
cancel_token=token,
request_id=message.id,
message.capability, # 要执行的能力
message.input, # 输入参数
stream=message.stream, # 是否流式输出
cancel_token=token, # 取消令牌
request_id=message.id, # 请求ID
)
# 设置处理函数
peer.set_initialize_handler(init_handler)
peer.set_invoke_handler(invoke_handler)
await peer.start()
await peer.start() # 启动对等方
yield peer
await peer.stop()
await peer.stop() # 测试结束后停止
@pytest.fixture
async def plugin_peer(transport_pair: tuple[MemoryTransport, MemoryTransport]) -> Peer:
"""Create a plugin peer connected to core."""
_, right = transport_pair
"""创建一个连接到核心的插件对等方。
这个fixture创建并启动一个插件角色的对等方。
Args:
transport_pair: 传输对fixture
Returns:
已启动的插件对等方实例
"""
_, right = transport_pair # 使用传输对中的右传输
# 创建插件对等方
peer = Peer(
transport=right,
peer_info=PeerInfo(name="plugin", role="plugin", version="v4"),
)
await peer.start()
await peer.start() # 启动对等方
yield peer
await peer.stop()
await peer.stop() # 测试结束后停止
@pytest.fixture
def temp_plugin_dir() -> Generator[Path, None, None]:
"""Create a temporary directory for plugin testing."""
"""创建用于插件测试的临时目录。
这个fixture创建一个临时目录并在测试结束后自动清理。
Yields:
临时目录的Path对象
"""
with tempfile.TemporaryDirectory() as temp_dir:
yield Path(temp_dir)
def create_test_plugin(plugin_root: Path, name: str = "test_plugin") -> None:
"""Helper to create a minimal test plugin."""
"""辅助函数:创建一个最小的测试插件。
在指定目录创建插件所需的基本文件结构:
- commands/__init__.py
- commands/sample.py包含测试命令
- requirements.txt空文件
- plugin.yaml插件配置文件
Args:
plugin_root: 插件根目录
name: 插件名称,默认为"test_plugin"
"""
# 创建commands目录和__init__.py文件
(plugin_root / "commands").mkdir(parents=True, exist_ok=True)
(plugin_root / "commands" / "__init__.py").write_text("", encoding="utf-8")
# 创建空的requirements.txt
(plugin_root / "requirements.txt").write_text("", encoding="utf-8")
# 创建插件配置文件plugin.yaml
(plugin_root / "plugin.yaml").write_text(
textwrap.dedent(
f"""\
@@ -173,7 +273,7 @@ def create_test_plugin(plugin_root: Path, name: str = "test_plugin") -> None:
author: tester
version: 0.1.0
runtime:
python: "{sys.version_info.major}.{sys.version_info.minor}"
python: "{sys.version_info.major}.{sys.version_info.minor}" # 使用当前Python版本
components:
- class: commands.sample:TestPlugin
type: command
@@ -183,6 +283,8 @@ def create_test_plugin(plugin_root: Path, name: str = "test_plugin") -> None:
),
encoding="utf-8",
)
# 创建测试命令文件sample.py
(plugin_root / "commands" / "sample.py").write_text(
textwrap.dedent(
"""\
@@ -190,9 +292,9 @@ def create_test_plugin(plugin_root: Path, name: str = "test_plugin") -> None:
class TestPlugin(Star):
@on_command("test")
@on_command("test") # 注册test命令
async def test_cmd(self, event: MessageEvent, ctx: Context):
await event.reply("test ok")
await event.reply("test ok") # 回复消息
"""
),
encoding="utf-8",
@@ -201,7 +303,16 @@ def create_test_plugin(plugin_root: Path, name: str = "test_plugin") -> None:
@pytest.fixture
def test_plugin(temp_plugin_dir: Path) -> Path:
"""Create a test plugin and return its root directory."""
"""创建一个测试插件并返回其根目录。
这个fixture使用create_test_plugin函数创建插件并返回插件目录路径。
Args:
temp_plugin_dir: 临时插件目录fixture
Returns:
包含插件的目录路径
"""
plugin_root = temp_plugin_dir / "plugins" / "test_plugin"
create_test_plugin(plugin_root)
return temp_plugin_dir / "plugins"
create_test_plugin(plugin_root) # 创建测试插件
return temp_plugin_dir / "plugins"

View File

@@ -1,4 +1,4 @@
from __future__ import annotations
from __future__ import annotations # 启用延迟类型注解求值,避免循环引用
import asyncio
from pathlib import Path
@@ -7,34 +7,91 @@ from astrbot_sdk.runtime.transport import Transport
class MemoryTransport(Transport):
"""基于内存的传输层实现,用于测试场景。
继承自Transport基类模拟两个对等方之间的通信所有消息在内存中传递
无需实际网络连接。主要用于单元测试。
"""
def __init__(self) -> None:
super().__init__()
self.partner: "MemoryTransport | None" = None
"""初始化内存传输实例。"""
super().__init__() # 调用父类初始化方法
self.partner: "MemoryTransport | None" = None # 通信伙伴可以是对等的另一个MemoryTransport实例
async def start(self) -> None:
self._closed.clear()
"""启动传输。
通过清除关闭标志使传输变为可用状态。
"""
self._closed.clear() # 清除关闭事件
async def stop(self) -> None:
self._closed.set()
"""停止传输。
设置关闭标志,表示传输已停止。
"""
self._closed.set() # 设置关闭事件
async def send(self, payload: str) -> None:
"""发送消息给伙伴。
Args:
payload: 要发送的消息内容字符串
Raises:
RuntimeError: 如果没有设置伙伴传输即self.partner为None
"""
if self.partner is None:
raise RuntimeError("MemoryTransport 未连接 partner")
# 将消息转发给伙伴的_dispatch方法进行处理
await self.partner._dispatch(payload)
def make_transport_pair() -> tuple[MemoryTransport, MemoryTransport]:
left = MemoryTransport()
right = MemoryTransport()
left.partner = right
right.partner = left
return left, right
"""创建一对相互连接的内存传输实例。
工厂函数用于创建两个互为通信伙伴的MemoryTransport实例
简化测试设置过程。
Returns:
tuple[MemoryTransport, MemoryTransport]: 返回左右两个传输实例的元组,
它们已互相设置为伙伴关系
"""
left = MemoryTransport() # 创建左侧传输实例
right = MemoryTransport() # 创建右侧传输实例
left.partner = right # 设置左侧的伙伴为右侧
right.partner = left # 设置右侧的伙伴为左侧
return left, right # 返回配对的传输实例
class FakeEnvManager:
"""虚假的环境管理器,用于测试。
模拟真实环境管理器的行为,但不执行实际的环境准备操作,
主要用于需要环境管理器但又不希望产生副作用的测试场景。
"""
def prepare_environment(self, _plugin) -> Path:
"""模拟准备插件环境的方法。
不实际创建虚拟环境而是返回当前Python解释器路径作为模拟的环境路径。
Args:
_plugin: 插件对象参数,在模拟实现中未使用
Returns:
Path: 当前Python解释器的路径
"""
# 动态导入sys模块并返回可执行文件路径
return Path(__import__("sys").executable)
async def drain_loop() -> None:
await asyncio.sleep(0.05)
"""清空事件循环的辅助函数。
通过短暂的异步睡眠,让事件循环有机会处理所有已排队的任务和回调。
常用于测试中等待异步操作完成,确保所有待处理的事件被处理。
睡眠时间(0.05秒)足够短不会明显减慢测试,又足够长让事件循环有机会处理任务。
"""
await asyncio.sleep(0.05) # 暂停当前协程50毫秒让出控制权给事件循环