feat: 插件自动挂载测试机制 — capabilities + self_test + 3阶段启动

- Plugin trait 增加 capabilities() 和 self_test() 方法
- PluginVTable 增加 get_capabilities 和 self_test FFI
- ServiceManager 三阶段启动: init → self_test → start
- SendCallback 改为 ctx 参数传递,消除 thread_local
- export_plugin! 宏所有 FFI 函数包裹 catch_unwind
- PluginManifest 增加 capabilities/required_capabilities/auto_test
- 新增 3 个自测相关测试用例 (共 59 测试)
This commit is contained in:
showen
2026-03-13 04:31:39 +08:00
parent 1863efb0f5
commit 99ee78984c
9 changed files with 694 additions and 123 deletions

View File

@@ -1,6 +1,6 @@
use crate::core::config::AppConfig;
use crate::core::message::{Destination, Envelope, Message};
use crate::core::plugin::{Plugin, PluginContext};
use crate::core::plugin::{CapabilityTestResult, Plugin, PluginContext};
use crate::core::plugin_loader::ErrorPolicy;
use anyhow::{anyhow, Result};
use std::collections::{HashMap, HashSet};
@@ -19,6 +19,14 @@ struct PluginState {
max_errors: u32,
/// 是否启用
enabled: bool,
/// 挂载时的自测结果
test_results: Vec<CapabilityTestResult>,
/// 声明的功能列表
capabilities: Vec<String>,
/// manifest 中声明的必须通过的功能
required_capabilities: Vec<String>,
/// 是否自动测试
auto_test: bool,
}
impl PluginState {
@@ -30,14 +38,14 @@ impl PluginState {
error_count: 0,
max_errors: u32::MAX, // 静态插件不自动禁用
enabled: true,
test_results: vec![],
capabilities: vec![],
required_capabilities: vec![],
auto_test: false, // 静态插件默认不自测
}
}
fn new_dynamic(
plugin: Box<dyn Plugin>,
error_policy: ErrorPolicy,
max_errors: u32,
) -> Self {
fn new_dynamic(plugin: Box<dyn Plugin>, error_policy: ErrorPolicy, max_errors: u32) -> Self {
Self {
plugin,
is_dynamic: true,
@@ -45,6 +53,10 @@ impl PluginState {
error_count: 0,
max_errors,
enabled: true,
test_results: vec![],
capabilities: vec![],
required_capabilities: vec![],
auto_test: true,
}
}
@@ -97,6 +109,19 @@ impl ServiceManager {
plugin: Box<dyn Plugin>,
error_policy: ErrorPolicy,
max_errors: u32,
) {
self.register_dynamic_with_manifest(plugin, error_policy, max_errors, vec![], vec![], true);
}
/// 注册动态插件(带 manifest 自测信息)
pub fn register_dynamic_with_manifest(
&mut self,
plugin: Box<dyn Plugin>,
error_policy: ErrorPolicy,
max_errors: u32,
required_capabilities: Vec<String>,
capabilities: Vec<String>,
auto_test: bool,
) {
println!(
"[ServiceManager] 注册动态插件: {} (策略: {:?}, 最大错误: {})",
@@ -104,16 +129,19 @@ impl ServiceManager {
error_policy,
max_errors
);
self.plugins
.push(PluginState::new_dynamic(plugin, error_policy, max_errors));
let mut state = PluginState::new_dynamic(plugin, error_policy, max_errors);
state.required_capabilities = required_capabilities;
state.capabilities = capabilities;
state.auto_test = auto_test;
self.plugins.push(state);
}
/// 按注册顺序 init() + start() 所有插件
/// 动态插件 init/start 失败时按策略处理,不中断其他插件
/// 按注册顺序 init() → self_test() → start() 所有插件
/// 动态插件 init/start/test 失败时按策略处理,不中断其他插件
pub fn start_all(&mut self) -> Result<()> {
self.validate_and_sort_plugins()?;
// init
// Phase 1: init
for state in &mut self.plugins {
let ctx = PluginContext {
tx: self.tx.clone(),
@@ -135,7 +163,91 @@ impl ServiceManager {
}
}
// start
// Phase 2: self_test (init 之后, start 之前)
for state in &mut self.plugins {
if !state.enabled || !state.auto_test {
continue;
}
// 获取插件的功能列表并运行自测
let caps = state.plugin.capabilities();
if caps.is_empty() && state.required_capabilities.is_empty() {
// 无功能声明 → 跳过自测
continue;
}
println!(
"[ServiceManager] 自测插件: {} (功能: {:?})",
state.id(),
caps
);
state.capabilities = caps;
let results = state.plugin.self_test();
// 检查 required_capabilities 中的项是否全部通过
let mut has_required_failure = false;
let passed_caps: std::collections::HashSet<&str> = results
.iter()
.filter(|r| r.passed)
.map(|r| r.capability.as_str())
.collect();
// 检查每个 required capability 是否出现在通过列表中
for req in &state.required_capabilities {
if !passed_caps.contains(req.as_str()) {
eprintln!(
"[ServiceManager] ✗ [必须] {}{}",
req,
results
.iter()
.find(|r| r.capability == *req)
.map(|r| r.message.as_str())
.unwrap_or("未在测试结果中出现")
);
has_required_failure = true;
}
}
for result in &results {
if result.passed {
println!(
"[ServiceManager] ✓ {}{}",
result.capability, result.message
);
} else if !state.required_capabilities.contains(&result.capability) {
eprintln!(
"[ServiceManager] ✗ [可选] {}{}",
result.capability, result.message
);
}
}
state.test_results = results;
if has_required_failure {
if state.is_dynamic {
match &state.error_policy {
ErrorPolicy::AutoRollback => {
eprintln!(
"[ServiceManager] 动态插件 '{}' 必须能力自测失败,禁用 (待回退)",
state.id()
);
}
ErrorPolicy::DisableAndLog => {
eprintln!(
"[ServiceManager] 动态插件 '{}' 必须能力自测失败,禁用",
state.id()
);
}
}
state.enabled = false;
} else {
return Err(anyhow!("静态插件 '{}' 必须能力自测失败", state.id()));
}
}
}
// Phase 3: start
for state in &mut self.plugins {
if !state.enabled {
continue;
@@ -198,11 +310,7 @@ impl ServiceManager {
}
println!("[ServiceManager] 停止插件: {}", state.id());
if let Err(e) = state.plugin.stop() {
eprintln!(
"[ServiceManager] 停止插件 '{}' 失败: {}",
state.id(),
e
);
eprintln!("[ServiceManager] 停止插件 '{}' 失败: {}", state.id(), e);
}
}
Ok(())
@@ -241,6 +349,8 @@ impl ServiceManager {
error_count: s.error_count,
max_errors: s.max_errors,
enabled: s.enabled,
test_results: s.test_results.clone(),
capabilities: s.capabilities.clone(),
})
.collect()
}
@@ -259,15 +369,13 @@ impl ServiceManager {
.position(|s| s.id() == plugin_id)
.ok_or_else(|| anyhow!("plugin '{plugin_id}' not found for replacement"))?;
// Stop old plugin
if self.plugins[idx].enabled {
let _ = self.plugins[idx].plugin.stop();
if !self.plugins[idx].is_dynamic {
return Err(anyhow!(
"plugin '{plugin_id}' is not dynamic and cannot be replaced"
));
}
// Replace
let mut new_state = PluginState::new_dynamic(new_plugin, error_policy, max_errors);
// Init new plugin
let ctx = PluginContext {
tx: self.tx.clone(),
config: Arc::clone(&self.config),
@@ -275,6 +383,10 @@ impl ServiceManager {
new_state.plugin.init(ctx)?;
new_state.plugin.start()?;
if self.plugins[idx].enabled {
let _ = self.plugins[idx].plugin.stop();
}
self.plugins[idx] = new_state;
println!("[ServiceManager] 插件 '{plugin_id}' 热替换成功");
Ok(())
@@ -487,6 +599,7 @@ impl ServiceManager {
"[ServiceManager] 插件 '{}' 错误次数达到阈值,已禁用",
plugin_id
);
let _ = state.plugin.stop();
state.enabled = false;
}
ErrorPolicy::AutoRollback => {
@@ -495,6 +608,7 @@ impl ServiceManager {
plugin_id
);
// 先禁用,等待外部 (main.rs / HTTP API) 调用 VersionManager 执行回退
let _ = state.plugin.stop();
state.enabled = false;
}
}
@@ -516,4 +630,6 @@ pub struct PluginStateInfo {
pub error_count: u32,
pub max_errors: u32,
pub enabled: bool,
pub test_results: Vec<CapabilityTestResult>,
pub capabilities: Vec<String>,
}