// When testing functions, QuickCheck (QC) uses small values for integer (`u*`/`i*`) arguments // (~ `[-100, 100]`), but these values don't stress all the code paths in our intrinsics. Here we // create newtypes over the primitive integer types with the goal of having full control over the // random values that will be used to test our intrinsics. use std::boxed::Box; use std::fmt; use core::{f32, f64}; use quickcheck::{Arbitrary, Gen}; use int::LargeInt; use float::Float; // Generates values in the full range of the integer type macro_rules! arbitrary { ($TY:ident : $ty:ident) => { #[derive(Clone, Copy, PartialEq)] pub struct $TY(pub $ty); impl Arbitrary for $TY { fn arbitrary(g: &mut G) -> $TY where G: Gen { // NOTE Generate edge cases with a 10% chance let t = if g.gen_weighted_bool(10) { *g.choose(&[ $ty::min_value(), 0, $ty::max_value(), ]).unwrap() } else { g.gen() }; $TY(t) } fn shrink(&self) -> Box> { struct Shrinker { x: $ty, } impl Iterator for Shrinker { type Item = $TY; fn next(&mut self) -> Option<$TY> { self.x /= 2; if self.x == 0 { None } else { Some($TY(self.x)) } } } if self.0 == 0 { ::quickcheck::empty_shrinker() } else { Box::new(Shrinker { x: self.0 }) } } } impl fmt::Debug for $TY { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, f) } } } } arbitrary!(I32: i32); arbitrary!(U32: u32); // These integers are "too large". If we generate e.g. `u64` values in the full range then there's // only `1 / 2^32` chance of seeing a value smaller than `2^32` (i.e. whose higher "word" (32-bits) // is `0`)! But this is an important group of values to tests because we have special code paths for // them. Instead we'll generate e.g. `u64` integers this way: uniformly pick between (a) setting the // low word to 0 and generating a random high word, (b) vice versa: high word to 0 and random low // word or (c) generate both words randomly. This let's cover better the code paths in our // intrinsics. macro_rules! arbitrary_large { ($TY:ident : $ty:ident) => { #[derive(Clone, Copy, PartialEq)] pub struct $TY(pub $ty); impl Arbitrary for $TY { fn arbitrary(g: &mut G) -> $TY where G: Gen { // NOTE Generate edge cases with a 10% chance let t = if g.gen_weighted_bool(10) { *g.choose(&[ $ty::min_value(), 0, $ty::max_value(), ]).unwrap() } else { match g.gen_range(0, 3) { 0 => $ty::from_parts(g.gen(), g.gen()), 1 => $ty::from_parts(0, g.gen()), 2 => $ty::from_parts(g.gen(), 0), _ => unreachable!(), } }; $TY(t) } fn shrink(&self) -> Box> { struct Shrinker { x: $ty, } impl Iterator for Shrinker { type Item = $TY; fn next(&mut self) -> Option<$TY> { self.x /= 2; if self.x == 0 { None } else { Some($TY(self.x)) } } } if self.0 == 0 { ::quickcheck::empty_shrinker() } else { Box::new(Shrinker { x: self.0 }) } } } impl fmt::Debug for $TY { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, f) } } } } arbitrary_large!(I64: i64); arbitrary_large!(U64: u64); arbitrary_large!(I128: i128); arbitrary_large!(U128: u128); macro_rules! arbitrary_float { ($TY:ident : $ty:ident) => { #[derive(Clone, Copy)] pub struct $TY(pub $ty); impl Arbitrary for $TY { fn arbitrary(g: &mut G) -> $TY where G: Gen { let special = [ -0.0, 0.0, $ty::NAN, $ty::INFINITY, -$ty::INFINITY ]; if g.gen_weighted_bool(10) { // Random special case $TY(*g.choose(&special).unwrap()) } else if g.gen_weighted_bool(10) { // NaN variants let sign: bool = g.gen(); let exponent: <$ty as Float>::Int = g.gen(); let significand: <$ty as Float>::Int = 0; $TY($ty::from_parts(sign, exponent, significand)) } else if g.gen() { // Denormalized let sign: bool = g.gen(); let exponent: <$ty as Float>::Int = 0; let significand: <$ty as Float>::Int = g.gen(); $TY($ty::from_parts(sign, exponent, significand)) } else { // Random anything let sign: bool = g.gen(); let exponent: <$ty as Float>::Int = g.gen(); let significand: <$ty as Float>::Int = g.gen(); $TY($ty::from_parts(sign, exponent, significand)) } } fn shrink(&self) -> Box> { ::quickcheck::empty_shrinker() } } impl fmt::Debug for $TY { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, f) } } impl PartialEq for $TY { fn eq(&self, other: &$TY) -> bool { self.0.eq_repr(other.0) } } } } arbitrary_float!(F32: f32); arbitrary_float!(F64: f64); // Convenience macro to test intrinsics against their reference implementations. // // Each intrinsic is tested against both the `gcc_s` library as well as // `compiler-rt`. These libraries are defined in the `gcc_s` crate as well as // the `compiler-rt` crate in this repository. Both load a dynamic library and // lookup symbols through that dynamic library to ensure that we're using the // right intrinsic. // // This macro hopefully allows you to define a bare minimum of how to test an // intrinsic without worrying about these implementation details. A sample // invocation looks like: // // // check! { // // First argument is the function we're testing (either from this lib // // or a dynamically loaded one. Further arguments are all generated by // // quickcheck. // fn __my_intrinsic(f: extern fn(i32) -> i32, // a: I32) // -> Option<(i32, i64)> { // // // Discard tests by returning Some // if a.0 == 0 { // return None // } // // // Return the result via `Some` if the test can run // let mut other_result = 0; // let result = f(a.0, &mut other_result); // Some((result, other_result)) // } // } // // If anything returns `None` then the test is discarded, otherwise the two // results are compared for equality and the test fails if this equality check // fails. macro_rules! check { ($( $(#[$cfg:meta])* fn $name:ident($f:ident: extern $abi:tt fn($($farg:ty),*) -> $fret:ty, $($arg:ident: $t:ty),*) -> Option<$ret:ty> { $($code:tt)* } )*) => ( $( $(#[$cfg])* fn $name($f: extern $abi fn($($farg),*) -> $fret, $($arg: $t),*) -> Option<$ret> { $($code)* } )* mod _test { use qc::*; use std::mem; use quickcheck::TestResult; $( $(#[$cfg])* #[test] fn $name() { fn my_check($($arg:$t),*) -> TestResult { let my_answer = super::$name(super::super::$name, $($arg),*); let compiler_rt_fn = ::compiler_rt::get(stringify!($name)); let compiler_rt_answer = unsafe { super::$name(mem::transmute(compiler_rt_fn), $($arg),*) }; let gcc_s_answer = match ::gcc_s::get(stringify!($name)) { Some(f) => unsafe { Some(super::$name(mem::transmute(f), $($arg),*)) }, None => None, }; let print_values = || { print!("{} - Args: ", stringify!($name)); $(print!("{:?} ", $arg);)* print!("\n"); println!(" compiler-builtins: {:?}", my_answer); println!(" compiler_rt: {:?}", compiler_rt_answer); println!(" gcc_s: {:?}", gcc_s_answer); }; if my_answer != compiler_rt_answer { print_values(); TestResult::from_bool(false) } else if gcc_s_answer.is_some() && my_answer != gcc_s_answer.unwrap() { print_values(); TestResult::from_bool(false) } else { TestResult::from_bool(true) } } ::quickcheck::quickcheck(my_check as fn($($t),*) -> TestResult) } )* } ) }