use std::future::Future;
use std::sync::Arc;
use super::clock::SimClock;
use super::config::SimConfig;
use super::fault::{FaultConfig, FaultInjector, FaultInjectorBuilder};
use super::llm::SimLLM;
use super::network::SimNetwork;
use super::rng::DeterministicRng;
use super::storage::SimStorage;
pub struct SimEnvironment {
pub config: SimConfig,
pub clock: SimClock,
pub rng: DeterministicRng,
pub faults: Arc<FaultInjector>,
pub storage: SimStorage,
pub network: SimNetwork,
pub llm: SimLLM,
}
impl SimEnvironment {
pub fn advance_time_ms(&self, ms: u64) -> u64 {
self.clock.advance_ms(ms)
}
pub fn advance_time_secs(&self, secs: f64) -> u64 {
self.clock.advance_secs(secs)
}
#[must_use]
pub fn now_ms(&self) -> u64 {
self.clock.now_ms()
}
pub async fn sleep_ms(&self, ms: u64) {
self.clock.sleep_ms(ms).await;
}
}
pub struct Simulation {
config: SimConfig,
fault_configs: Vec<FaultConfig>,
}
impl Simulation {
#[must_use]
pub fn new(config: SimConfig) -> Self {
Self {
config,
fault_configs: Vec::new(),
}
}
#[must_use]
pub fn with_fault(mut self, fault_config: FaultConfig) -> Self {
self.fault_configs.push(fault_config);
self
}
#[must_use]
pub fn with_storage_faults(self, probability: f64) -> Self {
use super::fault::FaultType;
self.with_fault(FaultConfig::new(FaultType::StorageWriteFail, probability))
.with_fault(FaultConfig::new(FaultType::StorageReadFail, probability))
}
#[must_use]
pub fn with_db_faults(self, probability: f64) -> Self {
use super::fault::FaultType;
self.with_fault(FaultConfig::new(FaultType::DbConnectionFail, probability))
.with_fault(FaultConfig::new(FaultType::DbQueryTimeout, probability))
}
#[must_use]
pub fn with_llm_faults(self, probability: f64) -> Self {
use super::fault::FaultType;
self.with_fault(FaultConfig::new(FaultType::LlmTimeout, probability))
.with_fault(FaultConfig::new(FaultType::LlmRateLimit, probability))
}
pub async fn run<F, Fut, E>(self, test_fn: F) -> Result<(), E>
where
F: FnOnce(SimEnvironment) -> Fut,
Fut: Future<Output = Result<(), E>>,
{
let mut rng = DeterministicRng::new(self.config.seed());
let clock = SimClock::new();
let mut fault_builder = FaultInjectorBuilder::new(rng.fork());
for fault_config in self.fault_configs {
fault_builder = fault_builder.with_fault(fault_config);
}
let faults = Arc::new(fault_builder.build());
let storage = SimStorage::new(
clock.clone(),
rng.fork(),
Arc::clone(&faults), );
let network = SimNetwork::new(
clock.clone(),
rng.fork(),
Arc::clone(&faults), );
let llm = SimLLM::new(
clock.clone(),
rng.fork(),
Arc::clone(&faults), );
let env = SimEnvironment {
config: self.config,
clock,
rng,
faults,
storage,
network,
llm,
};
let result = test_fn(env).await;
result
}
#[must_use]
pub fn build(self) -> SimEnvironment {
let mut rng = DeterministicRng::new(self.config.seed());
let clock = SimClock::new();
let mut fault_builder = FaultInjectorBuilder::new(rng.fork());
for fault_config in self.fault_configs {
fault_builder = fault_builder.with_fault(fault_config);
}
let faults = Arc::new(fault_builder.build());
let storage = SimStorage::new(clock.clone(), rng.fork(), Arc::clone(&faults));
let network = SimNetwork::new(clock.clone(), rng.fork(), Arc::clone(&faults));
let llm = SimLLM::new(clock.clone(), rng.fork(), Arc::clone(&faults));
SimEnvironment {
config: self.config,
clock,
rng,
faults,
storage,
network,
llm,
}
}
}
#[must_use]
pub fn create_simulation(seed: Option<u64>) -> Simulation {
let config = match seed {
Some(s) => SimConfig::with_seed(s),
None => SimConfig::from_env_or_random(),
};
Simulation::new(config)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dst::fault::FaultType;
use crate::dst::storage::StorageError;
#[tokio::test]
async fn test_basic_simulation() {
let sim = Simulation::new(SimConfig::with_seed(42));
sim.run(|mut env| async move {
env.storage.write("key", b"value").await?;
env.advance_time_ms(1000);
let result = env.storage.read("key").await?;
assert_eq!(result, Some(b"value".to_vec()));
assert_eq!(env.now_ms(), 1000);
Ok::<(), StorageError>(())
})
.await
.unwrap();
}
#[tokio::test]
async fn test_simulation_build() {
let sim = Simulation::new(SimConfig::with_seed(42));
let mut env = sim.build();
env.storage.write("key", b"value").await.unwrap();
let result = env.storage.read("key").await.unwrap();
assert_eq!(result, Some(b"value".to_vec()));
}
#[tokio::test]
async fn test_simulation_determinism() {
let mut results1 = Vec::new();
let mut results2 = Vec::new();
let sim1 = Simulation::new(SimConfig::with_seed(12345));
sim1.run(|mut env| async move {
for _ in 0..10 {
results1.push(env.rng.next_float());
}
Ok::<(), StorageError>(())
})
.await
.unwrap();
let sim2 = Simulation::new(SimConfig::with_seed(12345));
sim2.run(|mut env| async move {
for _ in 0..10 {
results2.push(env.rng.next_float());
}
Ok::<(), StorageError>(())
})
.await
.unwrap();
}
#[tokio::test]
async fn test_create_simulation() {
let sim = create_simulation(Some(42));
let env = sim.build();
assert_eq!(env.config.seed(), 42);
}
#[test]
fn test_fluent_api() {
let sim = Simulation::new(SimConfig::with_seed(42))
.with_storage_faults(0.1)
.with_db_faults(0.05)
.with_llm_faults(0.01);
let _env = sim.build();
}
#[tokio::test]
async fn test_fault_injection_through_harness() {
let sim = Simulation::new(SimConfig::with_seed(42))
.with_fault(FaultConfig::new(FaultType::StorageWriteFail, 1.0));
let result = sim
.run(|mut env| async move {
env.storage.write("key", b"value").await?;
Ok::<(), StorageError>(())
})
.await;
assert!(
result.is_err(),
"Fault injection should have caused write to fail!"
);
}
#[tokio::test]
async fn test_fault_stats_shared() {
let sim = Simulation::new(SimConfig::with_seed(42))
.with_fault(FaultConfig::new(FaultType::StorageWriteFail, 1.0));
let env = sim.build();
assert_eq!(env.faults.total_injections(), 0);
}
}