diff --git a/src/lib.rs b/src/lib.rs index f5e13c36..e8b8d671 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -404,3 +404,10 @@ pub fn getrandom_uninit(dest: &mut [MaybeUninit]) -> Result<&mut [u8], Error // since it returned `Ok`. Ok(unsafe { slice_assume_init_mut(dest) }) } + +// Don't run normal unit tests when testing custom getrandom +#[cfg(all( + test, + not(all(target_family = "wasm", target_os = "unknown", feature = "custom")) +))] +pub(crate) mod tests; diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 00000000..6664e280 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,117 @@ +//! Common tests and testing utilities +extern crate std; + +use crate::Error; +use std::{mem::MaybeUninit, sync::mpsc, thread, vec, vec::Vec}; + +#[cfg(feature = "test-in-browser")] +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +fn num_diff_bits(s1: &[u8], s2: &[u8]) -> usize { + assert_eq!(s1.len(), s2.len()); + s1.iter() + .zip(s2.iter()) + .map(|(a, b)| (a ^ b).count_ones() as usize) + .sum() +} + +// A function which fills a buffer with random bytes. +type FillFn = fn(&mut [B]) -> Result<(), Error>; + +// Helper trait for testing different `FillFn`s. +pub(crate) trait Byte: Sized + 'static { + fn make_vec(len: usize, fill: FillFn) -> Vec; +} +impl Byte for u8 { + fn make_vec(len: usize, fill: FillFn) -> Vec { + let mut v = vec![0; len]; + fill(&mut v).unwrap(); + v + } +} +impl Byte for MaybeUninit { + fn make_vec(len: usize, fill: FillFn>) -> Vec { + // Using Vec::spare_capacity_mut more consistently gives us truly + // uninitialized memory regardless of optimization level. + let mut v = Vec::with_capacity(len); + fill(v.spare_capacity_mut()).unwrap(); + unsafe { v.set_len(len) } + v + } +} + +// For calls of size `len`, count the number of bits which differ between calls +// and check that between 3 and 5 bits per byte differ. Probability of failure: +// ~ 10^(-30) = 2 * CDF[BinomialDistribution[8*256, 0.5], 3*256] +pub(crate) fn check_bits(len: usize, fill: FillFn) { + let mut num_bytes = 0; + let mut diff_bits = 0; + while num_bytes < 256 { + let v1 = B::make_vec(len, fill); + let v2 = B::make_vec(len, fill); + + num_bytes += len; + diff_bits += num_diff_bits(&v1, &v2); + } + + // When the custom feature is enabled, don't check RNG quality. + assert!(diff_bits > 3 * num_bytes); + assert!(diff_bits < 5 * num_bytes); +} + +pub(crate) fn check_multithreading(fill: FillFn) { + let mut txs = vec![]; + for _ in 0..20 { + let (tx, rx) = mpsc::channel(); + txs.push(tx); + + thread::spawn(move || { + // wait until all the tasks are ready to go. + rx.recv().unwrap(); + for _ in 0..100 { + check_bits(1000, fill); + thread::yield_now(); + } + }); + } + + // start all the tasks + for tx in txs.iter() { + tx.send(()).unwrap(); + } +} + +macro_rules! define_tests { + ($fill:expr) => { + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + #[test] + fn fill_zero() { + $fill(&mut []).unwrap(); + } + #[test] + fn fill_small() { + for len in 1..=64 { + crate::tests::check_bits(len, $fill); + } + } + #[test] + fn fill_large() { + crate::tests::check_bits(1_000, $fill); + } + #[test] + fn fill_huge() { + crate::tests::check_bits(1_000_000, $fill); + } + // On WASM, the thread API always fails/panics. + #[test] + #[cfg_attr(target_family = "wasm", ignore)] + fn multithreading() { + crate::tests::check_multithreading($fill) + } + }; +} +pub(crate) use define_tests; + +define_tests!(crate::getrandom); diff --git a/tests/common/mod.rs b/tests/common/mod.rs deleted file mode 100644 index 666f7f57..00000000 --- a/tests/common/mod.rs +++ /dev/null @@ -1,100 +0,0 @@ -use super::getrandom_impl; - -#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] -use wasm_bindgen_test::wasm_bindgen_test as test; - -#[cfg(feature = "test-in-browser")] -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -fn test_zero() { - // Test that APIs are happy with zero-length requests - getrandom_impl(&mut [0u8; 0]).unwrap(); -} - -// Return the number of bits in which s1 and s2 differ -#[cfg(not(feature = "custom"))] -fn num_diff_bits(s1: &[u8], s2: &[u8]) -> usize { - assert_eq!(s1.len(), s2.len()); - s1.iter() - .zip(s2.iter()) - .map(|(a, b)| (a ^ b).count_ones() as usize) - .sum() -} - -// Tests the quality of calling getrandom on two large buffers -#[test] -#[cfg(not(feature = "custom"))] -fn test_diff() { - let mut v1 = [0u8; 1000]; - getrandom_impl(&mut v1).unwrap(); - - let mut v2 = [0u8; 1000]; - getrandom_impl(&mut v2).unwrap(); - - // Between 3.5 and 4.5 bits per byte should differ. Probability of failure: - // ~ 2^(-94) = 2 * CDF[BinomialDistribution[8000, 0.5], 3500] - let d = num_diff_bits(&v1, &v2); - assert!(d > 3500); - assert!(d < 4500); -} - -// Tests the quality of calling getrandom repeatedly on small buffers -#[test] -#[cfg(not(feature = "custom"))] -fn test_small() { - // For each buffer size, get at least 256 bytes and check that between - // 3 and 5 bits per byte differ. Probability of failure: - // ~ 2^(-91) = 64 * 2 * CDF[BinomialDistribution[8*256, 0.5], 3*256] - for size in 1..=64 { - let mut num_bytes = 0; - let mut diff_bits = 0; - while num_bytes < 256 { - let mut s1 = vec![0u8; size]; - getrandom_impl(&mut s1).unwrap(); - let mut s2 = vec![0u8; size]; - getrandom_impl(&mut s2).unwrap(); - - num_bytes += size; - diff_bits += num_diff_bits(&s1, &s2); - } - assert!(diff_bits > 3 * num_bytes); - assert!(diff_bits < 5 * num_bytes); - } -} - -#[test] -fn test_huge() { - let mut huge = [0u8; 100_000]; - getrandom_impl(&mut huge).unwrap(); -} - -// On WASM, the thread API always fails/panics -#[cfg(not(target_arch = "wasm32"))] -#[test] -fn test_multithreading() { - extern crate std; - use std::{sync::mpsc::channel, thread, vec}; - - let mut txs = vec![]; - for _ in 0..20 { - let (tx, rx) = channel(); - txs.push(tx); - - thread::spawn(move || { - // wait until all the tasks are ready to go. - rx.recv().unwrap(); - let mut v = [0u8; 1000]; - - for _ in 0..100 { - getrandom_impl(&mut v).unwrap(); - thread::yield_now(); - } - }); - } - - // start all the tasks - for tx in txs.iter() { - tx.send(()).unwrap(); - } -} diff --git a/tests/normal.rs b/tests/normal.rs deleted file mode 100644 index 5fff13b3..00000000 --- a/tests/normal.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Don't test on custom wasm32-unknown-unknown -#![cfg(not(all( - target_arch = "wasm32", - target_os = "unknown", - feature = "custom", - not(feature = "js") -)))] - -// Use the normal getrandom implementation on this architecture. -use getrandom::getrandom as getrandom_impl; -mod common; diff --git a/tests/rdrand.rs b/tests/rdrand.rs deleted file mode 100644 index a355c31e..00000000 --- a/tests/rdrand.rs +++ /dev/null @@ -1,22 +0,0 @@ -// We only test the RDRAND-based RNG source on supported architectures. -#![cfg(any(target_arch = "x86_64", target_arch = "x86"))] - -// rdrand.rs expects to be part of the getrandom main crate, so we need these -// additional imports to get rdrand.rs to compile. -use getrandom::Error; -#[macro_use] -extern crate cfg_if; -#[path = "../src/lazy.rs"] -mod lazy; -#[path = "../src/rdrand.rs"] -mod rdrand; -#[path = "../src/util.rs"] -mod util; - -// The rdrand implementation has the signature of getrandom_uninit(), but our -// tests expect getrandom_impl() to have the signature of getrandom(). -fn getrandom_impl(dest: &mut [u8]) -> Result<(), Error> { - rdrand::getrandom_inner(unsafe { util::slice_as_uninit_mut(dest) })?; - Ok(()) -} -mod common;