Merge pull request #36 from japaric/qc

quickcheck: better generation of input arguments
This commit is contained in:
Jorge Aparicio 2016-08-13 19:27:54 -05:00 committed by GitHub
commit 1f3bad04bc
5 changed files with 161 additions and 15 deletions

View File

@ -1,8 +1,8 @@
#![allow(unused_features)] #![allow(unused_features)]
#![no_std]
#![feature(asm)] #![feature(asm)]
#![feature(core_intrinsics)] #![feature(core_intrinsics)]
#![feature(naked_functions)] #![feature(naked_functions)]
#![cfg_attr(not(test), no_std)]
// TODO(rust-lang/rust#35021) uncomment when that PR lands // TODO(rust-lang/rust#35021) uncomment when that PR lands
// #![feature(rustc_builtins)] // #![feature(rustc_builtins)]
@ -13,6 +13,9 @@
#[macro_use] #[macro_use]
extern crate quickcheck; extern crate quickcheck;
#[cfg(test)]
extern crate core;
#[cfg(target_arch = "arm")] #[cfg(target_arch = "arm")]
pub mod arm; pub mod arm;
@ -20,6 +23,9 @@ pub mod udiv;
pub mod mul; pub mod mul;
pub mod shift; pub mod shift;
#[cfg(test)]
mod qc;
/// Trait for some basic operations on integers /// Trait for some basic operations on integers
trait Int { trait Int {
fn bits() -> u32; fn bits() -> u32;

View File

@ -19,7 +19,7 @@ macro_rules! mul {
low += (t & lower_mask) << half_bits; low += (t & lower_mask) << half_bits;
high += t >> half_bits; high += t >> half_bits;
high += (a.low() >> half_bits) * (b.low() >> half_bits); high += (a.low() >> half_bits) * (b.low() >> half_bits);
high += a.high().wrapping_mul(b.low()) + a.low().wrapping_mul(b.high()); high = high.wrapping_add(a.high().wrapping_mul(b.low()).wrapping_add(a.low().wrapping_mul(b.high())));
<$ty>::from_parts(low, high) <$ty>::from_parts(low, high)
} }
} }
@ -72,13 +72,17 @@ mulo!(__mulodi4: i64);
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use qc::{I32, I64, U64};
quickcheck! { quickcheck! {
fn muldi(a: u64, b: u64) -> bool { fn muldi(a: U64, b: U64) -> bool {
let (a, b) = (a.0, b.0);
let r = super::__muldi4(a, b); let r = super::__muldi4(a, b);
r == a.wrapping_mul(b) r == a.wrapping_mul(b)
} }
fn mulosi(a: i32, b: i32) -> bool { fn mulosi(a: I32, b: I32) -> bool {
let (a, b) = (a.0, b.0);
let mut overflow = 2; let mut overflow = 2;
let r = super::__mulosi4(a, b, &mut overflow); let r = super::__mulosi4(a, b, &mut overflow);
if overflow != 0 && overflow != 1 { if overflow != 0 && overflow != 1 {
@ -87,7 +91,8 @@ mod tests {
(r, overflow != 0) == a.overflowing_mul(b) (r, overflow != 0) == a.overflowing_mul(b)
} }
fn mulodi(a: i64, b: i64) -> bool { fn mulodi(a: I64, b: I64) -> bool {
let (a, b) = (a.0, b.0);
let mut overflow = 2; let mut overflow = 2;
let r = super::__mulodi4(a, b, &mut overflow); let r = super::__mulodi4(a, b, &mut overflow);
if overflow != 0 && overflow != 1 { if overflow != 0 && overflow != 1 {

123
src/qc.rs Normal file
View File

@ -0,0 +1,123 @@
// 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 quickcheck::{Arbitrary, Gen};
use LargeInt;
// Generates values in the full range of the integer type
macro_rules! arbitrary {
($TY:ident : $ty:ident) => {
#[derive(Clone, Copy)]
pub struct $TY(pub $ty);
impl Arbitrary for $TY {
fn arbitrary<G>(g: &mut G) -> $TY
where G: Gen
{
$TY(g.gen())
}
fn shrink(&self) -> Box<Iterator<Item=$TY>> {
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)]
pub struct $TY(pub $ty);
impl Arbitrary for $TY {
fn arbitrary<G>(g: &mut G) -> $TY
where G: Gen
{
if g.gen() {
$TY($ty::from_parts(g.gen(), g.gen()))
} else if g.gen() {
$TY($ty::from_parts(0, g.gen()))
} else {
$TY($ty::from_parts(g.gen(), 0))
}
}
fn shrink(&self) -> Box<Iterator<Item=$TY>> {
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);

View File

@ -61,9 +61,12 @@ lshr!(__lshrdi3: u64);
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use quickcheck::TestResult; use quickcheck::TestResult;
use qc::{I64, U64};
// NOTE We purposefully stick to `u32` for `b` here because we want "small" values (b < 64)
quickcheck! { quickcheck! {
fn ashldi(a: u64, b: u32) -> TestResult { fn ashldi(a: U64, b: u32) -> TestResult {
let a = a.0;
if b >= 64 { if b >= 64 {
TestResult::discard() TestResult::discard()
} else { } else {
@ -72,7 +75,8 @@ mod tests {
} }
} }
fn ashrdi(a: i64, b: u32) -> TestResult { fn ashrdi(a: I64, b: u32) -> TestResult {
let a = a.0;
if b >= 64 { if b >= 64 {
TestResult::discard() TestResult::discard()
} else { } else {
@ -81,7 +85,8 @@ mod tests {
} }
} }
fn lshrdi(a: u64, b: u32) -> TestResult { fn lshrdi(a: U64, b: u32) -> TestResult {
let a = a.0;
if b >= 64 { if b >= 64 {
TestResult::discard() TestResult::discard()
} else { } else {

View File

@ -186,8 +186,8 @@ pub extern "C" fn __udivmoddi4(n: u64, d: u64, rem: Option<&mut u64>) -> u64 {
if sr > u32::bits() - 1 { if sr > u32::bits() - 1 {
if let Some(rem) = rem { if let Some(rem) = rem {
*rem = n; *rem = n;
return 0;
} }
return 0;
} }
sr += 1; sr += 1;
@ -229,9 +229,11 @@ pub extern "C" fn __udivmoddi4(n: u64, d: u64, rem: Option<&mut u64>) -> u64 {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use quickcheck::TestResult; use quickcheck::TestResult;
use qc::{U32, U64};
quickcheck!{ quickcheck!{
fn udivdi3(n: u64, d: u64) -> TestResult { fn udivdi3(n: U64, d: U64) -> TestResult {
let (n, d) = (n.0, d.0);
if d == 0 { if d == 0 {
TestResult::discard() TestResult::discard()
} else { } else {
@ -240,7 +242,8 @@ mod tests {
} }
} }
fn umoddi3(n: u64, d: u64) -> TestResult { fn umoddi3(n: U64, d: U64) -> TestResult {
let (n, d) = (n.0, d.0);
if d == 0 { if d == 0 {
TestResult::discard() TestResult::discard()
} else { } else {
@ -249,7 +252,8 @@ mod tests {
} }
} }
fn udivmoddi4(n: u64, d: u64) -> TestResult { fn udivmoddi4(n: U64, d: U64) -> TestResult {
let (n, d) = (n.0, d.0);
if d == 0 { if d == 0 {
TestResult::discard() TestResult::discard()
} else { } else {
@ -259,7 +263,8 @@ mod tests {
} }
} }
fn udivsi3(n: u32, d: u32) -> TestResult { fn udivsi3(n: U32, d: U32) -> TestResult {
let (n, d) = (n.0, d.0);
if d == 0 { if d == 0 {
TestResult::discard() TestResult::discard()
} else { } else {
@ -268,7 +273,8 @@ mod tests {
} }
} }
fn umodsi3(n: u32, d: u32) -> TestResult { fn umodsi3(n: U32, d: U32) -> TestResult {
let (n, d) = (n.0, d.0);
if d == 0 { if d == 0 {
TestResult::discard() TestResult::discard()
} else { } else {
@ -277,7 +283,8 @@ mod tests {
} }
} }
fn udivmodsi4(n: u32, d: u32) -> TestResult { fn udivmodsi4(n: U32, d: U32) -> TestResult {
let (n, d) = (n.0, d.0);
if d == 0 { if d == 0 {
TestResult::discard() TestResult::discard()
} else { } else {