moonpool_sim/chaos/
buggify.rs1use crate::sim::rng::sim_random;
7use std::cell::RefCell;
8use std::collections::HashMap;
9
10thread_local! {
11 static STATE: RefCell<State> = RefCell::new(State::default());
12}
13
14#[derive(Default)]
15struct State {
16 enabled: bool,
17 active_locations: HashMap<String, bool>,
18 activation_prob: f64,
19}
20
21pub fn buggify_init(activation_prob: f64, _firing_prob: f64) {
23 STATE.with(|state| {
24 let mut state = state.borrow_mut();
25 state.enabled = true;
26 state.active_locations.clear();
27 state.activation_prob = activation_prob;
28 });
29}
30
31pub fn buggify_reset() {
33 STATE.with(|state| {
34 let mut state = state.borrow_mut();
35 state.enabled = false;
36 state.active_locations.clear();
37 state.activation_prob = 0.0;
38 });
39}
40
41pub fn buggify_internal(prob: f64, location: &'static str) -> bool {
43 STATE.with(|state| {
44 let mut state = state.borrow_mut();
45
46 if !state.enabled || prob <= 0.0 {
47 return false;
48 }
49
50 let location_str = location.to_string();
51 let activation_prob = state.activation_prob;
52
53 let is_active = *state
55 .active_locations
56 .entry(location_str)
57 .or_insert_with(|| sim_random::<f64>() < activation_prob);
58
59 is_active && sim_random::<f64>() < prob
61 })
62}
63
64#[macro_export]
66macro_rules! buggify {
67 () => {
68 $crate::chaos::buggify::buggify_internal(0.25, concat!(file!(), ":", line!()))
69 };
70}
71
72#[macro_export]
74macro_rules! buggify_with_prob {
75 ($prob:expr) => {
76 $crate::chaos::buggify::buggify_internal($prob as f64, concat!(file!(), ":", line!()))
77 };
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::sim::rng::{reset_sim_rng, set_sim_seed};
84
85 #[test]
86 fn test_disabled_by_default() {
87 buggify_reset();
88 for _ in 0..10 {
89 assert!(!buggify_internal(1.0, "test"));
90 }
91 }
92
93 #[test]
94 fn test_activation_consistency() {
95 set_sim_seed(12345);
96 buggify_init(0.5, 1.0);
97
98 let location = "test_location";
99 let first = buggify_internal(1.0, location);
100 let second = buggify_internal(1.0, location);
101
102 assert_eq!(first, second);
104 buggify_reset();
105 }
106
107 #[test]
108 fn test_deterministic() {
109 const SEED: u64 = 54321;
110 let mut results1 = Vec::new();
111 let mut results2 = Vec::new();
112
113 for run in 0..2 {
114 set_sim_seed(SEED);
115 buggify_init(0.5, 0.5);
116
117 let results = if run == 0 {
118 &mut results1
119 } else {
120 &mut results2
121 };
122
123 for i in 0..5 {
124 let location = format!("loc_{}", i);
125 results.push(buggify_internal(0.5, Box::leak(location.into_boxed_str())));
126 }
127
128 buggify_reset();
129 reset_sim_rng();
130 }
131
132 assert_eq!(results1, results2);
133 }
134}