#[cfg(test)]
mod executor_adapter_tests {
use agentd::planner::executor_adapter::*;
use agentd::planner::oracle::{ExecutionPlan, PlanStep, ResourceRequirements};
use agentd::planner::{ExecutionConfig, ResourceLimits, RetryPolicy};
use anyhow::Result;
use serde_json::json;
use std::collections::HashMap;
use std::time::Duration;
use tokio::time::timeout;
use uuid::Uuid;
fn create_test_config() -> ExecutionConfig {
ExecutionConfig {
max_concurrent_workflows: 2,
max_workflow_duration_hours: 1,
resource_limits: ResourceLimits {
max_memory_mb: 1024,
max_cpu_percent: 80,
max_disk_mb: 1024,
max_network_connections: 50,
},
retry_policy: RetryPolicy {
max_retries: 3,
initial_backoff_ms: 100, max_backoff_ms: 1000, backoff_multiplier: 2.0,
},
}
}
fn create_test_plan_step(sequence: u32, capability: &str, duration_minutes: u32) -> PlanStep {
PlanStep {
step_id: Uuid::new_v4(),
sequence,
description: format!("Test step {}", sequence),
capability: capability.to_string(),
parameters: {
let mut params = HashMap::new();
match capability {
"implementation.execute.v1" => {
params.insert(
"operations".to_string(),
json!([{
"path": format!("tmp/test_step_{}.txt", sequence),
"content": "Generated by ExecutorAdapter tests",
"mode": "overwrite"
}]),
);
}
"analysis.system.v1" | "analysis.performance.v1" => {
params.insert("target_files".to_string(), json!(["README.md"]));
}
"validation.test.v1" => {
params.insert("command".to_string(), json!(["echo", "validation success"]));
}
_ => {}
}
params
},
expected_duration_minutes: duration_minutes,
success_criteria: vec!["Test criterion".to_string()],
failure_recovery: Some("Retry with different approach".to_string()),
parallel_group: None,
}
}
fn create_parallel_plan_step(sequence: u32, capability: &str, group: &str) -> PlanStep {
let mut step = create_test_plan_step(sequence, capability, 5);
step.parallel_group = Some(group.to_string());
step
}
fn create_test_execution_plan(steps: Vec<PlanStep>) -> ExecutionPlan {
let total_duration = steps.iter().map(|s| s.expected_duration_minutes).sum();
ExecutionPlan {
plan_id: Uuid::new_v4(),
summary: "Test execution plan".to_string(),
steps,
estimated_duration_minutes: total_duration,
resource_requirements: ResourceRequirements {
cpu_cores: 1.0,
memory_mb: 512,
disk_mb: 1024,
network_bandwidth_mbps: 10.0,
external_services: vec!["test-service".to_string()],
},
dependencies: vec![],
rollback_plan: None,
}
}
#[tokio::test]
async fn test_executor_adapter_creation() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
Ok(())
}
#[tokio::test]
async fn test_simple_plan_execution() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
let steps = vec![create_test_plan_step(1, "analysis.system.v1", 5)];
let plan = create_test_execution_plan(steps);
let result = adapter.execute_plan(&plan).await?;
assert_eq!(result.total_operations, 1);
assert!(result.execution_time_ms > 0);
assert!(!result.step_results.is_empty());
Ok(())
}
#[tokio::test]
async fn test_sequential_plan_execution() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
let steps = vec![
create_test_plan_step(1, "analysis.system.v1", 5),
create_test_plan_step(2, "implementation.execute.v1", 10),
create_test_plan_step(3, "validation.test.v1", 5),
];
let plan = create_test_execution_plan(steps);
let result = adapter.execute_plan(&plan).await?;
assert_eq!(result.total_operations, 3);
assert_eq!(result.step_results.len(), 3);
for (i, step_result) in result.step_results.iter().enumerate() {
assert_eq!(step_result.step_name, format!("Test step {}", i + 1));
}
Ok(())
}
#[tokio::test]
async fn test_parallel_group_execution() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
let steps = vec![
create_parallel_plan_step(1, "analysis.system.v1", "group1"),
create_parallel_plan_step(2, "analysis.performance.v1", "group1"),
create_test_plan_step(3, "implementation.execute.v1", 10), ];
let plan = create_test_execution_plan(steps);
let result = adapter.execute_plan(&plan).await?;
assert_eq!(result.total_operations, 3);
assert_eq!(result.step_results.len(), 3);
let parallel_steps: Vec<_> = result
.step_results
.iter()
.filter(|r| r.parallel_group.as_ref() == Some(&"group1".to_string()))
.collect();
assert_eq!(parallel_steps.len(), 2);
Ok(())
}
#[tokio::test]
async fn test_step_failure_recovery() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
let steps = vec![
create_test_plan_step(1, "test.failure.v1", 5), ];
let plan = create_test_execution_plan(steps);
let result = adapter.execute_plan(&plan).await?;
assert_eq!(result.total_operations, 1);
assert!(!result.step_results.is_empty());
let step_result = &result.step_results[0];
if !step_result.success {
assert!(
step_result.retry_count <= config.retry_policy.max_retries + 1,
"Retry count {} exceeded limit {}",
step_result.retry_count,
config.retry_policy.max_retries
);
assert!(step_result.error_message.is_some());
}
Ok(())
}
#[tokio::test]
async fn test_retry_policy_enforcement() -> Result<()> {
let mut config = create_test_config();
config.retry_policy.max_retries = 1; let adapter = ExecutorAdapter::new(&config).await?;
let steps = vec![create_test_plan_step(1, "test.always_fail.v1", 5)];
let plan = create_test_execution_plan(steps);
let result = adapter.execute_plan(&plan).await?;
if !result.success {
let step_result = &result.step_results[0];
assert!(
step_result.retry_count <= config.retry_policy.max_retries + 1,
"Retry count {} exceeded configured limit {}",
step_result.retry_count,
config.retry_policy.max_retries
);
}
Ok(())
}
#[tokio::test]
async fn test_timeout_handling() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
let mut step = create_test_plan_step(1, "analysis.system.v1", 1); step.expected_duration_minutes = 0;
let plan = create_test_execution_plan(vec![step]);
let execution_future = adapter.execute_plan(&plan);
let result = timeout(Duration::from_secs(10), execution_future).await??;
assert_eq!(result.total_operations, 1);
Ok(())
}
#[tokio::test]
async fn test_resource_usage_tracking() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
let steps = vec![
create_test_plan_step(1, "analysis.system.v1", 10),
create_test_plan_step(2, "implementation.execute.v1", 15),
];
let plan = create_test_execution_plan(steps);
let result = adapter.execute_plan(&plan).await?;
assert!(result.resource_usage.cpu_cores_used > 0.0);
assert!(result.resource_usage.memory_mb_used > 0);
assert!(result.resource_usage.total_cpu_time_ms > 0);
for step_result in &result.step_results {
assert!(step_result.resource_usage.cpu_cores_used > 0.0);
assert!(step_result.resource_usage.memory_mb_used > 0);
}
Ok(())
}
#[tokio::test]
async fn test_concurrent_execution_limits() -> Result<()> {
let mut config = create_test_config();
config.max_concurrent_workflows = 1; let adapter = ExecutorAdapter::new(&config).await?;
let steps = vec![
create_parallel_plan_step(1, "analysis.system.v1", "group1"),
create_parallel_plan_step(2, "analysis.performance.v1", "group1"),
create_parallel_plan_step(3, "analysis.security.v1", "group1"),
];
let plan = create_test_execution_plan(steps);
let result = adapter.execute_plan(&plan).await?;
assert_eq!(result.total_operations, 3);
assert_eq!(result.step_results.len(), 3);
Ok(())
}
#[tokio::test]
async fn test_performance_metrics_collection() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
let steps = vec![create_test_plan_step(1, "analysis.system.v1", 5)];
let plan = create_test_execution_plan(steps);
let result = adapter.execute_plan(&plan).await?;
assert!(result.execution_time_ms > 0);
assert!(result.execution_time_ms < 30000);
Ok(())
}
#[tokio::test]
async fn test_execution_history_tracking() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
let steps = vec![create_test_plan_step(1, "analysis.system.v1", 5)];
let plan = create_test_execution_plan(steps);
let _result = adapter.execute_plan(&plan).await?;
Ok(())
}
#[tokio::test]
async fn test_step_grouping_logic() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
let steps = vec![
create_parallel_plan_step(1, "analysis.system.v1", "analysis"),
create_parallel_plan_step(2, "analysis.performance.v1", "analysis"),
create_test_plan_step(3, "implementation.execute.v1", 10), create_parallel_plan_step(4, "validation.test.v1", "validation"),
create_parallel_plan_step(5, "validation.security.v1", "validation"),
];
Ok(())
}
#[tokio::test]
async fn test_parallel_execution_safety() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
let steps: Vec<PlanStep> = (1..=10)
.map(|i| create_parallel_plan_step(i, "analysis.concurrent.v1", "concurrent"))
.collect();
let plan = create_test_execution_plan(steps);
let result = adapter.execute_plan(&plan).await?;
assert_eq!(result.total_operations, 10);
assert_eq!(result.step_results.len(), 10);
for step_result in &result.step_results {
assert_eq!(step_result.parallel_group.as_ref().unwrap(), "concurrent");
}
Ok(())
}
#[tokio::test]
async fn test_complex_mixed_execution_plan() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
let steps = vec![
create_parallel_plan_step(1, "analysis.system.v1", "phase1"),
create_parallel_plan_step(2, "analysis.performance.v1", "phase1"),
create_parallel_plan_step(3, "analysis.security.v1", "phase1"),
create_test_plan_step(4, "implementation.prepare.v1", 5),
create_test_plan_step(5, "implementation.execute.v1", 15),
create_parallel_plan_step(6, "validation.functional.v1", "phase3"),
create_parallel_plan_step(7, "validation.performance.v1", "phase3"),
create_test_plan_step(8, "validation.final.v1", 10),
];
let plan = create_test_execution_plan(steps);
let result = adapter.execute_plan(&plan).await?;
assert_eq!(result.total_operations, 8);
assert_eq!(result.step_results.len(), 8);
assert!(result.execution_time_ms > 0);
let success_rate = result.completed_operations as f32 / result.total_operations as f32;
assert!(success_rate >= 0.5); Ok(())
}
#[tokio::test]
async fn test_capability_validation() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
let steps = vec![
create_test_plan_step(1, "analysis.system.v1", 5),
create_test_plan_step(2, "implementation.execute.v1", 10),
create_test_plan_step(3, "validation.test.v1", 5),
];
let plan = create_test_execution_plan(steps);
let result = adapter.execute_plan(&plan).await?;
assert!(result.completed_operations > 0);
Ok(())
}
#[tokio::test]
async fn test_resource_limit_adherence() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
let steps = vec![
create_test_plan_step(1, "analysis.system.v1", 30), ];
let plan = create_test_execution_plan(steps);
let result = adapter.execute_plan(&plan).await?;
assert!(result.resource_usage.memory_mb_used <= config.resource_limits.max_memory_mb);
assert!(result.resource_usage.cpu_cores_used <= 10.0); Ok(())
}
#[tokio::test]
async fn test_execution_metadata_collection() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
let steps = vec![create_test_plan_step(1, "analysis.system.v1", 5)];
let plan = create_test_execution_plan(steps);
let result = adapter.execute_plan(&plan).await?;
assert!(!result.execution_id.to_string().is_empty());
assert!(result.attempt_count > 0);
assert!(!result.output.is_empty());
Ok(())
}
#[tokio::test]
async fn test_empty_plan_execution() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
let plan = create_test_execution_plan(vec![]);
let result = adapter.execute_plan(&plan).await?;
assert_eq!(result.total_operations, 0);
assert_eq!(result.completed_operations, 0);
assert_eq!(result.failed_operations, 0);
assert!(result.step_results.is_empty());
assert!(result.success); Ok(())
}
#[tokio::test]
async fn test_large_plan_execution() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
let steps: Vec<PlanStep> = (1..=20)
.map(|i| create_test_plan_step(i, "analysis.system.v1", 2)) .collect();
let plan = create_test_execution_plan(steps);
let result = adapter.execute_plan(&plan).await?;
assert_eq!(result.total_operations, 20);
assert_eq!(result.step_results.len(), 20);
assert!(result.execution_time_ms < 60000); Ok(())
}
#[tokio::test]
async fn test_metrics_export_functionality() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
for i in 1..=3 {
let steps = vec![create_test_plan_step(i, "analysis.system.v1", 5)];
let plan = create_test_execution_plan(steps);
let _result = adapter.execute_plan(&plan).await?;
}
Ok(())
}
#[tokio::test]
async fn test_concurrent_plan_execution() -> Result<()> {
let config = create_test_config();
let adapter = ExecutorAdapter::new(&config).await?;
let plan1 =
create_test_execution_plan(vec![create_test_plan_step(1, "analysis.system.v1", 5)]);
let plan2 = create_test_execution_plan(vec![create_test_plan_step(
1,
"analysis.performance.v1",
5,
)]);
let (result1, result2) =
tokio::try_join!(adapter.execute_plan(&plan1), adapter.execute_plan(&plan2))?;
assert_eq!(result1.total_operations, 1);
assert_eq!(result2.total_operations, 1);
assert_ne!(result1.execution_id, result2.execution_id);
Ok(())
}
}