From 788d6d5ea43457dbd19418c0468a67173694cb73 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Thu, 20 Jun 2024 04:36:18 -0700 Subject: [PATCH 1/4] Move common integration tests to unit tests Signed-off-by: Joe Richey --- tests/common/mod.rs => src/tests.rs | 0 tests/normal.rs | 11 ----------- tests/rdrand.rs | 22 ---------------------- 3 files changed, 33 deletions(-) rename tests/common/mod.rs => src/tests.rs (100%) delete mode 100644 tests/normal.rs delete mode 100644 tests/rdrand.rs diff --git a/tests/common/mod.rs b/src/tests.rs similarity index 100% rename from tests/common/mod.rs rename to src/tests.rs 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; From 71a2ab8c5ef6578b9e795c70f39ca3b4c074b4a5 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Thu, 20 Jun 2024 04:40:22 -0700 Subject: [PATCH 2/4] Define common tests via define_tests! macro Signed-off-by: Joe Richey --- .github/workflows/tests.yml | 4 +- src/lib.rs | 3 + src/tests.rs | 123 ++++++++++++++++++++++++------------ 3 files changed, 86 insertions(+), 44 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 26f0760c..5b58f5b5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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..ddd2d913 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -404,3 +404,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)] +mod tests; diff --git a/src/tests.rs b/src/tests.rs index 666f7f57..3f848da1 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,19 +1,12 @@ -use super::getrandom_impl; +use crate::Error; -#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] -use wasm_bindgen_test::wasm_bindgen_test as test; +extern crate std; +use std::{mem::MaybeUninit, sync::mpsc::channel, thread, vec, vec::Vec}; #[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() @@ -23,14 +16,9 @@ fn num_diff_bits(s1: &[u8], s2: &[u8]) -> usize { } // 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(); +pub(crate) fn check_diff_large(make_vec: fn(usize) -> Vec) { + let v1 = make_vec(1000); + let v2 = make_vec(1000); // Between 3.5 and 4.5 bits per byte should differ. Probability of failure: // ~ 2^(-94) = 2 * CDF[BinomialDistribution[8000, 0.5], 3500] @@ -40,9 +28,7 @@ fn test_diff() { } // Tests the quality of calling getrandom repeatedly on small buffers -#[test] -#[cfg(not(feature = "custom"))] -fn test_small() { +pub(crate) fn check_small(make_vec: fn(usize) -> Vec) { // 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] @@ -50,11 +36,8 @@ fn test_small() { 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(); - + let s1 = make_vec(size); + let s2 = make_vec(size); num_bytes += size; diff_bits += num_diff_bits(&s1, &s2); } @@ -63,19 +46,7 @@ fn test_small() { } } -#[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}; - +pub(crate) fn check_multithreading(make_vec: fn(usize) -> Vec) { let mut txs = vec![]; for _ in 0..20 { let (tx, rx) = channel(); @@ -84,10 +55,8 @@ fn test_multithreading() { 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(); + make_vec(1000); thread::yield_now(); } }); @@ -98,3 +67,73 @@ fn test_multithreading() { tx.send(()).unwrap(); } } + +// Helper trait for testing different kinds of functions. +// DUMMY generic parameter is needed to avoid conflicting implementations. +pub(crate) trait FillFn { + fn make_vec(self, len: usize) -> Vec; +} +impl Result<(), Error>> FillFn<0> for F { + fn make_vec(self, len: usize) -> Vec { + let mut v = vec![0; len]; + self(&mut v).unwrap(); + v + } +} +impl]) -> Result<(), Error>> FillFn<1> for F { + 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]) -> Result<&mut [u8], Error>> FillFn<2> for F { + 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 + } +} + +macro_rules! define_tests { + ($fill:path) => { + use crate::tests::FillFn; + #[cfg(all(target_family = "wasm", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + #[test] + fn zero() { + $fill.make_vec(0); + } + #[test] + fn diff_large() { + crate::tests::check_diff_large(|len| $fill.make_vec(len)); + } + #[test] + fn small() { + crate::tests::check_small(|len| $fill.make_vec(len)); + } + #[test] + fn huge() { + $fill.make_vec(100_000); + } + // On WASM, the thread API always fails/panics. + #[test] + #[cfg_attr(target_family = "wasm", ignore)] + fn multithreading() { + crate::tests::check_multithreading(|len| $fill.make_vec(len)); + } + }; +} +pub(crate) use define_tests; + +mod init { + super::define_tests!(crate::getrandom); +} +mod uninit { + super::define_tests!(crate::getrandom_uninit); +} From b028603b0b49b6070804b86f7ef6a0ad429368d7 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Thu, 20 Jun 2024 05:19:40 -0700 Subject: [PATCH 3/4] Test RDRAND Signed-off-by: Joe Richey --- .github/workflows/tests.yml | 4 ++-- src/lib.rs | 11 +++++++++-- src/rdrand.rs | 6 ++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5b58f5b5..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 diff --git a/src/lib.rs b/src/lib.rs index ddd2d913..55b4a591 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -224,6 +224,13 @@ mod util; mod custom; #[cfg(feature = "std")] mod error_impls; +// If the rdrand feature is enabled, always bring in the rdrand module, so +// that the RDRAND implementation can be tested. +#[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 +337,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"))] { diff --git a/src/rdrand.rs b/src/rdrand.rs index f4c593bd..285cb79f 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); +} From 13f85e18a998443592c386f353da924718173b4a Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Thu, 20 Jun 2024 05:22:47 -0700 Subject: [PATCH 4/4] Test fallback on Linux/Andorid Signed-off-by: Joe Richey --- src/use_file.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/use_file.rs b/src/use_file.rs index 4fdbe3d7..c5e81669 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -174,3 +174,9 @@ impl Drop for DropGuard { self.0() } } + +// We only need separate tests here when use_file is the fallback. +#[cfg(all(test, any(target_os = "android", target_os = "linux")))] +mod tests { + crate::tests::define_tests!(super::getrandom_inner); +}