clnrm_core/backend/
mod.rs

1//! Backend implementations for cleanroom testing
2//!
3//! This module provides backend implementations following core team best practices.
4
5use crate::error::Result;
6use crate::policy::Policy;
7use std::collections::HashMap;
8use std::path::PathBuf;
9
10// Module structure for backends
11pub mod mock;
12pub mod testcontainer;
13pub mod volume;
14
15pub use mock::MockBackend;
16pub use testcontainer::TestcontainerBackend;
17pub use volume::{VolumeMount, VolumeValidator};
18
19/// Get a mock backend for fast testing
20pub fn mock_backend() -> MockBackend {
21    MockBackend::new()
22}
23
24/// Create a mock backend for fast testing
25/// This provides instant responses for testing without Docker overhead
26// pub fn create_mock_backend() -> MockBackend {
27//     MockBackend::new()
28// }
29/// Command to execute with all configuration
30#[derive(Debug, Clone)]
31pub struct Cmd {
32    /// Binary or executable path
33    pub bin: String,
34    /// Arguments to pass to the command
35    pub args: Vec<String>,
36    /// Working directory
37    pub workdir: Option<PathBuf>,
38    /// Environment variables
39    pub env: HashMap<String, String>,
40    /// Policy constraints
41    pub policy: Policy,
42}
43
44/// Result of a command execution
45#[derive(Debug, Clone)]
46pub struct RunResult {
47    /// Exit code of the command
48    pub exit_code: i32,
49    /// Standard output
50    pub stdout: String,
51    /// Standard error
52    pub stderr: String,
53    /// Duration of execution in milliseconds
54    pub duration_ms: u64,
55    /// Individual step results
56    pub steps: Vec<crate::scenario::StepResult>,
57    /// Environment variables that were redacted in forensics
58    pub redacted_env: Vec<String>,
59    /// Backend used for execution
60    pub backend: String,
61    /// Whether execution was concurrent
62    pub concurrent: bool,
63    /// Step execution order (for deterministic ordering)
64    pub step_order: Vec<String>,
65}
66
67impl RunResult {
68    /// Create a new run result
69    pub fn new(exit_code: i32, stdout: String, stderr: String, duration_ms: u64) -> Self {
70        Self {
71            exit_code,
72            stdout,
73            stderr,
74            duration_ms,
75            steps: Vec::new(),
76            redacted_env: Vec::new(),
77            backend: "unknown".to_string(),
78            concurrent: false,
79            step_order: Vec::new(),
80        }
81    }
82
83    /// Check if the command succeeded
84    pub fn success(&self) -> bool {
85        self.exit_code == 0
86    }
87
88    /// Check if the command failed
89    pub fn failed(&self) -> bool {
90        self.exit_code != 0
91    }
92}
93
94impl Cmd {
95    /// Create a new command
96    pub fn new(bin: impl Into<String>) -> Self {
97        Self {
98            bin: bin.into(),
99            args: Vec::new(),
100            workdir: None,
101            env: HashMap::new(),
102            policy: Policy::default(),
103        }
104    }
105
106    /// Add an argument
107    pub fn arg(mut self, arg: impl Into<String>) -> Self {
108        self.args.push(arg.into());
109        self
110    }
111
112    /// Add multiple arguments
113    pub fn args(mut self, args: &[&str]) -> Self {
114        for arg in args {
115            self.args.push(arg.to_string());
116        }
117        self
118    }
119
120    /// Set working directory
121    pub fn workdir(mut self, workdir: PathBuf) -> Self {
122        self.workdir = Some(workdir);
123        self
124    }
125
126    /// Set environment variable
127    pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
128        self.env.insert(key.into(), value.into());
129        self
130    }
131
132    /// Set policy
133    pub fn policy(mut self, policy: Policy) -> Self {
134        self.policy = policy;
135        self
136    }
137}
138
139/// Trait for backend execution environments
140pub trait Backend: Send + Sync + std::fmt::Debug {
141    /// Run a command in the backend
142    fn run_cmd(&self, cmd: Cmd) -> Result<RunResult>;
143    /// Get the name of the backend
144    fn name(&self) -> &str;
145    /// Check if the backend is available
146    fn is_available(&self) -> bool;
147    /// Check if the backend supports hermetic execution
148    fn supports_hermetic(&self) -> bool;
149    /// Check if the backend supports deterministic execution
150    fn supports_deterministic(&self) -> bool;
151}
152
153/// Auto-backend wrapper for testcontainers
154#[derive(Debug)]
155pub struct AutoBackend {
156    /// The underlying testcontainers backend
157    inner: TestcontainerBackend,
158}
159
160impl AutoBackend {
161    /// Create a new AutoBackend with testcontainers
162    pub fn new(backend: TestcontainerBackend) -> Self {
163        Self { inner: backend }
164    }
165
166    /// Create testcontainers backend with default image
167    pub fn detect() -> Result<Self> {
168        let backend = TestcontainerBackend::new("alpine:latest")?;
169        Ok(Self { inner: backend })
170    }
171
172    /// Create backend from name (only supports testcontainers now)
173    pub fn from_name(name: &str) -> Result<Self> {
174        match name {
175            "testcontainers" | "auto" => Self::detect(),
176            _ => Err(crate::error::CleanroomError::new(
177                crate::error::ErrorKind::ConfigurationError,
178                format!(
179                    "Unknown backend: {}. Only 'testcontainers' and 'auto' are supported",
180                    name
181                ),
182            )),
183        }
184    }
185
186    /// Get the resolved backend name
187    pub fn resolved_backend(&self) -> String {
188        self.inner.name().to_string()
189    }
190
191    /// Check if testcontainers backend is available
192    pub fn is_backend_available(name: &str) -> bool {
193        match name {
194            "testcontainers" | "auto" => TestcontainerBackend::is_available(),
195            _ => false,
196        }
197    }
198}
199
200impl Backend for AutoBackend {
201    fn run_cmd(&self, cmd: Cmd) -> Result<RunResult> {
202        self.inner.run_cmd(cmd)
203    }
204
205    fn name(&self) -> &str {
206        self.inner.name()
207    }
208
209    fn is_available(&self) -> bool {
210        self.inner.is_available()
211    }
212
213    fn supports_hermetic(&self) -> bool {
214        self.inner.supports_hermetic()
215    }
216
217    fn supports_deterministic(&self) -> bool {
218        self.inner.supports_deterministic()
219    }
220}