antithesis_sdk/
random.rs

1use rand::{Error, RngCore};
2use crate::internal;
3
4/// Returns a u64 value chosen by Antithesis.
5///
6/// You should use this value immediately rather than using it
7/// later. If you delay, then it is possible for the simulation
8/// to branch in between receiving the random data and using it.
9/// These branches will have the same random value, which
10/// defeats the purpose of branching.
11///
12/// Similarly, do not use the value to seed a pseudo-random
13/// number generator. The PRNG will produce a deterministic
14/// sequence of pseudo-random values based on the seed, so if the
15/// simulation branches, the PRNG will use the same sequence of
16/// values in all branches.
17///
18/// # Example
19///
20/// ```
21/// use antithesis_sdk::random;
22///
23/// let value = random::get_random();
24/// println!("Random value(u64): {value}");
25/// ```
26pub fn get_random() -> u64 {
27    internal::dispatch_random()
28}
29
30/// Returns a randomly chosen item from a list of options.
31///
32/// You should use this value immediately rather than using it
33/// later. If you delay, then it is possible for the simulation
34/// to branch in between receiving the random data and using it.
35/// These branches will have the same random value, which
36/// defeats the purpose of branching.
37///
38/// Similarly, do not use the value to seed a pseudo-random
39/// number generator. The PRNG will produce a deterministic
40/// sequence of pseudo-random values based on the seed, so if the
41/// simulation branches, the PRNG will use the same sequence of
42/// values in all branches.
43///
44/// This function is not purely for convenience. Signaling to
45/// the Antithesis platform that you intend to use a random value
46/// in a structured way enables it to provide more interesting
47/// choices over time.
48///
49/// # Example
50///
51/// ```
52/// use antithesis_sdk::random;
53///
54/// let choices: Vec<&str> = vec!["abc", "def", "xyz", "qrs"];
55/// if let Some(s) = random::random_choice(choices.as_slice()) {
56///     println!("Choice: '{s}'");
57/// };
58/// ```
59pub fn random_choice<T>(slice: &[T]) -> Option<&T> {
60    match slice {
61        [] => None,
62        [x] => Some(x),
63        _ => {
64            let idx: usize = (get_random() as usize) % slice.len();
65            Some(&slice[idx])
66        }
67    }
68}
69
70/// A random number generator that uses Antithesis's random number generation.
71///
72/// This implements the `RngCore` trait from the `rand` crate, allowing it to be used
73/// with any code that expects a random number generator from that ecosystem.
74///
75/// # Example
76///
77/// ```
78/// use antithesis_sdk::random::AntithesisRng;
79/// use rand::{Rng, RngCore};
80///
81/// let mut rng = AntithesisRng;
82/// let random_u32: u32 = rng.gen();
83/// let random_u64: u64 = rng.gen();
84/// let random_char: char = rng.gen();
85///
86/// let mut bytes = [0u8; 16];
87/// rng.fill_bytes(&mut bytes);
88/// ```
89pub struct AntithesisRng;
90
91impl RngCore for AntithesisRng {
92    fn next_u32(&mut self) -> u32 {
93        get_random() as u32
94    }
95
96    fn next_u64(&mut self) -> u64 {
97        get_random()
98    }
99
100    fn fill_bytes(&mut self, dest: &mut [u8]) {
101        // Split the destination buffer into chunks of 8 bytes each
102        // (since we'll fill each chunk with a u64/8 bytes of random data)
103        let mut chunks = dest.chunks_exact_mut(8);
104
105        // Fill each complete 8-byte chunk with random bytes
106        for chunk in chunks.by_ref() {
107            // Generate 8 random bytes from a u64 in native endian order
108            let random_bytes = self.next_u64().to_ne_bytes();
109            // Copy those random bytes into this chunk
110            chunk.copy_from_slice(&random_bytes);
111        }
112
113        // Get any remaining bytes that didn't fit in a complete 8-byte chunk
114        let remainder = chunks.into_remainder();
115
116        if !remainder.is_empty() {
117            // Generate 8 more random bytes
118            let random_bytes = self.next_u64().to_ne_bytes();
119            // Copy just enough random bytes to fill the remainder
120            remainder.copy_from_slice(&random_bytes[..remainder.len()]);
121        }
122    }
123
124    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
125        self.fill_bytes(dest);
126        Ok(())
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use std::collections::{HashMap, HashSet};
134    use rand::Rng;
135    use rand::seq::SliceRandom;
136
137    #[test]
138    fn random_choice_no_choices() {
139        let array = [""; 0];
140        assert_eq!(0, array.len());
141        assert_eq!(None, random_choice(&array))
142    }
143
144    #[test]
145    fn random_choice_one_choice() {
146        let array = ["ABc"; 1];
147        assert_eq!(1, array.len());
148        assert_eq!(Some(&"ABc"), random_choice(&array))
149    }
150
151    #[test]
152    fn random_choice_few_choices() {
153        // For each map key, the value is the count of the number of
154        // random_choice responses received matching that key
155        let mut counted_items: HashMap<&str, i64> = HashMap::new();
156        counted_items.insert("a", 0);
157        counted_items.insert("b", 0);
158        counted_items.insert("c", 0);
159
160        let all_keys: Vec<&str> = counted_items.keys().cloned().collect();
161        assert_eq!(counted_items.len(), all_keys.len());
162        for _i in 0..15 {
163            let rc = random_choice(all_keys.as_slice());
164            if let Some(choice) = rc {
165                if let Some(x) = counted_items.get_mut(choice) {
166                    *x += 1;
167                }
168            }
169        }
170        for (key, val) in counted_items.iter() {
171            assert_ne!(*val, 0, "Did not produce the choice: {}", key);
172        }
173    }
174
175    #[test]
176    fn get_random_100k() {
177        let mut random_numbers: HashSet<u64> = HashSet::new();
178        for _i in 0..100000 {
179            let rn = get_random();
180            assert!(!random_numbers.contains(&rn));
181            random_numbers.insert(rn);
182        }
183    }
184
185    #[test]
186    fn rng_no_choices() {
187        let mut rng = AntithesisRng;
188        let array = [""; 0];
189        assert_eq!(0, array.len());
190        assert_eq!(None, array.choose(&mut rng));
191    }
192
193    #[test]
194    fn rng_one_choice() {
195        let mut rng = AntithesisRng;
196        let array = ["ABc"; 1];
197        assert_eq!(1, array.len());
198        assert_eq!(Some(&"ABc"), array.choose(&mut rng));
199    }
200
201    #[test]
202    fn rng_few_choices() {
203        let mut rng = AntithesisRng;
204        // For each map key, the value is the count of the number of
205        // random_choice responses received matching that key
206        let mut counted_items: HashMap<&str, i64> = HashMap::new();
207        counted_items.insert("a", 0);
208        counted_items.insert("b", 0);
209        counted_items.insert("c", 0);
210
211        let all_keys: Vec<&str> = counted_items.keys().cloned().collect();
212        assert_eq!(counted_items.len(), all_keys.len());
213        for _i in 0..15 {
214            let rc = all_keys.choose(&mut rng);
215            if let Some(choice) = rc {
216                if let Some(x) = counted_items.get_mut(choice) {
217                    *x += 1;
218                }
219            }
220        }
221        for (key, val) in counted_items.iter() {
222            assert_ne!(*val, 0, "Did not produce the choice: {}", key);
223        }
224    }
225
226    #[test]
227    fn rng_100k() {
228        let mut rng = AntithesisRng;
229        let mut random_numbers: HashSet<u64> = HashSet::new();
230        for _i in 0..100000 {
231            let rn: u64 = rng.gen();
232            assert!(!random_numbers.contains(&rn));
233            random_numbers.insert(rn);
234        }
235    }
236}