use std::future::Future;
use std::sync::Arc;
use lix::LixError;
use lix::storage::Memory;
use lix::{engine::Engine, init::InitReceipt};
use super::expect_same::{
SharedExpectSameCase, SharedExpectSameRun, SharedExpectSameRunGuard, SimulationAssertions,
};
use super::mode::{SimulationMode, SimulationOptions};
use super::rebuild_tracked_state::deterministic_timestamp_shuffle_for;
use super::simulation::Simulation;
pub async fn run_simulation_test<F, Fut>(
mode: SimulationMode,
options: SimulationOptions,
case_id: &str,
test_fn: F,
) where
F: Fn(Simulation) -> Fut,
Fut: Future<Output = ()>,
{
if mode == SimulationMode::Base {
run_single_simulation_test(mode, options, case_id, test_fn).await;
return;
}
let shared_case = SharedExpectSameCase::new();
run_single_simulation_test_with_case(
SimulationMode::Base,
options,
case_id,
shared_case.clone(),
&test_fn,
)
.await;
run_single_simulation_test_with_case(mode, options, case_id, shared_case, &test_fn).await;
}
async fn run_single_simulation_test<F, Fut>(
mode: SimulationMode,
options: SimulationOptions,
case_id: &str,
test_fn: F,
) where
F: Fn(Simulation) -> Fut,
Fut: Future<Output = ()>,
{
run_single_simulation_test_with_case(
mode,
options,
case_id,
SharedExpectSameCase::new(),
&test_fn,
)
.await;
}
async fn run_single_simulation_test_with_case<F, Fut>(
mode: SimulationMode,
options: SimulationOptions,
case_id: &str,
shared_case: Arc<SharedExpectSameCase>,
test_fn: &F,
) where
F: Fn(Simulation) -> Fut,
Fut: Future<Output = ()>,
{
let bootstrap = Bootstrap::create()
.await
.expect("simulation bootstrap should initialize");
let expect_same = SharedExpectSameRun::with_case(case_id, mode, shared_case);
let _guard = SharedExpectSameRunGuard::new(expect_same.clone());
let sim = Simulation::from_bootstrap(
mode,
options,
bootstrap.storage,
bootstrap.receipt,
SimulationAssertions::shared(expect_same),
)
.await
.expect("simulation mode should boot");
test_fn(sim.clone()).await;
sim.finish();
}
#[derive(Clone)]
struct Bootstrap {
storage: Memory,
receipt: InitReceipt,
}
impl Bootstrap {
async fn create() -> Result<Self, LixError> {
let storage = Memory::new();
let receipt = Engine::initialize(storage.clone()).await?;
Ok(Self { storage, receipt })
}
}
pub(crate) async fn enable_deterministic_mode(
engine: &Engine,
receipt: &InitReceipt,
mode: SimulationMode,
) -> Result<(), LixError> {
let timestamp_shuffle = deterministic_timestamp_shuffle_for(mode);
let session = engine
.open_session_at(receipt.main_branch_id.clone())
.await?;
match session
.execute(&deterministic_mode_insert_sql(timestamp_shuffle), &[])
.await
{
Ok(_) => {}
Err(error) if error.code == "LIX_UNSUPPORTED_SQL" => {}
Err(error) => return Err(error),
}
Ok(())
}
fn deterministic_mode_insert_sql(timestamp_shuffle: bool) -> String {
format!(
"INSERT INTO lix_key_value (key, value, lixcol_global, lixcol_untracked) \
VALUES ('lix_deterministic_mode', \
CAST('{{\"enabled\":true,\"timestamp_shuffle\":{timestamp_shuffle}}}' AS JSONB), true, true)"
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deterministic_mode_write_sql_carries_timestamp_shuffle_flag() {
assert!(deterministic_mode_insert_sql(true).contains("\"timestamp_shuffle\":true"));
assert!(deterministic_mode_insert_sql(false).contains("\"timestamp_shuffle\":false"));
}
}