diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 26f0760c..77fb6fc5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,8 +38,8 @@ jobs: toolchain: ${{ matrix.toolchain }} - uses: Swatinem/rust-cache@v2 - run: cargo test - # Make sure enabling the std and custom features don't break anything - - run: cargo test --features=std,custom + # Ensure enabling features works, and run feature-specific tests. + - run: cargo test --features=std,custom,rdrand - run: cargo test --features=linux_disable_fallback - if: ${{ matrix.toolchain == 'nightly' }} run: cargo test --benches @@ -258,8 +258,8 @@ jobs: - name: Test (Safari) if: runner.os == 'macOS' run: wasm-pack test --headless --safari --features=js,test-in-browser - - name: Test (custom getrandom) - run: wasm-pack test --node --features=custom + - name: Test (custom getrandom, no unit tests) + run: wasm-pack test --node --features=custom --test=custom - name: Test (JS overrides custom) run: wasm-pack test --node --features=custom,js diff --git a/src/lib.rs b/src/lib.rs index f5e13c36..f23d4edc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -224,6 +224,11 @@ mod util; mod custom; #[cfg(feature = "std")] mod error_impls; +#[cfg(all( + any(target_env = "sgx", feature = "rdrand"), + any(target_arch = "x86_64", target_arch = "x86"), +))] +mod rdrand; pub use crate::error::Error; @@ -330,10 +335,10 @@ cfg_if! { } else if #[cfg(windows)] { #[path = "windows.rs"] mod imp; } else if #[cfg(all(target_arch = "x86_64", target_env = "sgx"))] { - #[path = "rdrand.rs"] mod imp; + use rdrand as imp; } else if #[cfg(all(feature = "rdrand", any(target_arch = "x86_64", target_arch = "x86")))] { - #[path = "rdrand.rs"] mod imp; + use rdrand as imp; } else if #[cfg(all(feature = "js", any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))] { @@ -404,3 +409,6 @@ pub fn getrandom_uninit(dest: &mut [MaybeUninit]) -> Result<&mut [u8], Error // since it returned `Ok`. Ok(unsafe { slice_assume_init_mut(dest) }) } + +#[cfg(test)] +pub(crate) mod tests; diff --git a/src/rdrand.rs b/src/rdrand.rs index f4c593bd..d8019d7c 100644 --- a/src/rdrand.rs +++ b/src/rdrand.rs @@ -93,6 +93,7 @@ fn is_rdrand_good() -> bool { unsafe { self_test() } } +#[allow(dead_code)] pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { static RDRAND_GOOD: LazyBool = LazyBool::new(); if !RDRAND_GOOD.unsync_init(is_rdrand_good) { @@ -121,3 +122,8 @@ unsafe fn rdrand_exact(dest: &mut [MaybeUninit]) -> Option<()> { } Some(()) } + +#[cfg(test)] +mod tests { + crate::tests::define_tests!(super::getrandom_inner as fn(&mut _) -> _); +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 00000000..266e2e8f --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,126 @@ +//! 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() +} + +pub(crate) trait FillFn: Copy + Send + 'static { + fn make_vec(self, len: usize) -> Vec; +} +impl FillFn for fn(&mut [u8]) -> Result<(), Error> { + fn make_vec(self, len: usize) -> Vec { + let mut v = vec![0; len]; + self(&mut v).unwrap(); + v + } +} +impl FillFn for fn(&mut [MaybeUninit]) -> Result<(), Error> { + fn make_vec(self, len: usize) -> Vec { + let mut v = Vec::with_capacity(len); + self(v.spare_capacity_mut()).unwrap(); + unsafe { v.set_len(len) }; + v + } +} +impl FillFn for fn(&mut [MaybeUninit]) -> Result<&mut [u8], Error> { + fn make_vec(self, len: usize) -> Vec { + let mut v = Vec::with_capacity(len); + let ret = self(v.spare_capacity_mut()).unwrap(); + assert_eq!(ret.len(), len); + assert_eq!(ret.as_ptr(), v.as_ptr()); + 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: impl FillFn) { + let mut num_bytes = 0; + let mut diff_bits = 0; + while num_bytes < 256 { + let v1 = fill.make_vec(len); + let v2 = fill.make_vec(len); + + 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: impl 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() { + crate::tests::FillFn::make_vec($fill, 0); + } + #[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; + +mod init { + super::define_tests!(crate::getrandom as fn(&mut _) -> _); +} +mod uninit { + super::define_tests!(crate::getrandom_uninit as fn(&mut _) -> Result<&mut _, _>); +} diff --git a/src/use_file.rs b/src/use_file.rs index 4fdbe3d7..deb4147b 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -174,3 +174,8 @@ impl Drop for DropGuard { self.0() } } + +#[cfg(test)] +mod tests { + crate::tests::define_tests!(super::getrandom_inner as fn(&mut _) -> _); +} 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;