From 254e2fd72ac5da7e9519ff78bb960fd022cad4db Mon Sep 17 00:00:00 2001 From: Mike Boutin Date: Tue, 17 Aug 2021 18:56:47 -0400 Subject: [PATCH] Update to `quickcheck` 1.0. Change required the MSRV update as well as refactoring `from_base`, `to_base`, and multiple tests. The `from_base` and `to_base` changes provide better floating point precision while still maintaing zero-cost guarantees. The test changes better handle floating point precision issues as well as the wider range of values generated by `quickcheck` 1.0's `Arbitrary` implementation. --- Cargo.toml | 2 +- src/lib.rs | 4 +- src/si/ratio.rs | 16 ++-- src/si/time.rs | 93 +++++++++------------ src/system.rs | 41 ++++++---- src/tests/mod.rs | 44 +++++++--- src/tests/quantity.rs | 186 ++++++++++++++++++++++++++++++------------ src/tests/system.rs | 26 ++++-- 8 files changed, 256 insertions(+), 156 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d421ee86..99dffdb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ typenum = "1.13" [dev-dependencies] approx = "0.5" -quickcheck = "0.9.2" +quickcheck = "1.0" serde_json = "1.0" static_assertions = "1.1" diff --git a/src/lib.rs b/src/lib.rs index 039050ef..2ed8a696 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -438,8 +438,10 @@ pub trait Conversion { /// * `V`: Underlying storage type trait is implemented for. /// /// [factor]: https://jcgm.bipm.org/vim/en/1.24.html +#[allow(unused_qualifications)] // lib:cmp::PartialOrder false positive. pub trait ConversionFactor: - lib::ops::Add + lib::cmp::PartialOrd + + lib::ops::Add + lib::ops::Sub + lib::ops::Mul + lib::ops::Div diff --git a/src/si/ratio.rs b/src/si/ratio.rs index 9c19b979..47abeba9 100644 --- a/src/si/ratio.rs +++ b/src/si/ratio.rs @@ -161,33 +161,29 @@ mod tests { use crate::si::quantities::*; use crate::tests::Test; - fn test_nan_or_eq(yl: V, yr: V) -> bool { - (yl.is_nan() && yr.is_nan()) || Test::eq(&yl, &yr) - } - quickcheck! { fn acos(x: V) -> bool { - test_nan_or_eq(x.acos(), Ratio::from(x).acos().get::()) + Test::eq(&x.acos(), &Ratio::from(x).acos().get::()) } fn acosh(x: V) -> bool { - test_nan_or_eq(x.acosh(), Ratio::from(x).acosh().get::()) + Test::eq(&x.acosh(), &Ratio::from(x).acosh().get::()) } fn asin(x: V) -> bool { - test_nan_or_eq(x.asin(), Ratio::from(x).asin().get::()) + Test::eq(&x.asin(), &Ratio::from(x).asin().get::()) } fn asinh(x: V) -> bool { - test_nan_or_eq(x.asinh(), Ratio::from(x).asinh().get::()) + Test::eq(&x.asinh(), &Ratio::from(x).asinh().get::()) } fn atan(x: V) -> bool { - test_nan_or_eq(x.atan(), Ratio::from(x).atan().get::()) + Test::eq(&x.atan(), &Ratio::from(x).atan().get::()) } fn atanh(x: V) -> bool { - test_nan_or_eq(x.atanh(), Ratio::from(x).atanh().get::()) + Test::eq(&x.atanh(), &Ratio::from(x).atanh().get::()) } } } diff --git a/src/si/time.rs b/src/si/time.rs index 1810b7ed..5f03de91 100644 --- a/src/si/time.rs +++ b/src/si/time.rs @@ -124,7 +124,7 @@ where fn try_from(duration: Duration) -> Result { let secs = V::from_u64(duration.as_secs()); - let nanos = V::from_u32(duration.subsec_micros()); + let nanos = V::from_u32(duration.subsec_nanos()); match (secs, nanos) { (Some(secs), Some(nanos)) => { @@ -140,73 +140,54 @@ mod tests { storage_types! { types: PrimInt, BigInt, BigUint, Float; - use crate::lib::convert::{TryFrom, TryInto}; + use crate::ConversionFactor; + use crate::lib::convert::TryFrom; use crate::lib::time::Duration; - use crate::num::{FromPrimitive, ToPrimitive, Zero}; + use crate::num::{FromPrimitive, ToPrimitive, One, Zero}; use crate::si::quantities::*; - use crate::si::time::{TryFromError, nanosecond}; + use crate::si::time::{TryFromError, second, nanosecond}; use crate::tests::*; use quickcheck::TestResult; quickcheck! { - fn duration_try_from(v: A) -> TestResult { - test_try_from(Duration::try_from(Time::new::((*v).clone())), v) - } - - fn time_try_into(v: A) -> TestResult { - test_try_from(Time::new::((*v).clone()).try_into(), v) - } - - fn time_try_from(v: A) -> TestResult { - match v.to_u64() { - Some(u) => test_try_into(Time::try_from(Duration::from_nanos(u)), v), - None => TestResult::discard(), + fn duration_try_from(v: A) -> bool { + let ns: V = >::coefficient().value(); + let t = Time::new::((*v).clone()); + let d = Duration::try_from(t); + let r = (*v).clone() % V::one(); + let s = ((*v).clone() - r.clone()).to_u64(); + let n = (r * (V::one() / &ns)).to_u32(); + + match (d, s, n) { + (Ok(d), Some(s), Some(n)) => d.as_secs() == s && d.subsec_nanos() == n, + (Err(TryFromError::NegativeDuration), _, _) if *v < V::zero() => true, + (Err(TryFromError::Overflow), None, _) => true, + (Err(TryFromError::Overflow), _, None) => true, + _ => false, } } - fn duration_try_into(v: A) -> TestResult { - match v.to_u64() { - Some(u) => test_try_into(Duration::from_nanos(u).try_into(), v), - None => TestResult::discard(), + fn time_try_from(v: A) -> TestResult { + if *v < V::zero() { + return TestResult::discard(); } - } - } - fn test_try_from(t: Result, v: A) -> TestResult { - if *v < V::zero() { - return TestResult::discard(); + let ns: V = >::coefficient().value(); + let r = (*v).clone() % V::one(); + let s = ((*v).clone() - r.clone()).to_u64(); + let n = (r * (V::one() / &ns)).to_u32(); + + return match (s, n) { + (Some(s), Some(n)) => TestResult::from_bool( + match (Time::try_from(Duration::new(s, n)), V::from_u64(s), V::from_u32(n)) { + (Ok(t), Some(s), Some(n)) => t == Time::new::(s) + Time::new::(n), + (Err(TryFromError::Overflow), None, _) => true, + (Err(TryFromError::Overflow), _, None) => true, + _ => false, + }), + _ => TestResult::discard(), + } } - - let ok = match (t, v.to_u64()) { - (Ok(t), Some(u)) => { - let d = Duration::from_nanos(u); - let r = if d > t { d - t } else { t - d }; - - Duration::from_nanos(1) >= r - }, - (Err(_), None) => true, - _ => false, - }; - - TestResult::from_bool(ok) - } - - fn test_try_into(t: Result, TryFromError>, v: A) -> TestResult { - if *v < V::zero() { - return TestResult::discard(); - } - - let ok = match t { - Ok(t) => { - let b = Time::new::((*v).clone()); - let d = if t > b { t - b } else { b - t }; - - d <= Time::new::(V::from_u8(100).unwrap()) - }, - _ => false, - }; - - TestResult::from_bool(ok) } } } diff --git a/src/system.rs b/src/system.rs index 18a55a4e..1b753e67 100644 --- a/src/system.rs +++ b/src/system.rs @@ -303,16 +303,23 @@ macro_rules! system { where D: Dimension + ?Sized, U: Units + ?Sized, - V: $crate::Conversion + $crate::lib::ops::Mul, + V: $crate::Conversion, N: $crate::Conversion, { use $crate::typenum::Integer; - use $crate::Conversion; - use $crate::ConversionFactor; + use $crate::{Conversion, ConversionFactor}; - (v.conversion() $(* U::$name::coefficient().powi(D::$symbol::to_i32()))+ - / N::coefficient() - N::constant($crate::ConstantOp::Sub)) - .value() + let v = v.conversion(); + let n_coef = N::coefficient(); + let f = V::coefficient() $(* U::$name::coefficient().powi(D::$symbol::to_i32()))+; + let n_cons = N::constant($crate::ConstantOp::Sub); + + if n_coef < f { + (v * (f / n_coef) - n_cons).value() + } + else { + (v / (n_coef / f) - n_cons).value() + } } /// Convert a value from the given unit to base units. @@ -327,16 +334,23 @@ macro_rules! system { where D: Dimension + ?Sized, U: Units + ?Sized, - V: $crate::Conversion + $crate::lib::ops::Mul, + V: $crate::Conversion, N: $crate::Conversion, { use $crate::typenum::Integer; - use $crate::Conversion; - use $crate::ConversionFactor; + use $crate::{Conversion, ConversionFactor}; - ((v.conversion() + N::constant($crate::ConstantOp::Add)) * N::coefficient() - / (V::coefficient() $(* U::$name::coefficient().powi(D::$symbol::to_i32()))+)) - .value() + let v = v.conversion(); + let n_coef = N::coefficient(); + let f = V::coefficient() $(* U::$name::coefficient().powi(D::$symbol::to_i32()))+; + let n_cons = N::constant($crate::ConstantOp::Add); + + if n_coef >= f { + ((v + n_cons) * (n_coef / f)).value() + } + else { + (((v + n_cons) * n_coef) / f).value() + } } autoconvert_test! { @@ -357,8 +371,7 @@ macro_rules! system { V: $crate::Conversion + $crate::lib::ops::Mul, { use $crate::typenum::Integer; - use $crate::Conversion; - use $crate::ConversionFactor; + use $crate::{Conversion, ConversionFactor}; (v.conversion() $(* Ur::$name::coefficient().powi(D::$symbol::to_i32()) / Ul::$name::coefficient().powi(D::$symbol::to_i32()))+) diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 7f733723..739c0408 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -108,12 +108,37 @@ mod test_trait { const ULPS: u32 = 3; impl super::super::Test for V { + /// Assert that `lhs` and `rhs` are exactly equal. + fn assert_eq(lhs: &Self, rhs: &Self) { + match (lhs.is_nan(), rhs.is_nan()) { + (true, true) => {} + _ => { assert_eq!(lhs, rhs); } + } + } + + /// Assert that `lhs` and `rhs` are approximately equal for floating point types or + /// exactly equal for other types. fn assert_approx_eq(lhs: &Self, rhs: &Self) { - assert_ulps_eq!(lhs, rhs, epsilon = EPS_FACTOR * V::epsilon(), max_ulps = ULPS); + match (lhs.is_nan(), rhs.is_nan()) { + (true, true) => {} + _ => { + assert_ulps_eq!(lhs, rhs, epsilon = EPS_FACTOR * V::epsilon(), + max_ulps = ULPS); + } + } + } + + /// Exactly compare `lhs` and `rhs` and return the result. + fn eq(lhs: &Self, rhs: &Self) -> bool { + (lhs.is_nan() && rhs.is_nan()) + || lhs == rhs } + /// Approximately compare `lhs` and `rhs` for floating point types or exactly compare + /// for other types and return the result. fn approx_eq(lhs: &Self, rhs: &Self) -> bool { - ulps_eq!(lhs, rhs, epsilon = EPS_FACTOR * V::epsilon(), max_ulps = ULPS) + (lhs.is_nan() && rhs.is_nan()) + || ulps_eq!(lhs, rhs, epsilon = EPS_FACTOR * V::epsilon(), max_ulps = ULPS) } } } @@ -140,32 +165,25 @@ impl crate::lib::ops::Deref for A { mod a_struct { storage_types! { - // Quickcheck 0.8 required for i128/u128 support. Use PrimInt after upgrade. - types: Float, usize, u8, u16, u32, u64, isize, i8, i16, i32, i64; + types: Float, PrimInt; use super::super::A; impl quickcheck::Arbitrary for A { - fn arbitrary(g: &mut G) -> Self - where - G: quickcheck::Gen, - { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { A { v: V::arbitrary(g), } } } } storage_types! { - types: BigInt, BigUint, Ratio, i128, u128; + types: BigInt, BigUint, Ratio; use crate::num::FromPrimitive; use super::super::A; impl quickcheck::Arbitrary for A { - fn arbitrary(g: &mut G) -> Self - where - G: quickcheck::Gen, - { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { A { v: loop { let v = V::from_f64(::arbitrary(g)); diff --git a/src/tests/quantity.rs b/src/tests/quantity.rs index 7ccc6565..1f0333f1 100644 --- a/src/tests/quantity.rs +++ b/src/tests/quantity.rs @@ -54,30 +54,55 @@ storage_types! { #[cfg(feature = "autoconvert")] quickcheck! { #[allow(trivial_casts)] - fn add(l: A, r: A) -> bool { - Test::approx_eq(&k::Length::new::(&*l + &*r), - &(k::Length::new::((*l).clone()) - + f::Length::new::((*r).clone()))) + fn add(l: A, r: A) -> TestResult { + let km: V = >::coefficient().value(); + let f = (&*l + &*r) / &km; + let i = (&*l / &km) + (&*r / &km); + + if !Test::approx_eq(&i, &f) { + return TestResult::discard(); + } + + TestResult::from_bool( + Test::approx_eq(&k::Length::new::(&*l + &*r), + &(k::Length::new::((*l).clone()) + + f::Length::new::((*r).clone())))) } #[allow(trivial_casts)] - fn sub(l: A, r: A) -> bool { - Test::approx_eq(&k::Length::new::(&*l - &*r), - &(k::Length::new::((*l).clone()) - - f::Length::new::((*r).clone()))) + fn sub(l: A, r: A) -> TestResult { + let km: V = >::coefficient().value(); + let f = (&*l - &*r) / &km; + let i = (&*l / &km) - (&*r / &km); + + if !Test::approx_eq(&i, &f) { + return TestResult::discard(); + } + + TestResult::from_bool( + Test::approx_eq(&k::Length::new::(&*l - &*r), + &(k::Length::new::((*l).clone()) + - f::Length::new::((*r).clone())))) } #[allow(trivial_casts)] - fn mul_quantity(l: A, r: A) -> bool { - Test::approx_eq(&/*Area::new::*/(&*l * &*r), - &(f::Length::new::((*l).clone()) - * k::Length::new::((*r).clone())).value) - && Test::approx_eq(&/*Area::new::*/(&*l * &*r), - &(f::Length::new::((*l).clone()) - * k::Mass::new::((*r).clone())).value) - && Test::approx_eq(&/*Area::new::*/(&*l * &*r), - &(k::Length::new::((*l).clone()) - * f::Mass::new::((*r).clone())).value) + fn mul_quantity(l: A, r: A) -> TestResult { + let km: V = >::coefficient().value(); + + TestResult::from_bool( + Test::approx_eq(&/*Area::new::*/(&*l * &*r), + &(f::Length::new::((*l).clone()) + * k::Length::new::((*r).clone())).value) + && Test::approx_eq( + &/*Area::new::*/(&(&*l / &km) * &(&*r / &km)), + &(k::Length::new::((*l).clone()) + * f::Length::new::((*r).clone())).value) + && Test::approx_eq(&/*Length-mass*/(&*l * &*r), + &(f::Length::new::((*l).clone()) + * k::Mass::new::((*r).clone())).value) + && Test::approx_eq(&/*Length-mass*/(&*l * &*r), + &(k::Length::new::((*l).clone()) + * f::Mass::new::((*r).clone())).value)) } #[allow(trivial_casts)] @@ -86,11 +111,21 @@ storage_types! { return TestResult::discard(); } - // TODO Use `.get(?)`, add ratio type? + let km: V = >::coefficient().value(); + TestResult::from_bool( - Test::approx_eq(&(&*l / &*r), - &(k::Length::new::((*l).clone()) - / f::Length::new::((*r).clone())).value)) + Test::approx_eq(&/*Ratio::new::*/(&*l / &*r), + &(f::Length::new::((*l).clone()) + / k::Length::new::((*r).clone())).value) + && Test::approx_eq(&/*Ratio::new::*/(&*l / &*r), + &(k::Length::new::((*l).clone()) + / f::Length::new::((*r).clone())).value) + && Test::approx_eq(&/*Length/mass*/(&*l / &*r), + &(f::Length::new::((*l).clone()) + / k::Mass::new::((*r).clone())).value) + && Test::approx_eq(&/*Length/mass*/(&*l / &km / &*r), + &(k::Length::new::((*l).clone()) + / f::Mass::new::((*r).clone())).value)) } #[allow(trivial_casts)] @@ -99,6 +134,14 @@ storage_types! { return TestResult::discard(); } + let km: V = >::coefficient().value(); + let f = (&*l % &*r) / &km; + let i = (&*l / &km) % (&*r / &km); + + if !Test::approx_eq(&i, &f) { + return TestResult::discard(); + } + TestResult::from_bool( Test::approx_eq(&k::Length::new::(&*l % &*r), &(k::Length::new::((*l).clone()) @@ -107,79 +150,93 @@ storage_types! { #[allow(trivial_casts)] fn eq(l: A, r: A) -> bool { - let a = *l == *r; - let b = f::Length::new::((*l).clone()) + let km: V = >::coefficient().value(); + let a = *l == (*r / &km) * &km; + let b = *l / &km == *r / &km; + let x = f::Length::new::((*l).clone()) == k::Length::new::((*r).clone()); - let c = k::Length::new::((*l).clone()) + let y = k::Length::new::((*l).clone()) == f::Length::new::((*r).clone()); - a == b && a == c + a == x && b == y } #[allow(trivial_casts)] fn ne(l: A, r: A) -> bool { - let a = *l != *r; - let b = f::Length::new::((*l).clone()) + let km: V = >::coefficient().value(); + let a = *l != (*r / &km) * &km; + let b = *l / &km != *r / &km; + let x = f::Length::new::((*l).clone()) != k::Length::new::((*r).clone()); - let c = k::Length::new::((*l).clone()) + let y = k::Length::new::((*l).clone()) != f::Length::new::((*r).clone()); - a == b && a == c + a == x && b == y } #[allow(trivial_casts)] fn partial_cmp(l: A, r: A) -> bool { - let a = (*l).partial_cmp(&*r); - let b = f::Length::new::((*l).clone()).partial_cmp( + let km: V = >::coefficient().value(); + let a = (*l).partial_cmp(&((*r / &km) * &km)); + let b = (*l / &km).partial_cmp(&(*r / &km)); + let x = f::Length::new::((*l).clone()).partial_cmp( &k::Length::new::((*r).clone())); - let c = k::Length::new::((*l).clone()).partial_cmp( + let y = k::Length::new::((*l).clone()).partial_cmp( &f::Length::new::((*r).clone())); - a == b && a == c + a == x && b == y } #[allow(trivial_casts)] fn lt(l: A, r: A) -> bool { - let a = (*l).lt(&*r); - let b = f::Length::new::((*l).clone()).lt( + let km: V = >::coefficient().value(); + let a = (*l).lt(&((*r / &km) * &km)); + let b = (*l / &km).lt(&(*r / &km)); + let x = f::Length::new::((*l).clone()).lt( &k::Length::new::((*r).clone())); - let c = k::Length::new::((*l).clone()).lt( + let y = k::Length::new::((*l).clone()).lt( &f::Length::new::((*r).clone())); - a == b && a == c + a == x && b == y } #[allow(trivial_casts)] fn le(l: A, r: A) -> bool { - let a = (*l).le(&*r); - let b = f::Length::new::((*l).clone()).le( + let km: V = >::coefficient().value(); + let a = (*l).le(&((*r / &km) * &km)); + let b = (*l / &km).le(&(*r / &km)); + let x = f::Length::new::((*l).clone()).le( &k::Length::new::((*r).clone())); - let c = k::Length::new::((*l).clone()).le( + let y = k::Length::new::((*l).clone()).le( &f::Length::new::((*r).clone())); - a == b && a == c + a == x && b == y } #[allow(trivial_casts)] fn gt(l: A, r: A) -> bool { - let a = (*l).gt(&*r); - let b = f::Length::new::((*l).clone()).gt( + let km: V = >::coefficient().value(); + let a = (*l).gt(&((*r / &km) * &km)); + let b = (*l / &km).gt(&(*r / &km)); + let x = f::Length::new::((*l).clone()).gt( &k::Length::new::((*r).clone())); - let c = k::Length::new::((*l).clone()).gt( + let y = k::Length::new::((*l).clone()).gt( &f::Length::new::((*r).clone())); - a == b && a == c + a == x && b == y } #[allow(trivial_casts)] fn ge(l: A, r: A) -> bool { - let a = (*l).ge(&*r); - let b = f::Length::new::((*l).clone()).ge( + let km: V = >::coefficient().value(); + let a = (*l).ge(&((*r / &km) * &km)); + let b = (*l / &km).ge(&(*r / &km)); + let x = f::Length::new::((*l).clone()).ge( &k::Length::new::((*r).clone())); - let c = k::Length::new::((*l).clone()).ge( + let y = k::Length::new::((*l).clone()).ge( &f::Length::new::((*r).clone())); - a == b && a == c + a == x && b == y } } } @@ -302,25 +359,39 @@ mod non_big { quickcheck! { #[allow(trivial_casts)] - fn add_assign(l: A, r: A) -> bool { + fn add_assign(l: A, r: A) -> TestResult { + let km: V = >::coefficient().value(); let mut f = *l; + let mut i = *l / &km; let mut v = k::Length::new::(*l); f += *r; + i += *r / &km; v += f::Length::new::(*r); - Test::approx_eq(&k::Length::new::(f), &v) + if !Test::approx_eq(&i, &(f / &km)) { + return TestResult::discard(); + } + + TestResult::from_bool(Test::approx_eq(&k::Length::new::(f), &v)) } #[allow(trivial_casts)] - fn sub_assign(l: A, r: A) -> bool { + fn sub_assign(l: A, r: A) -> TestResult { + let km: V = >::coefficient().value(); let mut f = *l; + let mut i = *l / &km; let mut v = k::Length::new::(*l); f -= *r; + i -= *r / &km; v -= f::Length::new::(*r); - Test::approx_eq(&k::Length::new::(f), &v) + if !Test::approx_eq(&i, &(f / &km)) { + return TestResult::discard(); + } + + TestResult::from_bool(Test::approx_eq(&k::Length::new::(f), &v)) } #[allow(trivial_casts)] @@ -329,12 +400,19 @@ mod non_big { return TestResult::discard(); } + let km: V = >::coefficient().value(); let mut f = *l; + let mut i = *l / &km; let mut v = k::Length::new::(*l); f %= *r; + i %= *r / &km; v %= f::Length::new::(*r); + if !Test::approx_eq(&i, &(f / km)) { + return TestResult::discard(); + } + TestResult::from_bool(Test::approx_eq(&k::Length::new::(f), &v)) } } diff --git a/src/tests/system.rs b/src/tests/system.rs index 87d366b6..ab715fa4 100644 --- a/src/tests/system.rs +++ b/src/tests/system.rs @@ -52,7 +52,7 @@ storage_types! { // &crate::tests::from_base::( // &*v)) // kelvin -> fahrenheit. - && Test::approx_eq(&(&*v / &f_coefficient - &f_constant), + && Test::approx_eq(&((&*v * (&V::one() / &f_coefficient)) - &f_constant), &crate::tests::from_base::( &*v)) // fahrenheit -> kelvin. @@ -103,12 +103,19 @@ storage_types! { } #[allow(trivial_casts)] - fn change_base(v: A) -> bool + fn change_base(v: A) -> TestResult { let km: V = >::coefficient().value(); + let f1 = (*v).clone(); + let i1 = &*v * &km / &km; - // meter -> meter. - Test::approx_eq(&*v, + if !Test::approx_eq(&f1, &i1) { + return TestResult::discard(); + } + + TestResult::from_bool( + // meter -> meter. + Test::approx_eq(&*v, &crate::tests::change_base::( &*v)) // kilometer -> kilometer. @@ -122,7 +129,7 @@ storage_types! { // meter -> kilometer. && Test::approx_eq(&(&*v / &km), &crate::tests::change_base::( - &*v)) + &*v))) } #[allow(trivial_casts)] @@ -552,12 +559,17 @@ mod primitive { } #[allow(trivial_casts)] - fn serde_deserialize(v: A) -> bool { + fn serde_deserialize(v: A) -> TestResult { let json_f = serde_json::to_string(&*v).expect("Must be able to serialize num"); + + if let Err(_) = serde_json::from_str::(&json_f) { + return TestResult::discard(); + } + let length: Length = serde_json::from_str(&json_f) .expect("Must be able to deserialize Quantity"); - Test::approx_eq(&Length::new::(*v), &length) + TestResult::from_bool(Test::approx_eq(&Length::new::(*v), &length)) } } }