From f52abc831493ee88644161fa21e0320c28ab6497 Mon Sep 17 00:00:00 2001
From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
Date: Tue, 30 May 2023 15:20:31 +0800
Subject: [PATCH] =?UTF-8?q?:sparkles:=20Feature:=20=E4=BC=98=E5=8C=96?=
=?UTF-8?q?=E4=BA=8B=E4=BB=B6=E5=88=86=E5=8F=91=E6=96=B9=E6=B3=95=20(#2067?=
=?UTF-8?q?)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
nonebot/message.py | 444 +++++++++++++++++++++-------
nonebot/utils.py | 4 +-
tests/conftest.py | 6 +-
tests/examples/weather.py | 29 --
tests/test_broadcast.py | 388 ++++++++++++++++++++++++
tests/test_examples/test_weather.py | 76 -----
tests/test_matcher/test_matcher.py | 8 +-
tests/test_plugin/test_load.py | 4 +-
8 files changed, 728 insertions(+), 231 deletions(-)
delete mode 100644 tests/examples/weather.py
create mode 100644 tests/test_broadcast.py
delete mode 100644 tests/test_examples/test_weather.py
diff --git a/nonebot/message.py b/nonebot/message.py
index 28dc2edb..9561005e 100644
--- a/nonebot/message.py
+++ b/nonebot/message.py
@@ -80,7 +80,10 @@ RUN_POSTPCS_PARAMS = (
def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
- """事件预处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。"""
+ """事件预处理。
+
+ 装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。
+ """
_event_preprocessors.add(
Dependent[Any].parse(call=func, allow_types=EVENT_PCS_PARAMS)
)
@@ -88,7 +91,10 @@ def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor:
- """事件后处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。"""
+ """事件后处理。
+
+ 装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。
+ """
_event_postprocessors.add(
Dependent[Any].parse(call=func, allow_types=EVENT_PCS_PARAMS)
)
@@ -96,7 +102,10 @@ def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor:
def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor:
- """运行预处理。装饰一个函数,使它在每次事件响应器运行前执行。"""
+ """运行预处理。
+
+ 装饰一个函数,使它在每次事件响应器运行前执行。
+ """
_run_preprocessors.add(
Dependent[Any].parse(call=func, allow_types=RUN_PREPCS_PARAMS)
)
@@ -104,13 +113,222 @@ def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor:
def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor:
- """运行后处理。装饰一个函数,使它在每次事件响应器运行后执行。"""
+ """运行后处理。
+
+ 装饰一个函数,使它在每次事件响应器运行后执行。
+ """
_run_postprocessors.add(
Dependent[Any].parse(call=func, allow_types=RUN_POSTPCS_PARAMS)
)
return func
+async def _apply_event_preprocessors(
+ bot: "Bot",
+ event: "Event",
+ state: T_State,
+ stack: Optional[AsyncExitStack] = None,
+ dependency_cache: Optional[T_DependencyCache] = None,
+ show_log: bool = True,
+) -> bool:
+ """运行事件预处理。
+
+ 参数:
+ bot: Bot 对象
+ event: Event 对象
+ state: 会话状态
+ stack: 异步上下文栈
+ dependency_cache: 依赖缓存
+ show_log: 是否显示日志
+
+ 返回:
+ 是否继续处理事件
+ """
+ if not _event_preprocessors:
+ return True
+
+ if show_log:
+ logger.debug("Running PreProcessors...")
+
+ try:
+ await asyncio.gather(
+ *(
+ run_coro_with_catch(
+ proc(
+ bot=bot,
+ event=event,
+ state=state,
+ stack=stack,
+ dependency_cache=dependency_cache,
+ ),
+ (SkippedException,),
+ )
+ for proc in _event_preprocessors
+ )
+ )
+ except IgnoredException as e:
+ logger.opt(colors=True).info(
+ f"Event {escape_tag(event.get_event_name())} is ignored"
+ )
+ return False
+ except Exception as e:
+ logger.opt(colors=True, exception=e).error(
+ "Error when running EventPreProcessors. "
+ "Event ignored!"
+ )
+ return False
+
+ return True
+
+
+async def _apply_event_postprocessors(
+ bot: "Bot",
+ event: "Event",
+ state: T_State,
+ stack: Optional[AsyncExitStack] = None,
+ dependency_cache: Optional[T_DependencyCache] = None,
+ show_log: bool = True,
+) -> None:
+ """运行事件后处理。
+
+ 参数:
+ bot: Bot 对象
+ event: Event 对象
+ state: 会话状态
+ stack: 异步上下文栈
+ dependency_cache: 依赖缓存
+ show_log: 是否显示日志
+ """
+ if not _event_postprocessors:
+ return
+
+ if show_log:
+ logger.debug("Running PostProcessors...")
+
+ try:
+ await asyncio.gather(
+ *(
+ run_coro_with_catch(
+ proc(
+ bot=bot,
+ event=event,
+ state=state,
+ stack=stack,
+ dependency_cache=dependency_cache,
+ ),
+ (SkippedException,),
+ )
+ for proc in _event_postprocessors
+ )
+ )
+ except Exception as e:
+ logger.opt(colors=True, exception=e).error(
+ "Error when running EventPostProcessors"
+ )
+
+
+async def _apply_run_preprocessors(
+ bot: "Bot",
+ event: "Event",
+ state: T_State,
+ matcher: Matcher,
+ stack: Optional[AsyncExitStack] = None,
+ dependency_cache: Optional[T_DependencyCache] = None,
+) -> bool:
+ """运行事件响应器运行前处理。
+
+ 参数:
+ bot: Bot 对象
+ event: Event 对象
+ state: 会话状态
+ matcher: 事件响应器
+ stack: 异步上下文栈
+ dependency_cache: 依赖缓存
+
+ 返回:
+ 是否继续处理事件
+ """
+ if not _run_preprocessors:
+ return True
+
+ # ensure matcher function can be correctly called
+ with matcher.ensure_context(bot, event):
+ try:
+ await asyncio.gather(
+ *(
+ run_coro_with_catch(
+ proc(
+ matcher=matcher,
+ bot=bot,
+ event=event,
+ state=state,
+ stack=stack,
+ dependency_cache=dependency_cache,
+ ),
+ (SkippedException,),
+ )
+ for proc in _run_preprocessors
+ )
+ )
+ except IgnoredException:
+ logger.opt(colors=True).info(f"{matcher} running is cancelled")
+ return False
+ except Exception as e:
+ logger.opt(colors=True, exception=e).error(
+ "Error when running RunPreProcessors. "
+ "Running cancelled!"
+ )
+ return False
+
+ return True
+
+
+async def _apply_run_postprocessors(
+ bot: "Bot",
+ event: "Event",
+ matcher: Matcher,
+ exception: Optional[Exception] = None,
+ stack: Optional[AsyncExitStack] = None,
+ dependency_cache: Optional[T_DependencyCache] = None,
+) -> None:
+ """运行事件响应器运行后处理。
+
+ Args:
+ bot: Bot 对象
+ event: Event 对象
+ matcher: 事件响应器
+ exception: 事件响应器运行异常
+ stack: 异步上下文栈
+ dependency_cache: 依赖缓存
+ """
+ if not _run_postprocessors:
+ return
+
+ with matcher.ensure_context(bot, event):
+ try:
+ await asyncio.gather(
+ *(
+ run_coro_with_catch(
+ proc(
+ matcher=matcher,
+ exception=exception,
+ bot=bot,
+ event=event,
+ state=matcher.state,
+ stack=stack,
+ dependency_cache=dependency_cache,
+ ),
+ (SkippedException,),
+ )
+ for proc in _run_postprocessors
+ )
+ )
+ except Exception as e:
+ logger.opt(colors=True, exception=e).error(
+ "Error when running RunPostProcessors"
+ )
+
+
async def _check_matcher(
Matcher: Type[Matcher],
bot: "Bot",
@@ -118,27 +336,39 @@ async def _check_matcher(
state: T_State,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
-) -> None:
+) -> bool:
+ """检查事件响应器是否符合运行条件。
+
+ 请注意,过时的事件响应器将被**销毁**。对于未过时的事件响应器,将会一次检查其响应类型、权限和规则。
+
+ 参数:
+ Matcher: 要检查的事件响应器
+ bot: Bot 对象
+ event: Event 对象
+ state: 会话状态
+ stack: 异步上下文栈
+ dependency_cache: 依赖缓存
+
+ 返回:
+ bool: 是否符合运行条件
+ """
if Matcher.expire_time and datetime.now() > Matcher.expire_time:
with contextlib.suppress(Exception):
Matcher.destroy()
- return
+ return False
try:
if not await Matcher.check_perm(
bot, event, stack, dependency_cache
) or not await Matcher.check_rule(bot, event, state, stack, dependency_cache):
- return
+ return False
except Exception as e:
logger.opt(colors=True, exception=e).error(
f"Rule check failed for {Matcher}."
)
- return
+ return False
- if Matcher.temp:
- with contextlib.suppress(Exception):
- Matcher.destroy()
- await _run_matcher(Matcher, bot, event, state, stack, dependency_cache)
+ return True
async def _run_matcher(
@@ -149,36 +379,38 @@ async def _run_matcher(
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None,
) -> None:
+ """运行事件响应器。
+
+ 临时事件响应器将在运行前被**销毁**。
+
+ 参数:
+ Matcher: 事件响应器
+ bot: Bot 对象
+ event: Event 对象
+ state: 会话状态
+ stack: 异步上下文栈
+ dependency_cache: 依赖缓存
+
+ 异常:
+ StopPropagation: 阻止事件继续传播
+ """
logger.info(f"Event will be handled by {Matcher}")
- matcher = Matcher()
- if coros := [
- run_coro_with_catch(
- proc(
- matcher=matcher,
- bot=bot,
- event=event,
- state=state,
- stack=stack,
- dependency_cache=dependency_cache,
- ),
- (SkippedException,),
- )
- for proc in _run_preprocessors
- ]:
- # ensure matcher function can be correctly called
- with matcher.ensure_context(bot, event):
- try:
- await asyncio.gather(*coros)
- except IgnoredException:
- logger.opt(colors=True).info(f"{matcher} running is cancelled")
- return
- except Exception as e:
- logger.opt(colors=True, exception=e).error(
- "Error when running RunPreProcessors. Running cancelled!"
- )
+ if Matcher.temp:
+ with contextlib.suppress(Exception):
+ Matcher.destroy()
- return
+ matcher = Matcher()
+
+ if not await _apply_run_preprocessors(
+ bot=bot,
+ event=event,
+ state=state,
+ matcher=matcher,
+ stack=stack,
+ dependency_cache=dependency_cache,
+ ):
+ return
exception = None
@@ -191,33 +423,55 @@ async def _run_matcher(
)
exception = e
- if coros := [
- run_coro_with_catch(
- proc(
- matcher=matcher,
- exception=exception,
- bot=bot,
- event=event,
- state=matcher.state,
- stack=stack,
- dependency_cache=dependency_cache,
- ),
- (SkippedException,),
- )
- for proc in _run_postprocessors
- ]:
- # ensure matcher function can be correctly called
- with matcher.ensure_context(bot, event):
- try:
- await asyncio.gather(*coros)
- except Exception as e:
- logger.opt(colors=True, exception=e).error(
- "Error when running RunPostProcessors"
- )
+ await _apply_run_postprocessors(
+ bot=bot,
+ event=event,
+ matcher=matcher,
+ exception=exception,
+ stack=stack,
+ dependency_cache=dependency_cache,
+ )
if matcher.block:
raise StopPropagation
- return
+
+
+async def check_and_run_matcher(
+ Matcher: Type[Matcher],
+ bot: "Bot",
+ event: "Event",
+ state: T_State,
+ stack: Optional[AsyncExitStack] = None,
+ dependency_cache: Optional[T_DependencyCache] = None,
+) -> None:
+ """检查并运行事件响应器。
+
+ 参数:
+ Matcher: 事件响应器
+ bot: Bot 对象
+ event: Event 对象
+ state: 会话状态
+ stack: 异步上下文栈
+ dependency_cache: 依赖缓存
+ """
+ if not await _check_matcher(
+ Matcher=Matcher,
+ bot=bot,
+ event=event,
+ state=state,
+ stack=stack,
+ dependency_cache=dependency_cache,
+ ):
+ return
+
+ await _run_matcher(
+ Matcher=Matcher,
+ bot=bot,
+ event=event,
+ state=state,
+ stack=stack,
+ dependency_cache=dependency_cache,
+ )
async def handle_event(bot: "Bot", event: "Event") -> None:
@@ -245,35 +499,16 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
state: Dict[Any, Any] = {}
dependency_cache: T_DependencyCache = {}
+ # create event scope context
async with AsyncExitStack() as stack:
- if coros := [
- run_coro_with_catch(
- proc(
- bot=bot,
- event=event,
- state=state,
- stack=stack,
- dependency_cache=dependency_cache,
- ),
- (SkippedException,),
- )
- for proc in _event_preprocessors
- ]:
- try:
- if show_log:
- logger.debug("Running PreProcessors...")
- await asyncio.gather(*coros)
- except IgnoredException as e:
- logger.opt(colors=True).info(
- f"Event {escape_tag(event.get_event_name())} is ignored"
- )
- return
- except Exception as e:
- logger.opt(colors=True, exception=e).error(
- "Error when running EventPreProcessors. "
- "Event ignored!"
- )
- return
+ if not await _apply_event_preprocessors(
+ bot=bot,
+ event=event,
+ state=state,
+ stack=stack,
+ dependency_cache=dependency_cache,
+ ):
+ return
# Trie Match
try:
@@ -284,6 +519,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
)
break_flag = False
+ # iterate through all priority until stop propagation
for priority in sorted(matchers.keys()):
if break_flag:
break
@@ -292,14 +528,12 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
logger.debug(f"Checking for matchers in priority {priority}...")
pending_tasks = [
- _check_matcher(
+ check_and_run_matcher(
matcher, bot, event, state.copy(), stack, dependency_cache
)
for matcher in matchers[priority]
]
-
results = await asyncio.gather(*pending_tasks, return_exceptions=True)
-
for result in results:
if not isinstance(result, Exception):
continue
@@ -314,24 +548,4 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
if show_log:
logger.debug("Checking for matchers completed")
- if coros := [
- run_coro_with_catch(
- proc(
- bot=bot,
- event=event,
- state=state,
- stack=stack,
- dependency_cache=dependency_cache,
- ),
- (SkippedException,),
- )
- for proc in _event_postprocessors
- ]:
- try:
- if show_log:
- logger.debug("Running PostProcessors...")
- await asyncio.gather(*coros)
- except Exception as e:
- logger.opt(colors=True, exception=e).error(
- "Error when running EventPostProcessors"
- )
+ await _apply_event_postprocessors(bot, event, state, stack, dependency_cache)
diff --git a/nonebot/utils.py b/nonebot/utils.py
index 9391a507..54e7d052 100644
--- a/nonebot/utils.py
+++ b/nonebot/utils.py
@@ -113,8 +113,7 @@ def run_sync(call: Callable[P, R]) -> Callable[P, Coroutine[None, None, R]]:
loop = asyncio.get_running_loop()
pfunc = partial(call, *args, **kwargs)
context = copy_context()
- context_run = context.run
- result = await loop.run_in_executor(None, context_run, pfunc)
+ result = await loop.run_in_executor(None, partial(context.run, pfunc))
return result
return _wrapper
@@ -139,6 +138,7 @@ async def run_sync_ctx_manager(
async def run_coro_with_catch(
coro: Coroutine[Any, Any, T],
exc: Tuple[Type[Exception], ...],
+ return_on_err: None = None,
) -> Union[T, None]:
...
diff --git a/tests/conftest.py b/tests/conftest.py
index b1a31cac..a25efd85 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -25,6 +25,6 @@ def load_plugin(nonebug_init: None) -> Set["Plugin"]:
@pytest.fixture(scope="session", autouse=True)
-def load_example(nonebug_init: None) -> Set["Plugin"]:
- # preload example plugins
- return nonebot.load_plugins(str(Path(__file__).parent / "examples"))
+def load_builtin_plugin(nonebug_init: None) -> Set["Plugin"]:
+ # preload builtin plugins
+ return nonebot.load_builtin_plugins("echo", "single_session")
diff --git a/tests/examples/weather.py b/tests/examples/weather.py
deleted file mode 100644
index ec0459f0..00000000
--- a/tests/examples/weather.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from nonebot import on_command
-from nonebot.rule import to_me
-from nonebot.matcher import Matcher
-from nonebot.adapters import Message
-from nonebot.params import Arg, CommandArg, ArgPlainText
-
-weather = on_command("weather", rule=to_me(), aliases={"天气", "天气预报"}, priority=5)
-
-
-@weather.handle()
-async def handle_first_receive(matcher: Matcher, args: Message = CommandArg()):
- plain_text = args.extract_plain_text() # 首次发送命令时跟随的参数,例:/天气 上海,则args为上海
- if plain_text:
- matcher.set_arg("city", args) # 如果用户发送了参数则直接赋值
-
-
-@weather.got("city", prompt="你想查询哪个城市的天气呢?")
-async def handle_city(city: Message = Arg(), city_name: str = ArgPlainText("city")):
- if city_name not in ["北京", "上海"]: # 如果参数不符合要求,则提示用户重新输入
- # 可以使用平台的 Message 类直接构造模板消息
- await weather.reject(city.template("你想查询的城市 {city} 暂不支持,请重新输入!"))
-
- city_weather = await get_weather(city_name)
- await weather.finish(city_weather)
-
-
-# 在这里编写获取天气信息的函数
-async def get_weather(city: str) -> str:
- return f"{city}的天气是..."
diff --git a/tests/test_broadcast.py b/tests/test_broadcast.py
new file mode 100644
index 00000000..4814761e
--- /dev/null
+++ b/tests/test_broadcast.py
@@ -0,0 +1,388 @@
+import sys
+from typing import Optional
+
+import pytest
+from nonebug import App
+
+from nonebot import on_message
+import nonebot.message as message
+from utils import make_fake_event
+from nonebot.params import Depends
+from nonebot.typing import T_State
+from nonebot.matcher import Matcher
+from nonebot.adapters import Bot, Event
+from nonebot.exception import IgnoredException
+from nonebot.log import logger, default_filter, default_format
+from nonebot.message import (
+ run_preprocessor,
+ run_postprocessor,
+ event_preprocessor,
+ event_postprocessor,
+)
+
+
+async def _dependency() -> int:
+ return 1
+
+
+@pytest.mark.asyncio
+async def test_event_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
+ with monkeypatch.context() as m:
+ m.setattr(message, "_event_preprocessors", set())
+
+ runned = False
+
+ @event_preprocessor
+ async def test_preprocessor(
+ bot: Bot,
+ event: Event,
+ state: T_State,
+ sub: int = Depends(_dependency),
+ default: int = 1,
+ ):
+ nonlocal runned
+ runned = True
+
+ assert test_preprocessor in {
+ dependent.call for dependent in message._event_preprocessors
+ }
+
+ with app.provider.context({}):
+ matcher = on_message()
+
+ async with app.test_matcher(matcher) as ctx:
+ bot = ctx.create_bot()
+ event = make_fake_event()()
+ ctx.receive_event(bot, event)
+
+ assert runned, "event_preprocessor should runned"
+
+
+@pytest.mark.asyncio
+async def test_event_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
+ with monkeypatch.context() as m:
+ m.setattr(message, "_event_preprocessors", set())
+
+ @event_preprocessor
+ async def test_preprocessor():
+ raise IgnoredException("pass")
+
+ assert test_preprocessor in {
+ dependent.call for dependent in message._event_preprocessors
+ }
+
+ runned = False
+
+ async def handler():
+ nonlocal runned
+ runned = True
+
+ with app.provider.context({}):
+ matcher = on_message(handlers=[handler])
+
+ async with app.test_matcher(matcher) as ctx:
+ bot = ctx.create_bot()
+ event = make_fake_event()()
+ ctx.receive_event(bot, event)
+
+ assert not runned, "matcher should not runned"
+
+
+@pytest.mark.asyncio
+async def test_event_preprocessor_exception(
+ app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
+):
+ with monkeypatch.context() as m:
+ m.setattr(message, "_event_preprocessors", set())
+
+ @event_preprocessor
+ async def test_preprocessor():
+ raise RuntimeError("test")
+
+ assert test_preprocessor in {
+ dependent.call for dependent in message._event_preprocessors
+ }
+
+ runned = False
+
+ async def handler():
+ nonlocal runned
+ runned = True
+
+ handler_id = logger.add(
+ sys.stdout,
+ level=0,
+ diagnose=False,
+ filter=default_filter,
+ format=default_format,
+ )
+
+ try:
+ with app.provider.context({}):
+ matcher = on_message(handlers=[handler])
+
+ async with app.test_matcher(matcher) as ctx:
+ bot = ctx.create_bot()
+ event = make_fake_event()()
+ ctx.receive_event(bot, event)
+ finally:
+ logger.remove(handler_id)
+
+ assert not runned, "matcher should not runned"
+ assert "RuntimeError: test" in capsys.readouterr().out
+
+
+@pytest.mark.asyncio
+async def test_event_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
+ with monkeypatch.context() as m:
+ m.setattr(message, "_event_postprocessors", set())
+
+ runned = False
+
+ @event_postprocessor
+ async def test_postprocessor(
+ bot: Bot,
+ event: Event,
+ state: T_State,
+ sub: int = Depends(_dependency),
+ default: int = 1,
+ ):
+ nonlocal runned
+ runned = True
+
+ assert test_postprocessor in {
+ dependent.call for dependent in message._event_postprocessors
+ }
+
+ with app.provider.context({}):
+ matcher = on_message()
+
+ async with app.test_matcher(matcher) as ctx:
+ bot = ctx.create_bot()
+ event = make_fake_event()()
+ ctx.receive_event(bot, event)
+
+ assert runned, "event_postprocessor should runned"
+
+
+@pytest.mark.asyncio
+async def test_event_postprocessor_exception(
+ app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
+):
+ with monkeypatch.context() as m:
+ m.setattr(message, "_event_postprocessors", set())
+
+ @event_postprocessor
+ async def test_postprocessor():
+ raise RuntimeError("test")
+
+ assert test_postprocessor in {
+ dependent.call for dependent in message._event_postprocessors
+ }
+
+ handler_id = logger.add(
+ sys.stdout,
+ level=0,
+ diagnose=False,
+ filter=default_filter,
+ format=default_format,
+ )
+
+ try:
+ with app.provider.context({}):
+ matcher = on_message()
+
+ async with app.test_matcher(matcher) as ctx:
+ bot = ctx.create_bot()
+ event = make_fake_event()()
+ ctx.receive_event(bot, event)
+ finally:
+ logger.remove(handler_id)
+
+ assert "RuntimeError: test" in capsys.readouterr().out
+
+
+@pytest.mark.asyncio
+async def test_run_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
+ with monkeypatch.context() as m:
+ m.setattr(message, "_run_preprocessors", set())
+
+ runned = False
+
+ @run_preprocessor
+ async def test_preprocessor(
+ bot: Bot,
+ event: Event,
+ state: T_State,
+ matcher: Matcher,
+ sub: int = Depends(_dependency),
+ default: int = 1,
+ ):
+ nonlocal runned
+ runned = True
+
+ await matcher.send("test")
+
+ assert test_preprocessor in {
+ dependent.call for dependent in message._run_preprocessors
+ }
+
+ with app.provider.context({}):
+ matcher = on_message()
+
+ async with app.test_matcher(matcher) as ctx:
+ bot = ctx.create_bot()
+ event = make_fake_event()()
+ ctx.receive_event(bot, event)
+ ctx.should_call_send(event, "test", True, bot)
+
+ assert runned, "run_preprocessor should runned"
+
+
+@pytest.mark.asyncio
+async def test_run_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
+ with monkeypatch.context() as m:
+ m.setattr(message, "_run_preprocessors", set())
+
+ @run_preprocessor
+ async def test_preprocessor():
+ raise IgnoredException("pass")
+
+ assert test_preprocessor in {
+ dependent.call for dependent in message._run_preprocessors
+ }
+
+ runned = False
+
+ async def handler():
+ nonlocal runned
+ runned = True
+
+ with app.provider.context({}):
+ matcher = on_message(handlers=[handler])
+
+ async with app.test_matcher(matcher) as ctx:
+ bot = ctx.create_bot()
+ event = make_fake_event()()
+ ctx.receive_event(bot, event)
+
+ assert not runned, "matcher should not runned"
+
+
+@pytest.mark.asyncio
+async def test_run_preprocessor_exception(
+ app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
+):
+ with monkeypatch.context() as m:
+ m.setattr(message, "_run_preprocessors", set())
+
+ @run_preprocessor
+ async def test_preprocessor():
+ raise RuntimeError("test")
+
+ assert test_preprocessor in {
+ dependent.call for dependent in message._run_preprocessors
+ }
+
+ runned = False
+
+ async def handler():
+ nonlocal runned
+ runned = True
+
+ handler_id = logger.add(
+ sys.stdout,
+ level=0,
+ diagnose=False,
+ filter=default_filter,
+ format=default_format,
+ )
+
+ try:
+ with app.provider.context({}):
+ matcher = on_message(handlers=[handler])
+
+ async with app.test_matcher(matcher) as ctx:
+ bot = ctx.create_bot()
+ event = make_fake_event()()
+ ctx.receive_event(bot, event)
+ finally:
+ logger.remove(handler_id)
+
+ assert not runned, "matcher should not runned"
+ assert "RuntimeError: test" in capsys.readouterr().out
+
+
+@pytest.mark.asyncio
+async def test_run_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
+ with monkeypatch.context() as m:
+ m.setattr(message, "_run_postprocessors", set())
+
+ runned = False
+
+ @run_postprocessor
+ async def test_postprocessor(
+ bot: Bot,
+ event: Event,
+ state: T_State,
+ matcher: Matcher,
+ exception: Optional[Exception],
+ sub: int = Depends(_dependency),
+ default: int = 1,
+ ):
+ nonlocal runned
+ runned = True
+
+ await matcher.send("test")
+
+ assert test_postprocessor in {
+ dependent.call for dependent in message._run_postprocessors
+ }
+
+ with app.provider.context({}):
+ matcher = on_message()
+
+ async with app.test_matcher(matcher) as ctx:
+ bot = ctx.create_bot()
+ event = make_fake_event()()
+ ctx.receive_event(bot, event)
+ ctx.should_call_send(event, "test", True, bot)
+
+ assert runned, "run_postprocessor should runned"
+
+
+@pytest.mark.asyncio
+async def test_run_postprocessor_exception(
+ app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
+):
+ with monkeypatch.context() as m:
+ m.setattr(message, "_run_postprocessors", set())
+
+ @run_postprocessor
+ async def test_postprocessor():
+ raise RuntimeError("test")
+
+ assert test_postprocessor in {
+ dependent.call for dependent in message._run_postprocessors
+ }
+
+ handler_id = logger.add(
+ sys.stdout,
+ level=0,
+ diagnose=False,
+ filter=default_filter,
+ format=default_format,
+ )
+
+ try:
+ with app.provider.context({}):
+ matcher = on_message()
+
+ async with app.test_matcher(matcher) as ctx:
+ bot = ctx.create_bot()
+ event = make_fake_event()()
+ ctx.receive_event(bot, event)
+ finally:
+ logger.remove(handler_id)
+
+ assert "RuntimeError: test" in capsys.readouterr().out
diff --git a/tests/test_examples/test_weather.py b/tests/test_examples/test_weather.py
deleted file mode 100644
index 437ad1b7..00000000
--- a/tests/test_examples/test_weather.py
+++ /dev/null
@@ -1,76 +0,0 @@
-import pytest
-from nonebug import App
-
-from utils import make_fake_event, make_fake_message
-
-
-@pytest.mark.asyncio
-async def test_weather(app: App):
- from examples.weather import weather
-
- # 将此处的 make_fake_message() 替换为你要发送的平台消息 Message 类型
- # from nonebot.adapters.console import Message
- Message = make_fake_message()
-
- async with app.test_matcher(weather) as ctx:
- bot = ctx.create_bot()
-
- msg = Message("/天气 上海")
- # 将此处的 make_fake_event() 替换为你要发送的平台事件 Event 类型
- # from nonebot.adapters.console import MessageEvent
- # event = MessageEvent(message=msg, to_me=True, ...)
- event = make_fake_event(_message=msg, _to_me=True)()
-
- ctx.receive_event(bot, event)
- ctx.should_call_send(event, "上海的天气是...", True)
- ctx.should_finished()
-
- async with app.test_matcher(weather) as ctx:
- bot = ctx.create_bot()
-
- msg = Message("/天气 南京")
- # 将此处的 make_fake_event() 替换为你要发送的平台事件 Event 类型
- event = make_fake_event(_message=msg, _to_me=True)()
-
- ctx.receive_event(bot, event)
- ctx.should_call_send(
- event,
- Message.template("你想查询的城市 {} 暂不支持,请重新输入!").format("南京"),
- True,
- )
- ctx.should_rejected()
-
- msg = Message("北京")
- event = make_fake_event(_message=msg)()
-
- ctx.receive_event(bot, event)
- ctx.should_call_send(event, "北京的天气是...", True)
- ctx.should_finished()
-
- async with app.test_matcher(weather) as ctx:
- bot = ctx.create_bot()
-
- msg = Message("/天气")
- # 将此处的 make_fake_event() 替换为你要发送的平台事件 Event 类型
- event = make_fake_event(_message=msg, _to_me=True)()
-
- ctx.receive_event(bot, event)
- ctx.should_call_send(event, "你想查询哪个城市的天气呢?", True)
-
- msg = Message("杭州")
- event = make_fake_event(_message=msg)()
-
- ctx.receive_event(bot, event)
- ctx.should_call_send(
- event,
- Message.template("你想查询的城市 {} 暂不支持,请重新输入!").format("杭州"),
- True,
- )
- ctx.should_rejected()
-
- msg = Message("北京")
- event = make_fake_event(_message=msg)()
-
- ctx.receive_event(bot, event)
- ctx.should_call_send(event, "北京的天气是...", True)
- ctx.should_finished()
diff --git a/tests/test_matcher/test_matcher.py b/tests/test_matcher/test_matcher.py
index ed6c0957..9433e74b 100644
--- a/tests/test_matcher/test_matcher.py
+++ b/tests/test_matcher/test_matcher.py
@@ -2,8 +2,8 @@ import pytest
from nonebug import App
from nonebot.permission import User
-from nonebot.message import _check_matcher
from nonebot.matcher import Matcher, matchers
+from nonebot.message import check_and_run_matcher
from utils import make_fake_event, make_fake_message
@@ -200,19 +200,19 @@ async def test_expire(app: App):
async with app.test_api() as ctx:
bot = ctx.create_bot()
assert test_temp_matcher in matchers[test_temp_matcher.priority]
- await _check_matcher(test_temp_matcher, bot, event, {})
+ await check_and_run_matcher(test_temp_matcher, bot, event, {})
assert test_temp_matcher not in matchers[test_temp_matcher.priority]
event = make_fake_event()()
async with app.test_api() as ctx:
bot = ctx.create_bot()
assert test_datetime_matcher in matchers[test_datetime_matcher.priority]
- await _check_matcher(test_datetime_matcher, bot, event, {})
+ await check_and_run_matcher(test_datetime_matcher, bot, event, {})
assert test_datetime_matcher not in matchers[test_datetime_matcher.priority]
event = make_fake_event()()
async with app.test_api() as ctx:
bot = ctx.create_bot()
assert test_timedelta_matcher in matchers[test_timedelta_matcher.priority]
- await _check_matcher(test_timedelta_matcher, bot, event, {})
+ await check_and_run_matcher(test_timedelta_matcher, bot, event, {})
assert test_timedelta_matcher not in matchers[test_timedelta_matcher.priority]
diff --git a/tests/test_plugin/test_load.py b/tests/test_plugin/test_load.py
index a0eb15ab..177ca486 100644
--- a/tests/test_plugin/test_load.py
+++ b/tests/test_plugin/test_load.py
@@ -22,11 +22,11 @@ async def test_load_plugin():
@pytest.mark.asyncio
-async def test_load_plugins(load_plugin: Set[Plugin], load_example: Set[Plugin]):
+async def test_load_plugins(load_plugin: Set[Plugin], load_builtin_plugin: Set[Plugin]):
loaded_plugins = {
plugin for plugin in nonebot.get_loaded_plugins() if not plugin.parent_plugin
}
- assert loaded_plugins >= load_plugin | load_example
+ assert loaded_plugins >= load_plugin | load_builtin_plugin
# check simple plugin
assert "plugins.export" in sys.modules