From 00c407198df68279e5e4dabcb6b6e44f4714dd55 Mon Sep 17 00:00:00 2001 From: AsahinaYui <250626441+AsahinaYui@users.noreply.github.com> Date: Wed, 5 Aug 2026 14:16:38 +0800 Subject: [PATCH] fix: prevent unknown plugin reload from reloading all --- astrbot/core/star/star_manager.py | 5 +- tests/test_plugin_manager.py | 84 +++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index 9d054af98b..1f4a7496a5 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -1022,12 +1022,15 @@ async def reload(self, specified_plugin_name=None): """ async with self._pm_lock: specified_module_path = None - if specified_plugin_name: + if specified_plugin_name is not None: for smd in star_registry: if smd.name == specified_plugin_name: specified_module_path = smd.module_path break + if not specified_module_path: + return False, f"Plugin '{specified_plugin_name}' not found." + # 终止插件 if not specified_module_path: # 重载所有插件 diff --git a/tests/test_plugin_manager.py b/tests/test_plugin_manager.py index d6bdc6e60e..bcff899738 100644 --- a/tests/test_plugin_manager.py +++ b/tests/test_plugin_manager.py @@ -625,6 +625,90 @@ async def mock_load( assert unbound == plugin_names +@pytest.mark.asyncio +@pytest.mark.parametrize("unknown_name", ["non_existent_plugin", ""]) +async def test_reload_unknown_plugin_has_no_lifecycle_side_effects( + plugin_manager_pm: PluginManager, monkeypatch, unknown_name +): + _clear_star_runtime_state() + plugin_names = ["plugin_one", "plugin_two"] + for pname in plugin_names: + module_path = f"data.plugins.{pname}.main" + metadata = star_manager_module.StarMetadata( + name=pname, + root_dir_name=pname, + module_path=module_path, + ) + star_manager_module.star_map[module_path] = metadata + star_manager_module.star_registry.append(metadata) + + # Seed a sentinel handler so that clearing the handler registry would + # be detected by the snapshot comparison. + async def _noop_handler(): + pass + + sentinel_handler = StarHandlerMetadata( + event_type=EventType.AdapterMessageEvent, + handler_full_name="sentinel_module.sentinel_handler", + handler_name="sentinel_handler", + handler_module_path="data.plugins.sentinel.main", + handler=_noop_handler, + event_filters=[], + ) + star_manager_module.star_handlers_registry.append(sentinel_handler) + + # Snapshot registries before the call. + registry_before = list(star_manager_module.star_registry) + map_before = dict(star_manager_module.star_map) + handlers_before = list(star_manager_module.star_handlers_registry) + handlers_map_before = dict( + star_manager_module.star_handlers_registry.star_handlers_map + ) + + terminated = [] + unbound = [] + loaded = [] + + async def mock_terminate(plugin): + terminated.append(plugin.name) + + async def mock_unbind(plugin_name, plugin_module_path): + unbound.append(plugin_name) + + async def mock_load( + specified_module_path=None, + specified_dir_name=None, + ignore_version_check=False, + ): + del specified_module_path, specified_dir_name, ignore_version_check + loaded.append(True) + return True, None + + monkeypatch.setattr(plugin_manager_pm, "_terminate_plugin", mock_terminate) + monkeypatch.setattr(plugin_manager_pm, "_unbind_plugin", mock_unbind) + monkeypatch.setattr(plugin_manager_pm, "load", mock_load) + + try: + success, message = await plugin_manager_pm.reload(unknown_name) + finally: + registries_unchanged = ( + list(star_manager_module.star_registry) == registry_before + and dict(star_manager_module.star_map) == map_before + and list(star_manager_module.star_handlers_registry) == handlers_before + and dict(star_manager_module.star_handlers_registry.star_handlers_map) + == handlers_map_before + ) + _clear_star_runtime_state() + + assert success is False + assert message is not None + assert "not found" in message.lower() or "不存在" in message + assert terminated == [] + assert unbound == [] + assert loaded == [] + assert registries_unchanged + + @pytest.mark.asyncio async def test_turn_plugin_toggles_llm_tools_from_plugin_child_module( plugin_manager_pm: PluginManager,