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}