use anyhow::Result;
use mecha10_runtime::lifecycle::{LifecycleManager, ModeConfig, SupervisorTrait};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
struct MockSupervisor {
started: Arc<tokio::sync::Mutex<Vec<String>>>,
stopped: Arc<tokio::sync::Mutex<Vec<String>>>,
running: Arc<tokio::sync::Mutex<HashSet<String>>>,
}
impl MockSupervisor {
fn new() -> Self {
Self {
started: Arc::new(tokio::sync::Mutex::new(Vec::new())),
stopped: Arc::new(tokio::sync::Mutex::new(Vec::new())),
running: Arc::new(tokio::sync::Mutex::new(HashSet::new())),
}
}
}
#[async_trait::async_trait]
impl SupervisorTrait for MockSupervisor {
async fn start_node(&self, name: &str) -> Result<()> {
self.started.lock().await.push(name.to_string());
self.running.lock().await.insert(name.to_string());
Ok(())
}
async fn stop_node(&self, name: &str) -> Result<()> {
self.stopped.lock().await.push(name.to_string());
self.running.lock().await.remove(name);
Ok(())
}
fn get_running_nodes(&self) -> Vec<String> {
vec![]
}
}
fn create_test_modes() -> HashMap<String, ModeConfig> {
let mut modes = HashMap::new();
modes.insert(
"startup".to_string(),
ModeConfig {
nodes: vec!["listener".to_string(), "speaker".to_string()],
description: Some("Basic startup mode".to_string()),
},
);
modes.insert(
"simulation".to_string(),
ModeConfig {
nodes: vec![
"listener".to_string(),
"speaker".to_string(),
"simulator".to_string(),
"object-detector".to_string(),
],
description: Some("Simulation mode".to_string()),
},
);
modes.insert(
"teleop".to_string(),
ModeConfig {
nodes: vec!["listener".to_string(), "speaker".to_string(), "teleop".to_string()],
description: Some("Teleop mode".to_string()),
},
);
modes
}
#[tokio::test]
async fn test_lifecycle_manager_creation() {
let supervisor = Arc::new(MockSupervisor::new());
let modes = create_test_modes();
let manager = LifecycleManager::new(supervisor, modes, "startup".to_string());
assert_eq!(manager.current_mode(), "startup");
assert_eq!(manager.running_nodes().len(), 0);
}
#[tokio::test]
async fn test_mode_diff_calculation() {
let supervisor = Arc::new(MockSupervisor::new());
let modes = create_test_modes();
let manager = LifecycleManager::new(supervisor, modes, "startup".to_string());
let diff = manager.calculate_mode_diff("simulation").unwrap();
assert_eq!(diff.start.len(), 4);
assert!(diff.start.contains(&"simulator".to_string()));
assert!(diff.start.contains(&"object-detector".to_string()));
assert_eq!(diff.stop.len(), 0);
}
#[tokio::test]
async fn test_mode_transition() {
let supervisor = Arc::new(MockSupervisor::new());
let modes = create_test_modes();
let mut manager = LifecycleManager::new(supervisor.clone(), modes, "startup".to_string());
let result = manager.change_mode("startup").await.unwrap();
assert_eq!(result.previous_mode, "startup");
assert_eq!(result.current_mode, "startup");
assert_eq!(result.nodes_started.len(), 2);
assert!(result.nodes_started.contains(&"listener".to_string()));
assert!(result.nodes_started.contains(&"speaker".to_string()));
let result = manager.change_mode("simulation").await.unwrap();
assert_eq!(result.previous_mode, "startup");
assert_eq!(result.current_mode, "simulation");
assert_eq!(result.nodes_started.len(), 2);
assert!(result.nodes_started.contains(&"simulator".to_string()));
assert_eq!(result.nodes_stopped.len(), 0);
}
#[tokio::test]
async fn test_invalid_mode() {
let supervisor = Arc::new(MockSupervisor::new());
let modes = create_test_modes();
let mut manager = LifecycleManager::new(supervisor, modes, "startup".to_string());
let result = manager.change_mode("invalid-mode").await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
}
#[tokio::test]
async fn test_stop_nodes_on_transition() {
let supervisor = Arc::new(MockSupervisor::new());
let modes = create_test_modes();
let mut modes_with_hardware = modes;
modes_with_hardware.insert(
"hardware".to_string(),
ModeConfig {
nodes: vec!["imu".to_string(), "motor".to_string(), "listener".to_string()],
description: Some("Hardware mode".to_string()),
},
);
let mut manager = LifecycleManager::new(supervisor.clone(), modes_with_hardware, "startup".to_string());
manager.change_mode("hardware").await.unwrap();
let result = manager.change_mode("simulation").await.unwrap();
assert!(result.nodes_stopped.contains(&"imu".to_string()));
assert!(result.nodes_stopped.contains(&"motor".to_string()));
assert!(!result.nodes_stopped.contains(&"listener".to_string()));
}
#[tokio::test]
async fn test_current_mode_getter() {
let supervisor = Arc::new(MockSupervisor::new());
let modes = create_test_modes();
let manager = LifecycleManager::new(supervisor, modes, "teleop".to_string());
assert_eq!(manager.current_mode(), "teleop");
}
#[tokio::test]
async fn test_running_nodes_getter() {
let supervisor = Arc::new(MockSupervisor::new());
let modes = create_test_modes();
let mut manager = LifecycleManager::new(supervisor, modes, "startup".to_string());
manager.change_mode("startup").await.unwrap();
let running = manager.running_nodes();
assert_eq!(running.len(), 2);
assert!(running.contains("listener"));
assert!(running.contains("speaker"));
}
#[tokio::test]
async fn test_mode_transition_result_structure() {
let supervisor = Arc::new(MockSupervisor::new());
let modes = create_test_modes();
let mut manager = LifecycleManager::new(supervisor, modes, "startup".to_string());
let result = manager.change_mode("teleop").await.unwrap();
assert_eq!(result.previous_mode, "startup");
assert_eq!(result.current_mode, "teleop");
assert!(result.errors.is_empty());
assert!(!result.nodes_started.is_empty());
}