Skip to content

Commit 180358e

Browse files
committed
Cleanup Testing code
This stops using weird include hacks to reuse code. Instead, we move the code to a nomral `tests.rs` file and use helper functions to avoid code duplication. This also simplifies our testing of the custom RNGs. Before, we had to disable the "normal" tests to test the custom RNG. Now, the custom RNG is "good enough" to simply pass the tests. I also added direct testing for the `uninit` methods and verified that ``` RUSTFLAGS="-Zsanitizer=memory" cargo +nightly test -Zbuild-std --target=x86_64-unknown-linux-gnu ``` fails (but passes when #463 is added), so we are actually testing the right stuff here. So this can replace #462 Signed-off-by: Joe Richey <[email protected]>
1 parent 79c29da commit 180358e

File tree

7 files changed

+202
-187
lines changed

7 files changed

+202
-187
lines changed

src/lib.rs

+6
Original file line numberDiff line numberDiff line change
@@ -407,3 +407,9 @@ pub fn getrandom_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<&mut [u8], Error
407407
// since it returned `Ok`.
408408
Ok(unsafe { slice_assume_init_mut(dest) })
409409
}
410+
411+
#[cfg(test)]
412+
pub(crate) mod tests;
413+
// TODO: Test via the public API once rdrand is exposed via the public API.
414+
#[cfg(all(test, any(target_arch = "x86_64", target_arch = "x86")))]
415+
mod rdrand;

src/rdrand.rs

+20
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,23 @@ unsafe fn rdrand_exact(dest: &mut [MaybeUninit<u8>]) -> Option<()> {
121121
}
122122
Some(())
123123
}
124+
125+
#[cfg(test)]
126+
mod tests {
127+
use crate::{tests::*, *};
128+
129+
fn rdrand_fill_uninit(dest: &mut [MaybeUninit<u8>]) -> Result<&mut [u8], Error> {
130+
super::getrandom_inner(dest)?;
131+
Ok(unsafe { slice_assume_init_mut(dest) })
132+
}
133+
134+
#[test]
135+
fn fill_zero() {
136+
super::getrandom_inner(&mut []).unwrap();
137+
}
138+
139+
#[test]
140+
fn fill_small() {
141+
check_small(MaybeUninit::uninit(), rdrand_fill_uninit);
142+
}
143+
}

src/tests.rs

+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
//! Common tests and testing utilities
2+
use super::*;
3+
4+
extern crate std;
5+
use std::{
6+
num::NonZeroU32,
7+
sync::atomic::{AtomicU32, Ordering},
8+
vec,
9+
};
10+
11+
#[cfg(all(
12+
any(target_arch = "wasm32", target_arch = "wasm64"),
13+
target_os = "unknown",
14+
))]
15+
pub(crate) use wasm_bindgen_test::wasm_bindgen_test as test;
16+
17+
#[cfg(feature = "test-in-browser")]
18+
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
19+
20+
fn num_diff_bits(s1: &[u8], s2: &[u8]) -> usize {
21+
assert_eq!(s1.len(), s2.len());
22+
s1.iter()
23+
.zip(s2.iter())
24+
.map(|(a, b)| (a ^ b).count_ones() as usize)
25+
.sum()
26+
}
27+
28+
// For calls of size `len`, count the number of bits which differ between calls
29+
// and check that between 3 and 5 bits per byte differ. Probability of failure:
30+
// ~ 10^(-30) = 2 * CDF[BinomialDistribution[8*256, 0.5], 3*256]
31+
fn check_bits<T: Copy>(len: usize, v: T, f: fn(&mut [T]) -> Result<&mut [u8], Error>) {
32+
let mut num_bytes = 0;
33+
let mut diff_bits = 0;
34+
while num_bytes < 256 {
35+
let mut v1 = vec![v; len];
36+
let s1 = f(&mut v1).unwrap();
37+
let mut v2 = vec![v; len];
38+
let s2 = f(&mut v2).unwrap();
39+
40+
num_bytes += len;
41+
diff_bits += num_diff_bits(s1, s2);
42+
}
43+
44+
assert!(diff_bits > 3 * num_bytes);
45+
assert!(diff_bits < 5 * num_bytes);
46+
}
47+
48+
pub(crate) fn check_small<T: Copy>(v: T, f: fn(&mut [T]) -> Result<&mut [u8], Error>) {
49+
for len in 1..=64 {
50+
check_bits(len, v, f)
51+
}
52+
}
53+
54+
fn wrapped_fill(dest: &mut [u8]) -> Result<&mut [u8], Error> {
55+
getrandom(dest).map(|()| dest)
56+
}
57+
58+
#[test]
59+
fn fill_zero() {
60+
getrandom(&mut []).unwrap();
61+
}
62+
#[test]
63+
fn fill_zero_uninit() {
64+
getrandom_uninit(&mut []).unwrap();
65+
}
66+
#[test]
67+
fn fill_small() {
68+
check_small(0, wrapped_fill);
69+
}
70+
#[test]
71+
fn fill_small_uninit() {
72+
check_small(MaybeUninit::uninit(), getrandom_uninit);
73+
}
74+
#[test]
75+
fn fill_large() {
76+
check_bits(1_000, 0, wrapped_fill);
77+
}
78+
#[test]
79+
fn fill_large_uninit() {
80+
check_bits(1_000, MaybeUninit::uninit(), getrandom_uninit);
81+
}
82+
#[test]
83+
fn fill_huge() {
84+
check_bits(1_000_000, 0, wrapped_fill);
85+
}
86+
#[test]
87+
fn fill_huge_uninit() {
88+
check_bits(1_000_000, MaybeUninit::uninit(), getrandom_uninit);
89+
}
90+
91+
// On WASM, the thread API always fails/panics
92+
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
93+
#[test]
94+
fn multithreading() {
95+
use std::{sync::mpsc, thread};
96+
97+
let mut txs = vec![];
98+
for _ in 0..20 {
99+
let (tx, rx) = mpsc::channel();
100+
txs.push(tx);
101+
102+
thread::spawn(move || {
103+
// wait until all the tasks are ready to go.
104+
rx.recv().unwrap();
105+
let mut v = [0u8; 1000];
106+
107+
for _ in 0..100 {
108+
getrandom(&mut v).unwrap();
109+
thread::yield_now();
110+
}
111+
});
112+
}
113+
114+
// start all the tasks
115+
for tx in txs.iter() {
116+
tx.send(()).unwrap();
117+
}
118+
}
119+
120+
// A simple Linear Congruential Generator (LCG) that passes our RNG tests.
121+
const A: u32 = 134775813;
122+
const C: u32 = 1;
123+
static X: AtomicU32 = AtomicU32::new(0);
124+
125+
fn gen_lcg() -> u32 {
126+
X.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |x| {
127+
Some(x.wrapping_mul(A).wrapping_add(C))
128+
})
129+
.unwrap()
130+
}
131+
132+
// Custom error returned when we get an input of length 1337
133+
const CUSTOM_CODE: u32 = Error::CUSTOM_START + 1337;
134+
135+
#[allow(dead_code)]
136+
fn insecure_rng(buf: &mut [u8]) -> Result<(), Error> {
137+
// We guarantee no implementation is called with an empty buffer.
138+
assert!(!buf.is_empty());
139+
if buf.len() == 1337 {
140+
return Err(NonZeroU32::new(CUSTOM_CODE).unwrap().into());
141+
}
142+
143+
let mut chunks = buf.chunks_exact_mut(4);
144+
for chunk in chunks.by_ref() {
145+
chunk.copy_from_slice(&gen_lcg().to_le_bytes());
146+
}
147+
let tail = chunks.into_remainder();
148+
tail.copy_from_slice(&gen_lcg().to_le_bytes()[..tail.len()]);
149+
Ok(())
150+
}
151+
152+
// Test registering a custom implementation, even on supported platforms.
153+
#[cfg(feature = "custom")]
154+
register_custom_getrandom!(insecure_rng);
155+
156+
// On a supported platform, make sure the custom implementation isn't used.
157+
#[cfg(all(feature = "custom", target_os = "linux"))]
158+
#[test]
159+
fn custom_not_used() {
160+
getrandom(&mut [0; 1337]).unwrap();
161+
}
162+
163+
// On an unsupported platform, make sure the custom implementation is used.
164+
#[cfg(all(feature = "custom", target_arch = "wasm32", target_os = "unknown"))]
165+
#[test]
166+
fn custom_used() {
167+
let err = getrandom(&mut [0; 1337]).unwrap_err();
168+
assert_eq!(err.code().get(), CUSTOM_CODE);
169+
170+
// After resetting the LCG seed, the next numbers generated will be:
171+
// 0, C, (A+1)*C
172+
let mut buf = [0u8; 12];
173+
X.store(0, Ordering::Relaxed);
174+
getrandom(&mut buf).unwrap();
175+
assert_eq!(buf, [0, 0, 0, 0, 1, 0, 0, 0, 6, 132, 8, 8]);
176+
}

tests/common/mod.rs

-100
This file was deleted.

tests/custom.rs

-54
This file was deleted.

tests/normal.rs

-11
This file was deleted.

0 commit comments

Comments
 (0)