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:
@@ -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>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user