Skip to main content

antithesis_sdk/
random.rs

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