mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-16 17:47:06 +08:00
fix: simplify register_task completion handling (#27)
* fix: simplify register_task completion handling Remove duplicated cancellation logging in Context.register_task while keeping Future inputs compatible with asyncio.create_task semantics. Add regression coverage for coroutine, Future, cancellation, and failure paths. * fix: prioritize local src in tests_v4 Ensure tests_v4 always imports the working tree package by moving src to sys.path[0] even when another checkout or installed copy is already present.
This commit is contained in:
@@ -585,13 +585,13 @@ class Context:
|
||||
) -> asyncio.Task[Any]:
|
||||
task_desc = str(desc)
|
||||
|
||||
async def _await_future(future: asyncio.Future[Any]) -> Any:
|
||||
async def _wrap_future(future: asyncio.Future[Any]) -> Any:
|
||||
return await future
|
||||
|
||||
if isinstance(task, asyncio.Task):
|
||||
background_task = task
|
||||
elif asyncio.isfuture(task):
|
||||
background_task = asyncio.create_task(_await_future(task))
|
||||
background_task = asyncio.create_task(_wrap_future(task))
|
||||
elif asyncio.iscoroutine(task):
|
||||
background_task = asyncio.create_task(task)
|
||||
else:
|
||||
@@ -609,14 +609,6 @@ class Context:
|
||||
return
|
||||
try:
|
||||
done_task.result()
|
||||
except asyncio.CancelledError:
|
||||
debug_logger = getattr(self.logger, "debug", None)
|
||||
if callable(debug_logger):
|
||||
debug_logger(
|
||||
"SDK background task cancelled: plugin_id={} desc={}",
|
||||
self.plugin_id,
|
||||
task_desc,
|
||||
)
|
||||
except Exception:
|
||||
exception_logger = getattr(self.logger, "exception", None)
|
||||
if callable(exception_logger):
|
||||
|
||||
13
tests_v4/conftest.py
Normal file
13
tests_v4/conftest.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SRC = ROOT / "src"
|
||||
|
||||
while str(SRC) in sys.path:
|
||||
sys.path.remove(str(SRC))
|
||||
|
||||
sys.path.insert(0, str(SRC))
|
||||
97
tests_v4/test_context_register_task.py
Normal file
97
tests_v4/test_context_register_task.py
Normal file
@@ -0,0 +1,97 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
|
||||
from astrbot_sdk._testing_support import MockContext
|
||||
|
||||
|
||||
class RecordingLogger:
|
||||
def __init__(self) -> None:
|
||||
self.debug_calls: list[tuple[str, str, str]] = []
|
||||
self.exception_calls: list[tuple[str, str, str]] = []
|
||||
|
||||
def debug(self, message: str, plugin_id: str, desc: str) -> None:
|
||||
self.debug_calls.append((message, plugin_id, desc))
|
||||
|
||||
def exception(self, message: str, plugin_id: str, desc: str) -> None:
|
||||
self.exception_calls.append((message, plugin_id, desc))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_task_accepts_coroutine() -> None:
|
||||
ctx = MockContext()
|
||||
|
||||
async def background() -> str:
|
||||
await asyncio.sleep(0)
|
||||
return "done"
|
||||
|
||||
task = await ctx.register_task(background(), "coroutine")
|
||||
|
||||
assert isinstance(task, asyncio.Task)
|
||||
assert await task == "done"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_task_wraps_future_inputs() -> None:
|
||||
ctx = MockContext()
|
||||
loop = asyncio.get_running_loop()
|
||||
future: asyncio.Future[str] = loop.create_future()
|
||||
|
||||
task = await ctx.register_task(future, "future")
|
||||
future.set_result("done")
|
||||
|
||||
assert isinstance(task, asyncio.Task)
|
||||
assert task is not future
|
||||
assert await task == "done"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_task_logs_cancel_once() -> None:
|
||||
logger = RecordingLogger()
|
||||
ctx = MockContext(logger=logger)
|
||||
started = asyncio.Event()
|
||||
|
||||
async def background() -> None:
|
||||
started.set()
|
||||
await asyncio.Future()
|
||||
|
||||
task = await ctx.register_task(background(), "cancelled")
|
||||
await started.wait()
|
||||
task.cancel()
|
||||
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await task
|
||||
|
||||
assert logger.debug_calls == [
|
||||
(
|
||||
"SDK background task cancelled: plugin_id={} desc={}",
|
||||
"test-plugin",
|
||||
"cancelled",
|
||||
)
|
||||
]
|
||||
assert logger.exception_calls == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_task_logs_failures() -> None:
|
||||
logger = RecordingLogger()
|
||||
ctx = MockContext(logger=logger)
|
||||
|
||||
async def background() -> None:
|
||||
raise RuntimeError("boom")
|
||||
|
||||
task = await ctx.register_task(background(), "failing")
|
||||
|
||||
with pytest.raises(RuntimeError, match="boom"):
|
||||
await task
|
||||
|
||||
assert logger.debug_calls == []
|
||||
assert logger.exception_calls == [
|
||||
(
|
||||
"SDK background task failed: plugin_id={} desc={}",
|
||||
"test-plugin",
|
||||
"failing",
|
||||
)
|
||||
]
|
||||
Reference in New Issue
Block a user