use async_trait::async_trait;
use std::process::Stdio;
use std::time::Duration;
use tokio::process::Command;
use super::types::{CommandOutput, ContainerConfig, ContainerRuntime, RuntimeError, RuntimeResult};
#[derive(Debug, Clone, Default)]
pub struct NativeRuntime;
impl NativeRuntime {
pub fn new() -> Self {
Self
}
}
#[async_trait]
impl ContainerRuntime for NativeRuntime {
fn name(&self) -> &str {
"native"
}
async fn is_available(&self) -> bool {
true
}
async fn execute(
&self,
command: &str,
config: &ContainerConfig,
) -> RuntimeResult<CommandOutput> {
let mut cmd = Command::new("sh");
cmd.arg("-c").arg(command);
if let Some(ref workdir) = config.workdir {
cmd.current_dir(workdir);
}
for (key, value) in &config.env {
cmd.env(key, value);
}
cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
let output = tokio::time::timeout(Duration::from_secs(config.timeout_secs), cmd.output())
.await
.map_err(|_| RuntimeError::Timeout(config.timeout_secs))?
.map_err(|e| RuntimeError::ExecutionFailed(e.to_string()))?;
Ok(CommandOutput::new(
String::from_utf8_lossy(&output.stdout).to_string(),
String::from_utf8_lossy(&output.stderr).to_string(),
output.status.code(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_native_runtime_available() {
let runtime = NativeRuntime::new();
assert!(runtime.is_available().await);
}
#[tokio::test]
async fn test_native_runtime_name() {
let runtime = NativeRuntime::new();
assert_eq!(runtime.name(), "native");
}
#[tokio::test]
async fn test_native_runtime_echo() {
let runtime = NativeRuntime::new();
let config = ContainerConfig::new();
let output = runtime.execute("echo hello", &config).await.unwrap();
assert!(output.success());
assert_eq!(output.stdout.trim(), "hello");
}
#[tokio::test]
async fn test_native_runtime_with_workdir() {
let runtime = NativeRuntime::new();
let config = ContainerConfig::new().with_workdir(std::path::PathBuf::from("/tmp"));
let output = runtime.execute("pwd", &config).await.unwrap();
assert!(output.success());
assert!(output.stdout.contains("tmp"));
}
#[tokio::test]
async fn test_native_runtime_with_env() {
let runtime = NativeRuntime::new();
let config = ContainerConfig::new().with_env("TEST_VAR", "test_value");
let output = runtime.execute("echo $TEST_VAR", &config).await.unwrap();
assert!(output.success());
assert_eq!(output.stdout.trim(), "test_value");
}
#[tokio::test]
async fn test_native_runtime_stderr() {
let runtime = NativeRuntime::new();
let config = ContainerConfig::new();
let output = runtime.execute("echo error >&2", &config).await.unwrap();
assert!(output.success());
assert!(output.stderr.contains("error"));
}
#[tokio::test]
async fn test_native_runtime_exit_code() {
let runtime = NativeRuntime::new();
let config = ContainerConfig::new();
let output = runtime.execute("exit 42", &config).await.unwrap();
assert!(!output.success());
assert_eq!(output.exit_code, Some(42));
}
#[tokio::test]
async fn test_native_runtime_timeout() {
let runtime = NativeRuntime::new();
let config = ContainerConfig::new().with_timeout(1);
let result = runtime.execute("sleep 10", &config).await;
assert!(matches!(result, Err(RuntimeError::Timeout(1))));
}
}