1use crate::lab::chaos::ChaosConfig;
113use crate::trace::RecorderConfig;
114use crate::util::DetRng;
115
116#[derive(Debug, Clone)]
118#[allow(clippy::struct_excessive_bools)]
119pub struct LabConfig {
120 pub seed: u64,
122 pub entropy_seed: u64,
127 pub worker_count: usize,
132 pub panic_on_obligation_leak: bool,
134 pub trace_capacity: usize,
136 pub futurelock_max_idle_steps: u64,
140 pub panic_on_futurelock: bool,
142 pub max_steps: Option<u64>,
144 pub chaos: Option<ChaosConfig>,
149 pub replay_recording: Option<RecorderConfig>,
154 pub auto_advance_time: bool,
161 pub enable_cancellation_oracle: bool,
166 pub panic_on_cancellation_violation: bool,
170}
171
172impl LabConfig {
173 #[must_use]
175 pub const fn new(seed: u64) -> Self {
176 Self {
177 seed,
178 entropy_seed: seed,
179 worker_count: 1,
180 panic_on_obligation_leak: true,
181 trace_capacity: 4096,
182 futurelock_max_idle_steps: 10_000,
183 panic_on_futurelock: true,
184 max_steps: Some(100_000),
185 chaos: None,
186 replay_recording: None,
187 auto_advance_time: false,
188 enable_cancellation_oracle: true,
189 panic_on_cancellation_violation: true,
190 }
191 }
192
193 #[must_use]
195 pub fn from_time() -> Self {
196 use std::time::{SystemTime, UNIX_EPOCH};
197 let seed = SystemTime::now()
198 .duration_since(UNIX_EPOCH)
199 .map_or(42, |d| d.as_nanos().min(u128::from(u64::MAX)) as u64);
200 Self::new(seed)
201 }
202
203 #[must_use]
205 pub const fn panic_on_leak(mut self, value: bool) -> Self {
206 self.panic_on_obligation_leak = value;
207 self
208 }
209
210 #[must_use]
212 pub const fn trace_capacity(mut self, capacity: usize) -> Self {
213 self.trace_capacity = capacity;
214 self
215 }
216
217 #[must_use]
221 pub const fn worker_count(mut self, count: usize) -> Self {
222 self.worker_count = if count == 0 { 1 } else { count };
223 self
224 }
225
226 #[must_use]
228 pub const fn entropy_seed(mut self, seed: u64) -> Self {
229 self.entropy_seed = seed;
230 self
231 }
232
233 #[must_use]
235 pub const fn futurelock_max_idle_steps(mut self, steps: u64) -> Self {
236 self.futurelock_max_idle_steps = steps;
237 self
238 }
239
240 #[must_use]
242 pub const fn panic_on_futurelock(mut self, value: bool) -> Self {
243 self.panic_on_futurelock = value;
244 self
245 }
246
247 #[must_use]
249 pub const fn max_steps(mut self, steps: u64) -> Self {
250 self.max_steps = Some(steps);
251 self
252 }
253
254 #[must_use]
256 pub const fn no_step_limit(mut self) -> Self {
257 self.max_steps = None;
258 self
259 }
260
261 #[must_use]
265 pub fn with_chaos(mut self, config: ChaosConfig) -> Self {
266 let chaos_seed = self.seed.wrapping_add(0xCAFE_BABE);
268 self.chaos = Some(config.with_seed(chaos_seed));
269 self
270 }
271
272 #[must_use]
274 pub fn with_light_chaos(self) -> Self {
275 self.with_chaos(ChaosConfig::light())
276 }
277
278 #[must_use]
280 pub fn with_heavy_chaos(self) -> Self {
281 self.with_chaos(ChaosConfig::heavy())
282 }
283
284 #[must_use]
286 pub fn has_chaos(&self) -> bool {
287 self.chaos.as_ref().is_some_and(ChaosConfig::is_enabled)
288 }
289
290 #[must_use]
292 pub fn with_replay_recording(mut self, config: RecorderConfig) -> Self {
293 self.replay_recording = Some(config);
294 self
295 }
296
297 #[must_use]
299 pub fn with_default_replay_recording(self) -> Self {
300 self.with_replay_recording(RecorderConfig::enabled())
301 }
302
303 #[must_use]
309 pub const fn with_auto_advance(mut self) -> Self {
310 self.auto_advance_time = true;
311 self
312 }
313
314 #[must_use]
316 pub fn has_replay_recording(&self) -> bool {
317 self.replay_recording.as_ref().is_some_and(|c| c.enabled)
318 }
319
320 #[must_use]
322 pub const fn with_cancellation_oracle(mut self, enable: bool) -> Self {
323 self.enable_cancellation_oracle = enable;
324 self
325 }
326
327 #[must_use]
331 pub const fn panic_on_cancellation_violation(mut self, value: bool) -> Self {
332 self.panic_on_cancellation_violation = value;
333 self
334 }
335
336 #[must_use]
338 pub const fn with_cancellation_oracle_warnings(mut self) -> Self {
339 self.enable_cancellation_oracle = true;
340 self.panic_on_cancellation_violation = false;
341 self
342 }
343
344 #[must_use]
346 pub const fn has_cancellation_oracle(&self) -> bool {
347 self.enable_cancellation_oracle
348 }
349
350 #[must_use]
352 pub fn rng(&self) -> DetRng {
353 DetRng::new(self.seed)
354 }
355}
356
357impl Default for LabConfig {
358 fn default() -> Self {
359 Self::new(42)
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366
367 fn init_test(name: &str) {
368 crate::test_utils::init_test_logging();
369 crate::test_phase!(name);
370 }
371
372 #[test]
373 fn default_config() {
374 init_test("default_config");
375 let config = LabConfig::default();
376 let ok = config.seed == 42;
377 crate::assert_with_log!(ok, "seed", 42, config.seed);
378 crate::assert_with_log!(
379 config.entropy_seed == 42,
380 "entropy_seed",
381 42,
382 config.entropy_seed
383 );
384 crate::assert_with_log!(
385 config.worker_count == 1,
386 "worker_count",
387 1,
388 config.worker_count
389 );
390 crate::assert_with_log!(
391 config.panic_on_obligation_leak,
392 "panic_on_obligation_leak",
393 true,
394 config.panic_on_obligation_leak
395 );
396 crate::assert_with_log!(
397 config.panic_on_futurelock,
398 "panic_on_futurelock",
399 true,
400 config.panic_on_futurelock
401 );
402 crate::test_complete!("default_config");
403 }
404
405 #[test]
406 fn rng_is_deterministic() {
407 init_test("rng_is_deterministic");
408 let config = LabConfig::new(12345);
409 let mut rng1 = config.rng();
410 let mut rng2 = config.rng();
411
412 let a = rng1.next_u64();
413 let b = rng2.next_u64();
414 crate::assert_with_log!(a == b, "rng equal", b, a);
415 crate::test_complete!("rng_is_deterministic");
416 }
417
418 #[test]
419 fn worker_count_clamps_to_one() {
420 init_test("worker_count_clamps_to_one");
421 let config = LabConfig::new(7).worker_count(0);
422 crate::assert_with_log!(
423 config.worker_count == 1,
424 "worker_count",
425 1,
426 config.worker_count
427 );
428 crate::test_complete!("worker_count_clamps_to_one");
429 }
430
431 #[test]
432 fn lab_config_debug() {
433 init_test("lab_config_debug");
434 let cfg = LabConfig::new(42);
435 let dbg = format!("{cfg:?}");
436 assert!(dbg.contains("LabConfig"));
437 crate::test_complete!("lab_config_debug");
438 }
439
440 #[test]
441 fn lab_config_clone() {
442 init_test("lab_config_clone");
443 let cfg = LabConfig::new(99).worker_count(3);
444 let cfg2 = cfg;
445 assert_eq!(cfg2.seed, 99);
446 assert_eq!(cfg2.worker_count, 3);
447 crate::test_complete!("lab_config_clone");
448 }
449
450 #[test]
451 fn new_sets_fields() {
452 init_test("new_sets_fields");
453 let cfg = LabConfig::new(123);
454 assert_eq!(cfg.seed, 123);
455 assert_eq!(cfg.entropy_seed, 123);
456 assert_eq!(cfg.worker_count, 1);
457 assert!(cfg.panic_on_obligation_leak);
458 assert_eq!(cfg.trace_capacity, 4096);
459 assert_eq!(cfg.futurelock_max_idle_steps, 10_000);
460 assert!(cfg.panic_on_futurelock);
461 assert_eq!(cfg.max_steps, Some(100_000));
462 assert!(cfg.chaos.is_none());
463 assert!(cfg.replay_recording.is_none());
464 assert!(!cfg.auto_advance_time);
465 crate::test_complete!("new_sets_fields");
466 }
467
468 #[test]
469 fn from_time_creates_valid_config() {
470 init_test("from_time_creates_valid_config");
471 let cfg = LabConfig::from_time();
472 assert_eq!(cfg.entropy_seed, cfg.seed);
474 assert_eq!(cfg.worker_count, 1);
475 crate::test_complete!("from_time_creates_valid_config");
476 }
477
478 #[test]
479 fn panic_on_leak_builder() {
480 init_test("panic_on_leak_builder");
481 let cfg = LabConfig::new(1).panic_on_leak(false);
482 assert!(!cfg.panic_on_obligation_leak);
483 let cfg = cfg.panic_on_leak(true);
484 assert!(cfg.panic_on_obligation_leak);
485 crate::test_complete!("panic_on_leak_builder");
486 }
487
488 #[test]
489 fn trace_capacity_builder() {
490 init_test("trace_capacity_builder");
491 let cfg = LabConfig::new(1).trace_capacity(8192);
492 assert_eq!(cfg.trace_capacity, 8192);
493 crate::test_complete!("trace_capacity_builder");
494 }
495
496 #[test]
497 fn entropy_seed_builder() {
498 init_test("entropy_seed_builder");
499 let cfg = LabConfig::new(42).entropy_seed(7);
500 assert_eq!(cfg.seed, 42);
501 assert_eq!(cfg.entropy_seed, 7);
502 crate::test_complete!("entropy_seed_builder");
503 }
504
505 #[test]
506 fn futurelock_max_idle_steps_builder() {
507 init_test("futurelock_max_idle_steps_builder");
508 let cfg = LabConfig::new(1).futurelock_max_idle_steps(5000);
509 assert_eq!(cfg.futurelock_max_idle_steps, 5000);
510 crate::test_complete!("futurelock_max_idle_steps_builder");
511 }
512
513 #[test]
514 fn panic_on_futurelock_builder() {
515 init_test("panic_on_futurelock_builder");
516 let cfg = LabConfig::new(1).panic_on_futurelock(false);
517 assert!(!cfg.panic_on_futurelock);
518 crate::test_complete!("panic_on_futurelock_builder");
519 }
520
521 #[test]
522 fn max_steps_builder() {
523 init_test("max_steps_builder");
524 let cfg = LabConfig::new(1).max_steps(500);
525 assert_eq!(cfg.max_steps, Some(500));
526 crate::test_complete!("max_steps_builder");
527 }
528
529 #[test]
530 fn no_step_limit_builder() {
531 init_test("no_step_limit_builder");
532 let cfg = LabConfig::new(1).no_step_limit();
533 assert_eq!(cfg.max_steps, None);
534 crate::test_complete!("no_step_limit_builder");
535 }
536
537 #[test]
538 fn with_auto_advance_builder() {
539 init_test("with_auto_advance_builder");
540 let cfg = LabConfig::new(1);
541 assert!(!cfg.auto_advance_time);
542 let cfg = cfg.with_auto_advance();
543 assert!(cfg.auto_advance_time);
544 crate::test_complete!("with_auto_advance_builder");
545 }
546
547 #[test]
548 fn has_chaos_false_by_default() {
549 init_test("has_chaos_false_by_default");
550 let cfg = LabConfig::new(1);
551 assert!(!cfg.has_chaos());
552 crate::test_complete!("has_chaos_false_by_default");
553 }
554
555 #[test]
556 fn with_light_chaos_enables() {
557 init_test("with_light_chaos_enables");
558 let cfg = LabConfig::new(1).with_light_chaos();
559 assert!(cfg.has_chaos());
560 assert!(cfg.chaos.is_some());
561 crate::test_complete!("with_light_chaos_enables");
562 }
563
564 #[test]
565 fn with_heavy_chaos_enables() {
566 init_test("with_heavy_chaos_enables");
567 let cfg = LabConfig::new(1).with_heavy_chaos();
568 assert!(cfg.has_chaos());
569 crate::test_complete!("with_heavy_chaos_enables");
570 }
571
572 #[test]
573 fn has_replay_recording_false_by_default() {
574 init_test("has_replay_recording_false_by_default");
575 let cfg = LabConfig::new(1);
576 assert!(!cfg.has_replay_recording());
577 crate::test_complete!("has_replay_recording_false_by_default");
578 }
579
580 #[test]
581 fn with_default_replay_recording_enables() {
582 init_test("with_default_replay_recording_enables");
583 let cfg = LabConfig::new(1).with_default_replay_recording();
584 assert!(cfg.has_replay_recording());
585 assert!(cfg.replay_recording.is_some());
586 crate::test_complete!("with_default_replay_recording_enables");
587 }
588
589 #[test]
590 fn builder_chaining() {
591 init_test("builder_chaining");
592 let cfg = LabConfig::new(99)
593 .worker_count(4)
594 .entropy_seed(7)
595 .trace_capacity(2048)
596 .panic_on_leak(false)
597 .futurelock_max_idle_steps(3000)
598 .panic_on_futurelock(false)
599 .max_steps(5000)
600 .with_auto_advance();
601 assert_eq!(cfg.seed, 99);
602 assert_eq!(cfg.worker_count, 4);
603 assert_eq!(cfg.entropy_seed, 7);
604 assert_eq!(cfg.trace_capacity, 2048);
605 assert!(!cfg.panic_on_obligation_leak);
606 assert_eq!(cfg.futurelock_max_idle_steps, 3000);
607 assert!(!cfg.panic_on_futurelock);
608 assert_eq!(cfg.max_steps, Some(5000));
609 assert!(cfg.auto_advance_time);
610 crate::test_complete!("builder_chaining");
611 }
612
613 #[test]
614 fn worker_count_positive_value() {
615 init_test("worker_count_positive_value");
616 let cfg = LabConfig::new(1).worker_count(8);
617 assert_eq!(cfg.worker_count, 8);
618 crate::test_complete!("worker_count_positive_value");
619 }
620
621 #[test]
622 fn with_chaos_derives_seed() {
623 init_test("with_chaos_derives_seed");
624 let cfg = LabConfig::new(42).with_chaos(ChaosConfig::light());
625 let chaos = cfg.chaos.as_ref().unwrap();
626 let dbg = format!("{chaos:?}");
628 assert!(!dbg.is_empty());
629 crate::test_complete!("with_chaos_derives_seed");
630 }
631}