use ciphern::plugin::hot_reload::HotReloadEventType;
use ciphern::plugin::{HotReloadWatcher, PluginLoader, PluginManager, SafeReloadManager};
use std::fs;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
#[test]
fn test_plugin_hot_reload_basic() {
let temp_dir = tempfile::tempdir().unwrap();
let plugin_dir = temp_dir.path().join("plugins");
fs::create_dir(&plugin_dir).unwrap();
let plugin_path = plugin_dir.join("test_plugin.plugin");
fs::write(&plugin_path, b"test plugin content").unwrap();
let watcher = HotReloadWatcher::new();
watcher.watch_plugin(&plugin_path, "test_plugin").unwrap();
let reload_called = Arc::new(std::sync::atomic::AtomicBool::new(false));
let reload_called_clone = Arc::clone(&reload_called);
watcher
.register_reload_handler("test_plugin", move |_| {
reload_called_clone.store(true, std::sync::atomic::Ordering::SeqCst);
})
.unwrap();
thread::sleep(Duration::from_millis(100));
fs::write(&plugin_path, b"updated plugin content").unwrap();
thread::sleep(Duration::from_millis(200));
let events = watcher.check_for_changes().unwrap();
assert!(!events.is_empty(), "Should detect file change");
assert_eq!(events[0].plugin_name, "test_plugin");
assert_eq!(events[0].event_type, HotReloadEventType::FileChanged);
watcher.process_reload_events(events).unwrap();
thread::sleep(Duration::from_millis(100));
assert!(
reload_called.load(std::sync::atomic::Ordering::SeqCst),
"Reload handler should be called"
);
}
#[test]
fn test_plugin_fault_isolation() {
let manager = PluginManager::new();
struct FailingPlugin {
name: String,
fail_count: std::sync::atomic::AtomicU32,
}
impl ciphern::plugin::Plugin for FailingPlugin {
fn name(&self) -> &str {
&self.name
}
fn version(&self) -> &str {
"1.0.0"
}
fn initialize(&mut self) -> ciphern::Result<()> {
Ok(())
}
fn shutdown(&mut self) -> ciphern::Result<()> {
Ok(())
}
fn health_check(&self) -> ciphern::Result<bool> {
let count = self
.fail_count
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Ok(count < 3) }
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
let failing_plugin = Arc::new(FailingPlugin {
name: "failing_plugin".to_string(),
fail_count: std::sync::atomic::AtomicU32::new(0),
});
manager.register_plugin(failing_plugin.clone()).unwrap();
let health_results = manager.health_check_all();
assert_eq!(health_results.len(), 1);
assert!(
*health_results.get("failing_plugin").unwrap(),
"Initial health check should pass"
);
for _ in 0..5 {
let _results = manager.health_check_all();
thread::sleep(Duration::from_millis(10));
}
let final_health = manager.health_check_all();
assert!(
final_health.is_empty() || !final_health.contains_key("failing_plugin"),
"Failing plugin should be removed"
);
}
#[test]
fn test_safe_reload_manager() {
let safe_manager = SafeReloadManager::new();
struct TestPlugin {
name: String,
stable: std::sync::atomic::AtomicBool,
}
impl ciphern::plugin::Plugin for TestPlugin {
fn name(&self) -> &str {
&self.name
}
fn version(&self) -> &str {
"1.0.0"
}
fn initialize(&mut self) -> ciphern::Result<()> {
Ok(())
}
fn shutdown(&mut self) -> ciphern::Result<()> {
Ok(())
}
fn health_check(&self) -> ciphern::Result<bool> {
Ok(self.stable.load(std::sync::atomic::Ordering::SeqCst))
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
let plugin = Arc::new(TestPlugin {
name: "test_plugin".to_string(),
stable: std::sync::atomic::AtomicBool::new(true),
});
safe_manager.stage_plugin(plugin.clone()).unwrap();
let stable = safe_manager
.wait_for_stability("test_plugin", 3, 100)
.unwrap();
assert!(stable, "Plugin should be stable");
safe_manager.commit_plugin("test_plugin").unwrap();
let active_plugin = safe_manager.get_active_plugin("test_plugin");
assert!(
active_plugin.is_some(),
"Plugin should be active after commit"
);
}
#[test]
fn test_plugin_loader_functionality() {
let temp_dir = tempfile::tempdir().unwrap();
let plugin_dir = temp_dir.path().join("plugins");
fs::create_dir(&plugin_dir).unwrap();
for i in 0..3 {
let plugin_path = plugin_dir.join(format!("plugin_{}.plugin", i));
fs::write(&plugin_path, format!("plugin content {}", i)).unwrap();
}
let mut loader = PluginLoader::new(vec![plugin_dir]);
let results = loader.load_all_plugins();
assert_eq!(results.len(), 3, "Should load all 3 plugins");
let plugin_names = loader.list_plugins();
assert_eq!(plugin_names.len(), 3);
for name in &plugin_names {
let plugin = loader.get_plugin(name);
assert!(plugin.is_some(), "Should retrieve plugin by name");
if let Some(plugin) = plugin {
assert_eq!(plugin.name(), name);
assert_eq!(plugin.version(), "1.0.0");
}
}
let first_plugin = plugin_names[0].clone();
loader.unload_plugin(&first_plugin).unwrap();
let remaining_plugins = loader.list_plugins();
assert_eq!(remaining_plugins.len(), 2);
assert!(
!remaining_plugins.contains(&first_plugin),
"Plugin should be unloaded"
);
}
#[test]
fn test_plugin_manager_integration() {
let manager = PluginManager::new();
for i in 0..5 {
struct TestPlugin {
name: String,
}
impl ciphern::plugin::Plugin for TestPlugin {
fn name(&self) -> &str {
&self.name
}
fn version(&self) -> &str {
"1.0.0"
}
fn initialize(&mut self) -> ciphern::Result<()> {
Ok(())
}
fn shutdown(&mut self) -> ciphern::Result<()> {
Ok(())
}
fn health_check(&self) -> ciphern::Result<bool> {
Ok(true)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
let plugin = Arc::new(TestPlugin {
name: format!("test_plugin_{}", i),
});
manager.register_plugin(plugin).unwrap();
}
let plugin_names = manager.list_plugins();
assert_eq!(plugin_names.len(), 5, "Should have 5 registered plugins");
let health_results = manager.health_check_all();
assert_eq!(health_results.len(), 5);
for (name, is_healthy) in health_results {
assert!(is_healthy, "Plugin {} should be healthy", name);
}
manager.graceful_shutdown().unwrap();
}