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}