use crate::lab::chaos::ChaosConfig;
use crate::trace::RecorderConfig;
use crate::util::DetRng;
#[derive(Debug, Clone)]
#[allow(clippy::struct_excessive_bools)]
pub struct LabConfig {
pub seed: u64,
pub entropy_seed: u64,
pub worker_count: usize,
pub panic_on_obligation_leak: bool,
pub trace_capacity: usize,
pub futurelock_max_idle_steps: u64,
pub panic_on_futurelock: bool,
pub max_steps: Option<u64>,
pub chaos: Option<ChaosConfig>,
pub replay_recording: Option<RecorderConfig>,
pub auto_advance_time: bool,
pub enable_cancellation_oracle: bool,
pub panic_on_cancellation_violation: bool,
}
impl LabConfig {
#[must_use]
pub const fn new(seed: u64) -> Self {
Self {
seed,
entropy_seed: seed,
worker_count: 1,
panic_on_obligation_leak: true,
trace_capacity: 4096,
futurelock_max_idle_steps: 10_000,
panic_on_futurelock: true,
max_steps: Some(100_000),
chaos: None,
replay_recording: None,
auto_advance_time: false,
enable_cancellation_oracle: true,
panic_on_cancellation_violation: true,
}
}
#[must_use]
pub fn from_time() -> Self {
use std::time::{SystemTime, UNIX_EPOCH};
let seed = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(42, |d| d.as_nanos().min(u128::from(u64::MAX)) as u64);
Self::new(seed)
}
#[must_use]
pub const fn panic_on_leak(mut self, value: bool) -> Self {
self.panic_on_obligation_leak = value;
self
}
#[must_use]
pub const fn trace_capacity(mut self, capacity: usize) -> Self {
self.trace_capacity = capacity;
self
}
#[must_use]
pub const fn worker_count(mut self, count: usize) -> Self {
self.worker_count = if count == 0 { 1 } else { count };
self
}
#[must_use]
pub const fn entropy_seed(mut self, seed: u64) -> Self {
self.entropy_seed = seed;
self
}
#[must_use]
pub const fn futurelock_max_idle_steps(mut self, steps: u64) -> Self {
self.futurelock_max_idle_steps = steps;
self
}
#[must_use]
pub const fn panic_on_futurelock(mut self, value: bool) -> Self {
self.panic_on_futurelock = value;
self
}
#[must_use]
pub const fn max_steps(mut self, steps: u64) -> Self {
self.max_steps = Some(steps);
self
}
#[must_use]
pub const fn no_step_limit(mut self) -> Self {
self.max_steps = None;
self
}
#[must_use]
pub fn with_chaos(mut self, config: ChaosConfig) -> Self {
let chaos_seed = self.seed.wrapping_add(0xCAFE_BABE);
self.chaos = Some(config.with_seed(chaos_seed));
self
}
#[must_use]
pub fn with_light_chaos(self) -> Self {
self.with_chaos(ChaosConfig::light())
}
#[must_use]
pub fn with_heavy_chaos(self) -> Self {
self.with_chaos(ChaosConfig::heavy())
}
#[must_use]
pub fn has_chaos(&self) -> bool {
self.chaos.as_ref().is_some_and(ChaosConfig::is_enabled)
}
#[must_use]
pub fn with_replay_recording(mut self, config: RecorderConfig) -> Self {
self.replay_recording = Some(config);
self
}
#[must_use]
pub fn with_default_replay_recording(self) -> Self {
self.with_replay_recording(RecorderConfig::enabled())
}
#[must_use]
pub const fn with_auto_advance(mut self) -> Self {
self.auto_advance_time = true;
self
}
#[must_use]
pub fn has_replay_recording(&self) -> bool {
self.replay_recording.as_ref().is_some_and(|c| c.enabled)
}
#[must_use]
pub const fn with_cancellation_oracle(mut self, enable: bool) -> Self {
self.enable_cancellation_oracle = enable;
self
}
#[must_use]
pub const fn panic_on_cancellation_violation(mut self, value: bool) -> Self {
self.panic_on_cancellation_violation = value;
self
}
#[must_use]
pub const fn with_cancellation_oracle_warnings(mut self) -> Self {
self.enable_cancellation_oracle = true;
self.panic_on_cancellation_violation = false;
self
}
#[must_use]
pub const fn has_cancellation_oracle(&self) -> bool {
self.enable_cancellation_oracle
}
#[must_use]
pub fn rng(&self) -> DetRng {
DetRng::new(self.seed)
}
}
impl Default for LabConfig {
fn default() -> Self {
Self::new(42)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn init_test(name: &str) {
crate::test_utils::init_test_logging();
crate::test_phase!(name);
}
#[test]
fn default_config() {
init_test("default_config");
let config = LabConfig::default();
let ok = config.seed == 42;
crate::assert_with_log!(ok, "seed", 42, config.seed);
crate::assert_with_log!(
config.entropy_seed == 42,
"entropy_seed",
42,
config.entropy_seed
);
crate::assert_with_log!(
config.worker_count == 1,
"worker_count",
1,
config.worker_count
);
crate::assert_with_log!(
config.panic_on_obligation_leak,
"panic_on_obligation_leak",
true,
config.panic_on_obligation_leak
);
crate::assert_with_log!(
config.panic_on_futurelock,
"panic_on_futurelock",
true,
config.panic_on_futurelock
);
crate::test_complete!("default_config");
}
#[test]
fn rng_is_deterministic() {
init_test("rng_is_deterministic");
let config = LabConfig::new(12345);
let mut rng1 = config.rng();
let mut rng2 = config.rng();
let a = rng1.next_u64();
let b = rng2.next_u64();
crate::assert_with_log!(a == b, "rng equal", b, a);
crate::test_complete!("rng_is_deterministic");
}
#[test]
fn worker_count_clamps_to_one() {
init_test("worker_count_clamps_to_one");
let config = LabConfig::new(7).worker_count(0);
crate::assert_with_log!(
config.worker_count == 1,
"worker_count",
1,
config.worker_count
);
crate::test_complete!("worker_count_clamps_to_one");
}
#[test]
fn lab_config_debug() {
init_test("lab_config_debug");
let cfg = LabConfig::new(42);
let dbg = format!("{cfg:?}");
assert!(dbg.contains("LabConfig"));
crate::test_complete!("lab_config_debug");
}
#[test]
fn lab_config_clone() {
init_test("lab_config_clone");
let cfg = LabConfig::new(99).worker_count(3);
let cfg2 = cfg;
assert_eq!(cfg2.seed, 99);
assert_eq!(cfg2.worker_count, 3);
crate::test_complete!("lab_config_clone");
}
#[test]
fn new_sets_fields() {
init_test("new_sets_fields");
let cfg = LabConfig::new(123);
assert_eq!(cfg.seed, 123);
assert_eq!(cfg.entropy_seed, 123);
assert_eq!(cfg.worker_count, 1);
assert!(cfg.panic_on_obligation_leak);
assert_eq!(cfg.trace_capacity, 4096);
assert_eq!(cfg.futurelock_max_idle_steps, 10_000);
assert!(cfg.panic_on_futurelock);
assert_eq!(cfg.max_steps, Some(100_000));
assert!(cfg.chaos.is_none());
assert!(cfg.replay_recording.is_none());
assert!(!cfg.auto_advance_time);
crate::test_complete!("new_sets_fields");
}
#[test]
fn from_time_creates_valid_config() {
init_test("from_time_creates_valid_config");
let cfg = LabConfig::from_time();
assert_eq!(cfg.entropy_seed, cfg.seed);
assert_eq!(cfg.worker_count, 1);
crate::test_complete!("from_time_creates_valid_config");
}
#[test]
fn panic_on_leak_builder() {
init_test("panic_on_leak_builder");
let cfg = LabConfig::new(1).panic_on_leak(false);
assert!(!cfg.panic_on_obligation_leak);
let cfg = cfg.panic_on_leak(true);
assert!(cfg.panic_on_obligation_leak);
crate::test_complete!("panic_on_leak_builder");
}
#[test]
fn trace_capacity_builder() {
init_test("trace_capacity_builder");
let cfg = LabConfig::new(1).trace_capacity(8192);
assert_eq!(cfg.trace_capacity, 8192);
crate::test_complete!("trace_capacity_builder");
}
#[test]
fn entropy_seed_builder() {
init_test("entropy_seed_builder");
let cfg = LabConfig::new(42).entropy_seed(7);
assert_eq!(cfg.seed, 42);
assert_eq!(cfg.entropy_seed, 7);
crate::test_complete!("entropy_seed_builder");
}
#[test]
fn futurelock_max_idle_steps_builder() {
init_test("futurelock_max_idle_steps_builder");
let cfg = LabConfig::new(1).futurelock_max_idle_steps(5000);
assert_eq!(cfg.futurelock_max_idle_steps, 5000);
crate::test_complete!("futurelock_max_idle_steps_builder");
}
#[test]
fn panic_on_futurelock_builder() {
init_test("panic_on_futurelock_builder");
let cfg = LabConfig::new(1).panic_on_futurelock(false);
assert!(!cfg.panic_on_futurelock);
crate::test_complete!("panic_on_futurelock_builder");
}
#[test]
fn max_steps_builder() {
init_test("max_steps_builder");
let cfg = LabConfig::new(1).max_steps(500);
assert_eq!(cfg.max_steps, Some(500));
crate::test_complete!("max_steps_builder");
}
#[test]
fn no_step_limit_builder() {
init_test("no_step_limit_builder");
let cfg = LabConfig::new(1).no_step_limit();
assert_eq!(cfg.max_steps, None);
crate::test_complete!("no_step_limit_builder");
}
#[test]
fn with_auto_advance_builder() {
init_test("with_auto_advance_builder");
let cfg = LabConfig::new(1);
assert!(!cfg.auto_advance_time);
let cfg = cfg.with_auto_advance();
assert!(cfg.auto_advance_time);
crate::test_complete!("with_auto_advance_builder");
}
#[test]
fn has_chaos_false_by_default() {
init_test("has_chaos_false_by_default");
let cfg = LabConfig::new(1);
assert!(!cfg.has_chaos());
crate::test_complete!("has_chaos_false_by_default");
}
#[test]
fn with_light_chaos_enables() {
init_test("with_light_chaos_enables");
let cfg = LabConfig::new(1).with_light_chaos();
assert!(cfg.has_chaos());
assert!(cfg.chaos.is_some());
crate::test_complete!("with_light_chaos_enables");
}
#[test]
fn with_heavy_chaos_enables() {
init_test("with_heavy_chaos_enables");
let cfg = LabConfig::new(1).with_heavy_chaos();
assert!(cfg.has_chaos());
crate::test_complete!("with_heavy_chaos_enables");
}
#[test]
fn has_replay_recording_false_by_default() {
init_test("has_replay_recording_false_by_default");
let cfg = LabConfig::new(1);
assert!(!cfg.has_replay_recording());
crate::test_complete!("has_replay_recording_false_by_default");
}
#[test]
fn with_default_replay_recording_enables() {
init_test("with_default_replay_recording_enables");
let cfg = LabConfig::new(1).with_default_replay_recording();
assert!(cfg.has_replay_recording());
assert!(cfg.replay_recording.is_some());
crate::test_complete!("with_default_replay_recording_enables");
}
#[test]
fn builder_chaining() {
init_test("builder_chaining");
let cfg = LabConfig::new(99)
.worker_count(4)
.entropy_seed(7)
.trace_capacity(2048)
.panic_on_leak(false)
.futurelock_max_idle_steps(3000)
.panic_on_futurelock(false)
.max_steps(5000)
.with_auto_advance();
assert_eq!(cfg.seed, 99);
assert_eq!(cfg.worker_count, 4);
assert_eq!(cfg.entropy_seed, 7);
assert_eq!(cfg.trace_capacity, 2048);
assert!(!cfg.panic_on_obligation_leak);
assert_eq!(cfg.futurelock_max_idle_steps, 3000);
assert!(!cfg.panic_on_futurelock);
assert_eq!(cfg.max_steps, Some(5000));
assert!(cfg.auto_advance_time);
crate::test_complete!("builder_chaining");
}
#[test]
fn worker_count_positive_value() {
init_test("worker_count_positive_value");
let cfg = LabConfig::new(1).worker_count(8);
assert_eq!(cfg.worker_count, 8);
crate::test_complete!("worker_count_positive_value");
}
#[test]
fn with_chaos_derives_seed() {
init_test("with_chaos_derives_seed");
let cfg = LabConfig::new(42).with_chaos(ChaosConfig::light());
let chaos = cfg.chaos.as_ref().unwrap();
let dbg = format!("{chaos:?}");
assert!(!dbg.is_empty());
crate::test_complete!("with_chaos_derives_seed");
}
}