antithesis_sdk/
random.rs

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