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}