umi_memory/dst/
simulation.rs1use std::future::Future;
6use std::sync::Arc;
7
8use super::clock::SimClock;
9use super::config::SimConfig;
10use super::fault::{FaultConfig, FaultInjector, FaultInjectorBuilder};
11use super::llm::SimLLM;
12use super::network::SimNetwork;
13use super::rng::DeterministicRng;
14use super::storage::SimStorage;
15
16pub struct SimEnvironment {
20 pub config: SimConfig,
22 pub clock: SimClock,
24 pub rng: DeterministicRng,
26 pub faults: Arc<FaultInjector>,
28 pub storage: SimStorage,
30 pub network: SimNetwork,
32 pub llm: SimLLM,
34}
35
36impl SimEnvironment {
37 pub fn advance_time_ms(&self, ms: u64) -> u64 {
39 self.clock.advance_ms(ms)
40 }
41
42 pub fn advance_time_secs(&self, secs: f64) -> u64 {
44 self.clock.advance_secs(secs)
45 }
46
47 #[must_use]
49 pub fn now_ms(&self) -> u64 {
50 self.clock.now_ms()
51 }
52
53 pub async fn sleep_ms(&self, ms: u64) {
55 self.clock.sleep_ms(ms).await;
56 }
57}
58
59pub struct Simulation {
86 config: SimConfig,
87 fault_configs: Vec<FaultConfig>,
88}
89
90impl Simulation {
91 #[must_use]
93 pub fn new(config: SimConfig) -> Self {
94 Self {
95 config,
96 fault_configs: Vec::new(),
97 }
98 }
99
100 #[must_use]
104 pub fn with_fault(mut self, fault_config: FaultConfig) -> Self {
105 self.fault_configs.push(fault_config);
106 self
107 }
108
109 #[must_use]
113 pub fn with_storage_faults(self, probability: f64) -> Self {
114 use super::fault::FaultType;
115
116 self.with_fault(FaultConfig::new(FaultType::StorageWriteFail, probability))
117 .with_fault(FaultConfig::new(FaultType::StorageReadFail, probability))
118 }
119
120 #[must_use]
122 pub fn with_db_faults(self, probability: f64) -> Self {
123 use super::fault::FaultType;
124
125 self.with_fault(FaultConfig::new(FaultType::DbConnectionFail, probability))
126 .with_fault(FaultConfig::new(FaultType::DbQueryTimeout, probability))
127 }
128
129 #[must_use]
131 pub fn with_llm_faults(self, probability: f64) -> Self {
132 use super::fault::FaultType;
133
134 self.with_fault(FaultConfig::new(FaultType::LlmTimeout, probability))
135 .with_fault(FaultConfig::new(FaultType::LlmRateLimit, probability))
136 }
137
138 pub async fn run<F, Fut, E>(self, test_fn: F) -> Result<(), E>
145 where
146 F: FnOnce(SimEnvironment) -> Fut,
147 Fut: Future<Output = Result<(), E>>,
148 {
149 let mut rng = DeterministicRng::new(self.config.seed());
151 let clock = SimClock::new();
152
153 let mut fault_builder = FaultInjectorBuilder::new(rng.fork());
155 for fault_config in self.fault_configs {
156 fault_builder = fault_builder.with_fault(fault_config);
157 }
158 let faults = Arc::new(fault_builder.build());
160
161 let storage = SimStorage::new(
163 clock.clone(),
164 rng.fork(),
165 Arc::clone(&faults), );
167
168 let network = SimNetwork::new(
170 clock.clone(),
171 rng.fork(),
172 Arc::clone(&faults), );
174
175 let llm = SimLLM::new(
177 clock.clone(),
178 rng.fork(),
179 Arc::clone(&faults), );
181
182 let env = SimEnvironment {
183 config: self.config,
184 clock,
185 rng,
186 faults,
187 storage,
188 network,
189 llm,
190 };
191
192 let result = test_fn(env).await;
194
195 result
199 }
200
201 #[must_use]
205 pub fn build(self) -> SimEnvironment {
206 let mut rng = DeterministicRng::new(self.config.seed());
207 let clock = SimClock::new();
208
209 let mut fault_builder = FaultInjectorBuilder::new(rng.fork());
211 for fault_config in self.fault_configs {
212 fault_builder = fault_builder.with_fault(fault_config);
213 }
214 let faults = Arc::new(fault_builder.build());
216
217 let storage = SimStorage::new(clock.clone(), rng.fork(), Arc::clone(&faults));
219
220 let network = SimNetwork::new(clock.clone(), rng.fork(), Arc::clone(&faults));
222
223 let llm = SimLLM::new(clock.clone(), rng.fork(), Arc::clone(&faults));
225
226 SimEnvironment {
227 config: self.config,
228 clock,
229 rng,
230 faults,
231 storage,
232 network,
233 llm,
234 }
235 }
236}
237
238#[must_use]
242pub fn create_simulation(seed: Option<u64>) -> Simulation {
243 let config = match seed {
244 Some(s) => SimConfig::with_seed(s),
245 None => SimConfig::from_env_or_random(),
246 };
247 Simulation::new(config)
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253 use crate::dst::fault::FaultType;
254 use crate::dst::storage::StorageError;
255
256 #[tokio::test]
257 async fn test_basic_simulation() {
258 let sim = Simulation::new(SimConfig::with_seed(42));
259
260 sim.run(|mut env| async move {
261 env.storage.write("key", b"value").await?;
262 env.advance_time_ms(1000);
263 let result = env.storage.read("key").await?;
264
265 assert_eq!(result, Some(b"value".to_vec()));
266 assert_eq!(env.now_ms(), 1000);
267
268 Ok::<(), StorageError>(())
269 })
270 .await
271 .unwrap();
272 }
273
274 #[tokio::test]
275 async fn test_simulation_build() {
276 let sim = Simulation::new(SimConfig::with_seed(42));
277 let mut env = sim.build();
278
279 env.storage.write("key", b"value").await.unwrap();
280 let result = env.storage.read("key").await.unwrap();
281
282 assert_eq!(result, Some(b"value".to_vec()));
283 }
284
285 #[tokio::test]
286 async fn test_simulation_determinism() {
287 let mut results1 = Vec::new();
288 let mut results2 = Vec::new();
289
290 let sim1 = Simulation::new(SimConfig::with_seed(12345));
292 sim1.run(|mut env| async move {
293 for _ in 0..10 {
294 results1.push(env.rng.next_float());
295 }
296 Ok::<(), StorageError>(())
297 })
298 .await
299 .unwrap();
300
301 let sim2 = Simulation::new(SimConfig::with_seed(12345));
303 sim2.run(|mut env| async move {
304 for _ in 0..10 {
305 results2.push(env.rng.next_float());
306 }
307 Ok::<(), StorageError>(())
308 })
309 .await
310 .unwrap();
311
312 }
315
316 #[tokio::test]
317 async fn test_create_simulation() {
318 let sim = create_simulation(Some(42));
319 let env = sim.build();
320 assert_eq!(env.config.seed(), 42);
321 }
322
323 #[test]
324 fn test_fluent_api() {
325 let sim = Simulation::new(SimConfig::with_seed(42))
326 .with_storage_faults(0.1)
327 .with_db_faults(0.05)
328 .with_llm_faults(0.01);
329
330 let _env = sim.build();
332 }
333
334 #[tokio::test]
339 async fn test_fault_injection_through_harness() {
340 let sim = Simulation::new(SimConfig::with_seed(42))
342 .with_fault(FaultConfig::new(FaultType::StorageWriteFail, 1.0));
343
344 let result = sim
345 .run(|mut env| async move {
346 env.storage.write("key", b"value").await?;
348 Ok::<(), StorageError>(())
349 })
350 .await;
351
352 assert!(
354 result.is_err(),
355 "Fault injection should have caused write to fail!"
356 );
357 }
358
359 #[tokio::test]
361 async fn test_fault_stats_shared() {
362 let sim = Simulation::new(SimConfig::with_seed(42))
363 .with_fault(FaultConfig::new(FaultType::StorageWriteFail, 1.0));
364
365 let env = sim.build();
366
367 assert_eq!(env.faults.total_injections(), 0);
370 }
371}