use async_trait::async_trait;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct SandboxConfig {
pub image: String,
pub memory_mb: u32,
pub cpus: u32,
pub network: bool,
pub env: HashMap<String, String>,
}
impl Default for SandboxConfig {
fn default() -> Self {
Self {
image: "alpine:latest".into(),
memory_mb: 512,
cpus: 1,
network: false,
env: HashMap::new(),
}
}
}
pub struct SandboxOutput {
pub stdout: String,
pub stderr: String,
pub exit_code: i32,
}
#[async_trait]
pub trait BashSandbox: Send + Sync {
async fn exec_command(
&self,
command: &str,
guest_workspace: &str,
) -> anyhow::Result<SandboxOutput>;
async fn shutdown(&self);
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn test_sandbox_config_default() {
let cfg = SandboxConfig::default();
assert_eq!(cfg.image, "alpine:latest");
assert_eq!(cfg.memory_mb, 512);
assert_eq!(cfg.cpus, 1);
assert!(!cfg.network);
assert!(cfg.env.is_empty());
}
#[test]
fn test_sandbox_config_custom() {
let cfg = SandboxConfig {
image: "ubuntu:22.04".into(),
memory_mb: 1024,
cpus: 2,
network: true,
env: [("FOO".into(), "bar".into())].into(),
};
assert_eq!(cfg.image, "ubuntu:22.04");
assert_eq!(cfg.memory_mb, 1024);
assert_eq!(cfg.cpus, 2);
assert!(cfg.network);
assert_eq!(cfg.env["FOO"], "bar");
}
#[test]
fn test_sandbox_config_clone() {
let cfg = SandboxConfig {
image: "python:3.12-slim".into(),
..SandboxConfig::default()
};
let cloned = cfg.clone();
assert_eq!(cloned.image, "python:3.12-slim");
assert_eq!(cloned.memory_mb, cfg.memory_mb);
}
struct MockSandbox {
output: String,
exit_code: i32,
}
#[async_trait]
impl BashSandbox for MockSandbox {
async fn exec_command(
&self,
_command: &str,
_guest_workspace: &str,
) -> anyhow::Result<SandboxOutput> {
Ok(SandboxOutput {
stdout: self.output.clone(),
stderr: String::new(),
exit_code: self.exit_code,
})
}
async fn shutdown(&self) {}
}
#[tokio::test]
async fn test_mock_sandbox_success() {
let sandbox = MockSandbox {
output: "hello sandbox\n".into(),
exit_code: 0,
};
let result = sandbox
.exec_command("echo hello sandbox", "/workspace")
.await
.unwrap();
assert_eq!(result.stdout, "hello sandbox\n");
assert_eq!(result.exit_code, 0);
assert!(result.stderr.is_empty());
}
#[tokio::test]
async fn test_mock_sandbox_nonzero_exit() {
let sandbox = MockSandbox {
output: String::new(),
exit_code: 127,
};
let result = sandbox
.exec_command("nonexistent_cmd", "/workspace")
.await
.unwrap();
assert_eq!(result.exit_code, 127);
}
#[tokio::test]
async fn test_bash_sandbox_is_arc_send_sync() {
let sandbox: Arc<dyn BashSandbox> = Arc::new(MockSandbox {
output: "ok".into(),
exit_code: 0,
});
let result = sandbox.exec_command("true", "/workspace").await.unwrap();
assert_eq!(result.exit_code, 0);
}
}