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/// # Example
51///
52/// ```
53/// use antithesis_sdk::random;
54///
55/// let choices: Vec<&str> = vec!["abc", "def", "xyz", "qrs"];
56/// if let Some(s) = random::random_choice(choices.as_slice()) {
57///     println!("Choice: '{s}'");
58/// };
59/// ```
60pub fn random_choice<T>(slice: &[T]) -> Option<&T> {
61    match slice {
62        [] => None,
63        [x] => Some(x),
64        _ => {
65            let ceiling = (u64::MAX / slice.len() as u64) * slice.len() as u64;
66
67            let mut random = get_random();
68            while random >= ceiling {
69                random = get_random();
70            }
71
72            let idx: usize = (random as usize) % slice.len();
73            Some(&slice[idx])
74        }
75    }
76}
77
78/// A random number generator that uses Antithesis's random number generation.
79///
80/// This implements the `RngCore` trait from the `rand` crate, allowing it to be used
81/// with any code that expects a random number generator from that ecosystem.
82///
83/// # Example
84///
85/// ```
86/// use antithesis_sdk::random::AntithesisRng;
87/// use rand::{Rng, RngCore};
88///
89/// let mut rng = AntithesisRng;
90/// let random_u32: u32 = rng.gen();
91/// let random_u64: u64 = rng.gen();
92/// let random_char: char = rng.gen();
93///
94/// let mut bytes = [0u8; 16];
95/// rng.fill_bytes(&mut bytes);
96/// ```
97pub struct AntithesisRng;
98
99fn fill_bytes_impl(dest: &mut [u8]) {
100    // Split the destination buffer into chunks of 8 bytes each
101    // (since we'll fill each chunk with a u64/8 bytes of random data)
102    let mut chunks = dest.chunks_exact_mut(8);
103
104    // Fill each complete 8-byte chunk with random bytes
105    for chunk in chunks.by_ref() {
106        // Generate 8 random bytes from a u64 in native endian order
107        let random_bytes = get_random().to_ne_bytes();
108        // Copy those random bytes into this chunk
109        chunk.copy_from_slice(&random_bytes);
110    }
111
112    // Get any remaining bytes that didn't fit in a complete 8-byte chunk
113    let remainder = chunks.into_remainder();
114
115    if !remainder.is_empty() {
116        // Generate 8 more random bytes
117        let random_bytes = get_random().to_ne_bytes();
118        // Copy just enough random bytes to fill the remainder
119        remainder.copy_from_slice(&random_bytes[..remainder.len()]);
120    }
121}
122
123// rand-core 0.6 is the underlying core crate for rand 0.8
124#[cfg(feature = "rand_core_v0_6")]
125impl rand_core_v0_6::RngCore for AntithesisRng {
126    fn next_u32(&mut self) -> u32 {
127        get_random() as u32
128    }
129
130    fn next_u64(&mut self) -> u64 {
131        get_random()
132    }
133
134    fn fill_bytes(&mut self, dest: &mut [u8]) {
135        fill_bytes_impl(dest)
136    }
137
138    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_v0_6::Error> {
139        self.fill_bytes(dest);
140        Ok(())
141    }
142}
143
144#[cfg(feature = "rand_core_v0_9")]
145impl rand_core_v0_9::RngCore for AntithesisRng {
146    fn next_u32(&mut self) -> u32 {
147        get_random() as u32
148    }
149
150    fn next_u64(&mut self) -> u64 {
151        get_random()
152    }
153
154    fn fill_bytes(&mut self, dest: &mut [u8]) {
155        fill_bytes_impl(dest)
156    }
157}
158
159#[cfg(feature = "rand_core_v0_10")]
160impl rand_core_v0_10::TryRng for AntithesisRng {
161    type Error = std::convert::Infallible;
162
163    fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
164        Ok(get_random() as u32)
165    }
166
167    fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
168        Ok(get_random())
169    }
170
171    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
172        Ok(fill_bytes_impl(dest))
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179    use std::collections::{HashMap, HashSet};
180
181    #[test]
182    fn random_choice_no_choices() {
183        let array = [""; 0];
184        assert_eq!(0, array.len());
185        assert_eq!(None, random_choice(&array))
186    }
187
188    #[test]
189    fn random_choice_one_choice() {
190        let array = ["ABc"; 1];
191        assert_eq!(1, array.len());
192        assert_eq!(Some(&"ABc"), random_choice(&array))
193    }
194
195    #[test]
196    fn random_choice_few_choices() {
197        // For each map key, the value is the count of the number of
198        // random_choice responses received matching that key
199        let mut counted_items: HashMap<&str, i64> = HashMap::new();
200        counted_items.insert("a", 0);
201        counted_items.insert("b", 0);
202        counted_items.insert("c", 0);
203
204        let all_keys: Vec<&str> = counted_items.keys().cloned().collect();
205        assert_eq!(counted_items.len(), all_keys.len());
206        for _i in 0..30 {
207            let rc = random_choice(all_keys.as_slice());
208            if let Some(choice) = rc {
209                if let Some(x) = counted_items.get_mut(choice) {
210                    *x += 1;
211                }
212            }
213        }
214        for (key, val) in counted_items.iter() {
215            assert_ne!(*val, 0, "Did not produce the choice: {}", key);
216        }
217    }
218
219    #[test]
220    fn get_random_100k() {
221        let mut random_numbers: HashSet<u64> = HashSet::new();
222        for _i in 0..100000 {
223            let rn = get_random();
224            assert!(!random_numbers.contains(&rn));
225            random_numbers.insert(rn);
226        }
227    }
228}