use execution_engine::*;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
#[tokio::test]
async fn test_simple_shell_command() {
let config = ExecutionConfig::default();
let engine = ExecutionEngine::new(config).unwrap();
let request = ExecutionRequest {
id: Uuid::new_v4(),
command: Command::Shell {
command: "echo 'Hello from execution engine!'".to_string(),
shell: "bash".to_string(),
},
env: HashMap::new(),
working_dir: None,
timeout_ms: Some(5000),
output_log_path: None,
metadata: ExecutionMetadata::default(),
};
let execution_id = engine.execute(request).await.unwrap();
println!("Execution started with ID: {}", execution_id);
let result = engine.wait_for_completion(execution_id).await.unwrap();
assert_eq!(result.status, ExecutionStatus::Completed);
assert_eq!(result.exit_code, 0);
assert!(result.success);
assert!(result.stdout.contains("Hello from execution engine!"));
println!("✅ Simple shell command test passed!");
println!(" Status: {:?}", result.status);
println!(" Exit code: {}", result.exit_code);
println!(" Duration: {:?}", result.duration);
println!(" Stdout: {}", result.stdout);
}
#[tokio::test]
async fn test_script_execution() {
let script_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("test-scripts")
.join("simple-test.sh");
if !script_path.exists() {
panic!("Test script not found at: {:?}", script_path);
}
let config = ExecutionConfig::default();
let engine = ExecutionEngine::new(config).unwrap();
let mut env = HashMap::new();
env.insert("TEST_VAR".to_string(), "test_value_123".to_string());
let request = ExecutionRequest {
id: Uuid::new_v4(),
command: Command::Script {
path: script_path.clone(),
interpreter: Some("bash".to_string()),
},
env,
working_dir: None,
timeout_ms: Some(5000),
output_log_path: None,
metadata: ExecutionMetadata::default(),
};
let execution_id = engine.execute(request).await.unwrap();
println!("Script execution started with ID: {}", execution_id);
let result = engine.wait_for_completion(execution_id).await.unwrap();
assert_eq!(result.status, ExecutionStatus::Completed);
assert_eq!(result.exit_code, 0);
assert!(result.success);
assert!(result.stdout.contains("Starting execution..."));
assert!(result.stdout.contains("TEST_VAR is set to: test_value_123"));
assert!(result.stdout.contains("Execution completed successfully!"));
assert!(result.stderr.contains("This is a stderr message"));
println!("✅ Script execution test passed!");
println!(" Status: {:?}", result.status);
println!(" Exit code: {}", result.exit_code);
println!(" Duration: {:?}", result.duration);
println!(" Stdout lines: {}", result.stdout.lines().count());
println!(" Stderr lines: {}", result.stderr.lines().count());
println!("\nStdout:\n{}", result.stdout);
println!("\nStderr:\n{}", result.stderr);
}
#[tokio::test]
async fn test_concurrent_executions() {
let config = ExecutionConfig {
max_concurrent_executions: 10,
..Default::default()
};
let engine = Arc::new(ExecutionEngine::new(config).unwrap());
let mut handles = vec![];
for i in 0..5 {
let engine_clone = Arc::clone(&engine);
let handle = tokio::spawn(async move {
let request = ExecutionRequest {
id: Uuid::new_v4(),
command: Command::Shell {
command: format!("echo 'Task {}' && sleep 0.1", i),
shell: "bash".to_string(),
},
env: HashMap::new(),
working_dir: None,
timeout_ms: Some(5000),
output_log_path: None,
metadata: ExecutionMetadata::default(),
};
let execution_id = engine_clone.execute(request).await.unwrap();
let result = engine_clone
.wait_for_completion(execution_id)
.await
.unwrap();
(i, result)
});
handles.push(handle);
}
let mut results = vec![];
for handle in handles {
let (task_id, result) = handle.await.unwrap();
results.push((task_id, result));
}
assert_eq!(results.len(), 5);
for (task_id, result) in results {
assert_eq!(result.status, ExecutionStatus::Completed);
assert_eq!(result.exit_code, 0);
assert!(result.stdout.contains(&format!("Task {}", task_id)));
}
println!("✅ Concurrent execution test passed!");
println!(" Executed 5 tasks concurrently");
println!(" All tasks completed successfully");
}
#[tokio::test]
async fn test_list_and_count_operations() {
let config = ExecutionConfig::default();
let engine = ExecutionEngine::new(config).unwrap();
assert_eq!(engine.total_count().await, 0);
assert_eq!(engine.running_count().await, 0);
let request1 = ExecutionRequest {
id: Uuid::new_v4(),
command: Command::Shell {
command: "echo 'test1'".to_string(),
shell: "bash".to_string(),
},
env: HashMap::new(),
working_dir: None,
timeout_ms: Some(5000),
output_log_path: None,
metadata: ExecutionMetadata::default(),
};
let request2 = ExecutionRequest {
id: Uuid::new_v4(),
command: Command::Shell {
command: "echo 'test2'".to_string(),
shell: "bash".to_string(),
},
env: HashMap::new(),
working_dir: None,
timeout_ms: Some(5000),
output_log_path: None,
metadata: ExecutionMetadata::default(),
};
let id1 = engine.execute(request1).await.unwrap();
let id2 = engine.execute(request2).await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
let total = engine.total_count().await;
assert_eq!(total, 2);
let list = engine.list_executions().await;
assert_eq!(list.len(), 2);
let ids: Vec<Uuid> = list.iter().map(|s| s.id).collect();
assert!(ids.contains(&id1));
assert!(ids.contains(&id2));
println!("✅ List and count operations test passed!");
println!(" Total executions: {}", total);
println!(" Listed executions: {}", list.len());
}
#[tokio::test]
async fn test_timeout_handling() {
let config = ExecutionConfig {
default_timeout_ms: 1000, ..Default::default()
};
let engine = ExecutionEngine::new(config).unwrap();
let request = ExecutionRequest {
id: Uuid::new_v4(),
command: Command::Shell {
command: "sleep 10".to_string(), shell: "bash".to_string(),
},
env: HashMap::new(),
working_dir: None,
timeout_ms: Some(1000), output_log_path: None,
metadata: ExecutionMetadata::default(),
};
let execution_id = engine.execute(request).await.unwrap();
let result = engine.wait_for_completion(execution_id).await.unwrap();
assert_eq!(result.status, ExecutionStatus::Timeout);
assert!(!result.success);
let status = engine.get_status(execution_id).await.unwrap();
assert_eq!(status, ExecutionStatus::Timeout);
println!("✅ Timeout handling test passed!");
println!(" Execution timed out as expected");
println!(" Status: {:?}", result.status);
}
#[tokio::test]
async fn test_failed_command() {
let config = ExecutionConfig::default();
let engine = ExecutionEngine::new(config).unwrap();
let request = ExecutionRequest {
id: Uuid::new_v4(),
command: Command::Shell {
command: "exit 42".to_string(), shell: "bash".to_string(),
},
env: HashMap::new(),
working_dir: None,
timeout_ms: Some(5000),
output_log_path: None,
metadata: ExecutionMetadata::default(),
};
let execution_id = engine.execute(request).await.unwrap();
let result = engine.wait_for_completion(execution_id).await.unwrap();
assert_eq!(result.status, ExecutionStatus::Failed);
assert_eq!(result.exit_code, 42);
assert!(!result.success);
println!("✅ Failed command test passed!");
println!(" Exit code: {}", result.exit_code);
println!(" Status: {:?}", result.status);
}
#[tokio::test]
async fn test_cancellation() {
let config = ExecutionConfig::default();
let engine = ExecutionEngine::new(config).unwrap();
let request = ExecutionRequest {
id: Uuid::new_v4(),
command: Command::Shell {
command: "sleep 30".to_string(), shell: "bash".to_string(),
},
env: HashMap::new(),
working_dir: None,
timeout_ms: Some(60000), output_log_path: None,
metadata: ExecutionMetadata::default(),
};
let execution_id = engine.execute(request).await.unwrap();
println!("Started execution: {}", execution_id);
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
let cancel_result = engine.cancel(execution_id).await;
println!("Cancellation result: {:?}", cancel_result);
assert!(cancel_result.is_ok(), "Cancellation should succeed");
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
let result = engine.get_result(execution_id).await.unwrap();
println!("Result status: {:?}", result.status);
println!("Exit code: {}", result.exit_code);
assert_eq!(result.status, ExecutionStatus::Cancelled);
assert!(!result.success);
println!("✅ Cancellation test passed!");
println!(" Execution was successfully cancelled");
}
#[tokio::test]
async fn test_stream_to_file_strategy() {
let config = ExecutionConfig {
max_output_size_bytes: 1024, oversized_output_strategy: execution_engine::OversizedOutputStrategy::StreamToFile,
..Default::default()
};
let engine = ExecutionEngine::new(config).unwrap();
let request = ExecutionRequest {
id: Uuid::new_v4(),
command: Command::Shell {
command: "for i in {1..100}; do echo \"Line $i: This is a test line with some content to make it longer\"; done".to_string(),
shell: "bash".to_string(),
},
env: HashMap::new(),
working_dir: None,
timeout_ms: Some(10000),
output_log_path: None,
metadata: ExecutionMetadata::default(),
};
let execution_id = engine.execute(request).await.unwrap();
let result = engine.wait_for_completion(execution_id).await.unwrap();
assert_eq!(result.status, ExecutionStatus::Completed);
assert!(result.success);
println!("Stdout size: {} bytes", result.stdout.len());
assert!(
result.stdout.len() <= 2000,
"Stdout should be limited to ~1KB + warning"
);
assert!(
result
.stdout
.contains("[OUTPUT LIMIT REACHED: Remaining output streamed to"),
"Should contain overflow warning"
);
assert!(
result.stdout_overflow_file.is_some(),
"Should have stdout overflow file"
);
if let Some(overflow_path) = &result.stdout_overflow_file {
assert!(
overflow_path.exists(),
"Overflow file should exist at {:?}",
overflow_path
);
let overflow_content = tokio::fs::read_to_string(overflow_path).await.unwrap();
println!("Overflow file size: {} bytes", overflow_content.len());
assert!(
overflow_content.len() > 1000,
"Overflow file should contain substantial content"
);
tokio::fs::remove_file(overflow_path).await.ok();
}
println!("✅ StreamToFile test passed!");
println!(" In-memory output: {} bytes", result.stdout.len());
println!(" Overflow file: {:?}", result.stdout_overflow_file);
}