From daeae167332a5cc7551f584bc298fa120311675f Mon Sep 17 00:00:00 2001 From: haraldmaida Date: Sun, 20 Apr 2025 17:23:25 +0200 Subject: [PATCH 1/2] feat: provide additional assertions for integers and floats --- src/assertions.rs | 126 +++++++++ src/expectations.rs | 30 +++ src/float/mod.rs | 256 ++++++++++++------ src/float/tests.rs | 615 ++++++++++++++++++++++++++++++++++++------- src/integer/mod.rs | 67 +++++ src/integer/tests.rs | 424 +++++++++++++++++++++++++++++ src/lib.rs | 5 +- src/number.rs | 230 ++++++++++++++++ src/properties.rs | 124 ++++++++- 9 files changed, 1686 insertions(+), 191 deletions(-) create mode 100644 src/number.rs diff --git a/src/assertions.rs b/src/assertions.rs index b15cdd7..ccfa4b1 100644 --- a/src/assertions.rs +++ b/src/assertions.rs @@ -209,6 +209,132 @@ pub trait AssertInRange { fn is_not_in_range(self, range: RangeInclusive) -> Self; } +/// Assert whether a numeric value is negative or positive. +/// +/// # Examples +/// +/// ``` +/// use asserting::prelude::*; +/// +/// assert_that!(-42).is_negative(); +/// assert_that!(42).is_positive(); +/// assert_that!(0).is_not_negative(); +/// assert_that!(1).is_not_negative(); +/// assert_that!(0).is_not_positive(); +/// assert_that!(-1).is_not_positive(); +/// +/// assert_that!(-0.1).is_negative(); +/// assert_that!(0.1).is_positive(); +/// assert_that!(0.0).is_not_negative(); +/// assert_that!(0.1).is_not_negative(); +/// assert_that!(0.0).is_not_positive(); +/// assert_that!(-0.1).is_not_positive(); +/// ``` +pub trait AssertSignum { + /// Verifies that the subject is a negative number. + /// + /// This is equivalent to asserting that a number is less than 0. + #[track_caller] + fn is_negative(self) -> Self; + + /// Verifies that the subject is a non-negative number. + /// + /// This is equivalent to asserting that a number is greater than or equal + /// to 0. + #[track_caller] + fn is_not_negative(self) -> Self; + + /// Verifies that the subject is a positive number. + /// + /// This is equivalent to asserting that a number is greater than 0. + #[track_caller] + fn is_positive(self) -> Self; + + /// Verifies that the subject is a non-positive number. + /// + /// This is equivalent to asserting that a number is less than or equal to + /// 0. + #[track_caller] + fn is_not_positive(self) -> Self; +} + +/// Assert the additive and multiplicative identity of a number. +/// +/// # Examples +/// +/// ``` +/// use asserting::prelude::*; +/// +/// assert_that!(0).is_zero(); +/// assert_that!(1).is_one(); +/// assert_that!(0.0).is_zero(); +/// assert_that!(1.0).is_one(); +/// ``` +pub trait AssertNumericIdentity { + /// Verifies whether the subject is the additive identity (zero). + #[track_caller] + fn is_zero(self) -> Self; + + /// Verifies whether the subject is the multiplicative identity (one). + #[track_caller] + fn is_one(self) -> Self; +} + +/// Assert whether a numeric value is infinite or finite. +/// +/// # Examples +/// +/// ``` +/// use asserting::prelude::*; +/// +/// assert_that!(0.1).is_finite(); +/// assert_that!(0.0).is_finite(); +/// assert_that!(f32::INFINITY).is_infinite(); +/// assert_that!(f32::NEG_INFINITY).is_infinite(); +/// assert_that!(f64::INFINITY).is_infinite(); +/// assert_that!(f64::NEG_INFINITY).is_infinite(); +/// ``` +/// +/// Assert negative and positive infinity: +/// +/// ``` +/// use asserting::prelude::*; +/// +/// assert_that!(f64::INFINITY).is_positive().is_infinite(); +/// assert_that!(f64::NEG_INFINITY).is_negative().is_infinite(); +/// ``` +pub trait AssertInfinity { + /// Verifies that the subject is an infinite number. + #[track_caller] + fn is_infinite(self) -> Self; + + /// Verifies that the subject is a finite number. + #[track_caller] + fn is_finite(self) -> Self; +} + +/// Assert whether a numeric value is not a number. +/// +/// # Examples +/// +/// ``` +/// use asserting::prelude::*; +/// +/// assert_that!(0.1).is_a_number(); +/// assert_that!(0.0).is_a_number(); +/// assert_that!(f32::NAN).is_not_a_number(); +/// assert_that!(f64::NAN).is_not_a_number(); +/// ``` +pub trait AssertNotANumber { + /// Verifies that the subject is not a number. + #[track_caller] + fn is_not_a_number(self) -> Self; + + /// Verifies that the subject is a number. + #[track_caller] + fn is_a_number(self) -> Self; +} + /// Assert whether some value or expression is true or false. /// /// # Examples diff --git a/src/expectations.rs b/src/expectations.rs index 402c5d4..2651a1f 100644 --- a/src/expectations.rs +++ b/src/expectations.rs @@ -119,6 +119,36 @@ pub struct IsNotInRange { pub expected_range: RangeInclusive, } +#[must_use] +pub struct IsNegative; + +#[must_use] +pub struct IsNotNegative; + +#[must_use] +pub struct IsPositive; + +#[must_use] +pub struct IsNotPositive; + +#[must_use] +pub struct IsZero; + +#[must_use] +pub struct IsOne; + +#[must_use] +pub struct IsFinite; + +#[must_use] +pub struct IsInfinite; + +#[must_use] +pub struct IsNotANumber; + +#[must_use] +pub struct IsANumber; + #[must_use] pub struct IsSome; diff --git a/src/float/mod.rs b/src/float/mod.rs index 348f0ff..b3d880f 100644 --- a/src/float/mod.rs +++ b/src/float/mod.rs @@ -1,117 +1,203 @@ -use crate::assertions::{AssertIsCloseToWithDefaultMargin, AssertIsCloseToWithinMargin}; -use crate::colored::mark_diff; -use crate::expectations::{IsCloseTo, IsNotCloseTo}; -use crate::spec::{DiffFormat, Expectation, Expression, FailingStrategy, Spec}; -use crate::std::{format, string::String}; -use float_cmp::{ApproxEq, F32Margin, F64Margin}; - -impl AssertIsCloseToWithDefaultMargin for Spec<'_, f32, R> -where - R: FailingStrategy, -{ - fn is_close_to(self, expected: f32) -> Self { - self.expecting( - IsCloseTo::<_, F32Margin>::new(expected).within_margin((4. * f32::EPSILON, 4)), - ) - } - - fn is_not_close_to(self, expected: f32) -> Self { - self.expecting( - IsNotCloseTo::<_, F32Margin>::new(expected).within_margin((4. * f32::EPSILON, 4)), - ) - } -} +use crate::properties::{ + AdditiveIdentityProperty, InfinityProperty, IsNanProperty, MultiplicativeIdentityProperty, + SignumProperty, +}; -impl AssertIsCloseToWithinMargin for Spec<'_, f32, R> -where - R: FailingStrategy, -{ - fn is_close_to_with_margin(self, expected: f32, margin: impl Into) -> Self { - self.expecting(IsCloseTo::new(expected).within_margin(margin)) +impl SignumProperty for f32 { + fn is_negative_property(&self) -> bool { + self.is_sign_negative() } - fn is_not_close_to_with_margin(self, expected: f32, margin: impl Into) -> Self { - self.expecting(IsNotCloseTo::new(expected).within_margin(margin)) + fn is_positive_property(&self) -> bool { + self.is_sign_positive() && *self != 0. } } -impl AssertIsCloseToWithDefaultMargin for Spec<'_, f64, R> -where - R: FailingStrategy, -{ - fn is_close_to(self, expected: f64) -> Self { - self.expecting( - IsCloseTo::<_, F64Margin>::new(expected).within_margin((4. * f64::EPSILON, 4)), - ) +impl SignumProperty for f64 { + fn is_negative_property(&self) -> bool { + self.is_sign_negative() } - fn is_not_close_to(self, expected: f64) -> Self { - self.expecting( - IsNotCloseTo::<_, F64Margin>::new(expected).within_margin((4. * f64::EPSILON, 4)), - ) + fn is_positive_property(&self) -> bool { + self.is_sign_positive() && *self != 0. } } -impl AssertIsCloseToWithinMargin for Spec<'_, f64, R> -where - R: FailingStrategy, -{ - fn is_close_to_with_margin(self, expected: f64, margin: impl Into) -> Self { - self.expecting(IsCloseTo::new(expected).within_margin(margin)) - } +impl AdditiveIdentityProperty for f32 { + const ADDITIVE_IDENTITY: Self = 0.; +} - fn is_not_close_to_with_margin(self, expected: f64, margin: impl Into) -> Self { - self.expecting(IsNotCloseTo::new(expected).within_margin(margin)) - } +impl AdditiveIdentityProperty for f64 { + const ADDITIVE_IDENTITY: Self = 0.; } -impl Expectation for IsCloseTo { - fn test(&mut self, subject: &f32) -> bool { - subject.approx_eq(self.expected, self.margin) - } +impl MultiplicativeIdentityProperty for f32 { + const MULTIPLICATIVE_IDENTITY: Self = 1.; +} - fn message(&self, expression: Expression<'_>, actual: &f32, format: &DiffFormat) -> String { - let (marked_actual, marked_expected) = mark_diff(actual, &self.expected, format); - format!("expected {expression} is close to {:?}\n within a margin of epsilon={:e} and ulps={}\n but was: {marked_actual}\n expected: {marked_expected}", - &self.expected, self.margin.epsilon, self.margin.ulps - ) - } +impl MultiplicativeIdentityProperty for f64 { + const MULTIPLICATIVE_IDENTITY: Self = 1.; } -impl Expectation for IsNotCloseTo { - fn test(&mut self, subject: &f32) -> bool { - !subject.approx_eq(self.expected, self.margin) +impl InfinityProperty for f32 { + fn is_infinite_property(&self) -> bool { + self.is_infinite() } - fn message(&self, expression: Expression<'_>, actual: &f32, _format: &DiffFormat) -> String { - format!("expected {expression} is not close to {:?}\n within a margin of epsilon={:e} and ulps={}\n but was: {actual:?}\n expected: {:?}", - &self.expected, self.margin.epsilon, self.margin.ulps, &self.expected - ) + fn is_finite_property(&self) -> bool { + self.is_finite() } } -impl Expectation for IsCloseTo { - fn test(&mut self, subject: &f64) -> bool { - subject.approx_eq(self.expected, self.margin) +impl InfinityProperty for f64 { + fn is_infinite_property(&self) -> bool { + self.is_infinite() + } + + fn is_finite_property(&self) -> bool { + self.is_finite() } +} - fn message(&self, expression: Expression<'_>, actual: &f64, format: &DiffFormat) -> String { - let (marked_actual, marked_expected) = mark_diff(actual, &self.expected, format); - format!("expected {expression} is close to {:?}\n within a margin of epsilon={:e} and ulps={}\n but was: {marked_actual}\n expected: {marked_expected}", - &self.expected, self.margin.epsilon, self.margin.ulps - ) +impl IsNanProperty for f32 { + fn is_nan_property(&self) -> bool { + self.is_nan() } } -impl Expectation for IsNotCloseTo { - fn test(&mut self, subject: &f64) -> bool { - !subject.approx_eq(self.expected, self.margin) +impl IsNanProperty for f64 { + fn is_nan_property(&self) -> bool { + self.is_nan() } +} - fn message(&self, expression: Expression<'_>, actual: &f64, _format: &DiffFormat) -> String { - format!("expected {expression} is not close to {:?}\n within a margin of epsilon={:e} and ulps={}\n but was: {actual:?}\n expected: {:?}", - &self.expected, self.margin.epsilon, self.margin.ulps, &self.expected - ) +#[cfg(feature = "float")] +mod cmp { + use crate::assertions::{AssertIsCloseToWithDefaultMargin, AssertIsCloseToWithinMargin}; + use crate::colored::mark_diff; + use crate::expectations::{IsCloseTo, IsNotCloseTo}; + use crate::spec::{DiffFormat, Expectation, Expression, FailingStrategy, Spec}; + use crate::std::{format, string::String}; + use float_cmp::{ApproxEq, F32Margin, F64Margin}; + + impl AssertIsCloseToWithDefaultMargin for Spec<'_, f32, R> + where + R: FailingStrategy, + { + fn is_close_to(self, expected: f32) -> Self { + self.expecting( + IsCloseTo::<_, F32Margin>::new(expected).within_margin((4. * f32::EPSILON, 4)), + ) + } + + fn is_not_close_to(self, expected: f32) -> Self { + self.expecting( + IsNotCloseTo::<_, F32Margin>::new(expected).within_margin((4. * f32::EPSILON, 4)), + ) + } + } + + impl AssertIsCloseToWithinMargin for Spec<'_, f32, R> + where + R: FailingStrategy, + { + fn is_close_to_with_margin(self, expected: f32, margin: impl Into) -> Self { + self.expecting(IsCloseTo::new(expected).within_margin(margin)) + } + + fn is_not_close_to_with_margin(self, expected: f32, margin: impl Into) -> Self { + self.expecting(IsNotCloseTo::new(expected).within_margin(margin)) + } + } + + impl AssertIsCloseToWithDefaultMargin for Spec<'_, f64, R> + where + R: FailingStrategy, + { + fn is_close_to(self, expected: f64) -> Self { + self.expecting( + IsCloseTo::<_, F64Margin>::new(expected).within_margin((4. * f64::EPSILON, 4)), + ) + } + + fn is_not_close_to(self, expected: f64) -> Self { + self.expecting( + IsNotCloseTo::<_, F64Margin>::new(expected).within_margin((4. * f64::EPSILON, 4)), + ) + } + } + + impl AssertIsCloseToWithinMargin for Spec<'_, f64, R> + where + R: FailingStrategy, + { + fn is_close_to_with_margin(self, expected: f64, margin: impl Into) -> Self { + self.expecting(IsCloseTo::new(expected).within_margin(margin)) + } + + fn is_not_close_to_with_margin(self, expected: f64, margin: impl Into) -> Self { + self.expecting(IsNotCloseTo::new(expected).within_margin(margin)) + } + } + + impl Expectation for IsCloseTo { + fn test(&mut self, subject: &f32) -> bool { + subject.approx_eq(self.expected, self.margin) + } + + fn message(&self, expression: Expression<'_>, actual: &f32, format: &DiffFormat) -> String { + let (marked_actual, marked_expected) = mark_diff(actual, &self.expected, format); + format!("expected {expression} is close to {:?}\n within a margin of epsilon={:e} and ulps={}\n but was: {marked_actual}\n expected: {marked_expected}", + &self.expected, self.margin.epsilon, self.margin.ulps + ) + } + } + + impl Expectation for IsNotCloseTo { + fn test(&mut self, subject: &f32) -> bool { + !subject.approx_eq(self.expected, self.margin) + } + + fn message( + &self, + expression: Expression<'_>, + actual: &f32, + _format: &DiffFormat, + ) -> String { + format!("expected {expression} is not close to {:?}\n within a margin of epsilon={:e} and ulps={}\n but was: {actual:?}\n expected: {:?}", + &self.expected, self.margin.epsilon, self.margin.ulps, &self.expected + ) + } + } + + impl Expectation for IsCloseTo { + fn test(&mut self, subject: &f64) -> bool { + subject.approx_eq(self.expected, self.margin) + } + + fn message(&self, expression: Expression<'_>, actual: &f64, format: &DiffFormat) -> String { + let (marked_actual, marked_expected) = mark_diff(actual, &self.expected, format); + format!("expected {expression} is close to {:?}\n within a margin of epsilon={:e} and ulps={}\n but was: {marked_actual}\n expected: {marked_expected}", + &self.expected, self.margin.epsilon, self.margin.ulps + ) + } + } + + impl Expectation for IsNotCloseTo { + fn test(&mut self, subject: &f64) -> bool { + !subject.approx_eq(self.expected, self.margin) + } + + fn message( + &self, + expression: Expression<'_>, + actual: &f64, + _format: &DiffFormat, + ) -> String { + format!("expected {expression} is not close to {:?}\n within a margin of epsilon={:e} and ulps={}\n but was: {actual:?}\n expected: {:?}", + &self.expected, self.margin.epsilon, self.margin.ulps, &self.expected + ) + } } } diff --git a/src/float/tests.rs b/src/float/tests.rs index eeed5d4..816f75d 100644 --- a/src/float/tests.rs +++ b/src/float/tests.rs @@ -3,216 +3,637 @@ use crate::prelude::*; #[test] -fn f32_is_close_to_another_f32_within_default_margin() { - assert_that(6.28_f32 / 2.).is_close_to(3.14); +fn f32_is_negative() { + assert_that(-4.2_f32).is_negative(); + assert_that(-0.001_f32).is_negative(); } #[test] -fn verify_f32_is_close_to_another_f32_within_default_margin_fails() { - let failures = verify_that(6.28_f32 / 2.) - .named("tau / 2") - .is_close_to(3.15) +fn f64_is_negative() { + assert_that(-4.2_f64).is_negative(); + assert_that(-0.001_f64).is_negative(); +} + +#[test] +fn borrowed_f64_is_negative() { + assert_that!(&-4.2_f64).is_negative(); + assert_that!(&-0.001_f64).is_negative(); +} + +#[test] +fn mutable_borrowed_f64_is_negative() { + assert_that!(&mut -4.2_f64).is_negative(); + assert_that!(&mut -0.001_f64).is_negative(); +} + +#[test] +fn verify_f64_is_negative_fails() { + let failures = verify_that(0.0_f64) + .named("some_number") + .is_negative() .display_failures(); assert_eq!( failures, - &[r"assertion failed: expected tau / 2 is close to 3.15 - within a margin of epsilon=4.7683716e-7 and ulps=4 - but was: 3.14 - expected: 3.15 + &[r"assertion failed: expected some_number is negative + but was: 0.0 + expected: < 0 "] ); } #[test] -fn f32_is_not_close_to_another_f32_within_default_margin() { - assert_that(6.28_f32 / 2.).is_not_close_to(3.15); +fn f32_is_not_negative() { + assert_that(4.2_f32).is_not_negative(); + assert_that(0.001_f32).is_not_negative(); + assert_that(0.0_f32).is_not_negative(); +} + +#[test] +fn f64_is_not_negative() { + assert_that(4.2_f64).is_not_negative(); + assert_that(0.001_f64).is_not_negative(); + assert_that(0.0_f64).is_not_negative(); +} + +#[test] +fn borrowed_f64_is_not_negative() { + assert_that!(&4.2_f64).is_not_negative(); + assert_that!(&0.001_f64).is_not_negative(); + assert_that!(&0.0_f64).is_not_negative(); +} + +#[test] +fn mutable_borrowed_f64_is_not_negative() { + assert_that!(&mut 4.2_f64).is_not_negative(); + assert_that!(&mut 0.001_f64).is_not_negative(); + assert_that!(&mut 0.0_f64).is_not_negative(); } #[test] -fn verify_f32_is_not_close_to_another_f32_within_default_margin_fails() { - let failures = verify_that(6.28_f32 / 2.) - .named("tau / 2") - .is_not_close_to(3.14) +fn verify_f64_is_not_negative_fails() { + let failures = verify_that(-0.001_f64) + .named("some_number") + .is_not_negative() .display_failures(); assert_eq!( failures, - &[r"assertion failed: expected tau / 2 is not close to 3.14 - within a margin of epsilon=4.7683716e-7 and ulps=4 - but was: 3.14 - expected: 3.14 + &[r"assertion failed: expected some_number is not negative + but was: -0.001 + expected: >= 0 "] ); } #[test] -fn f32_is_close_to_another_f32_within_given_margin() { - assert_that(6.28_f32 / 2.).is_close_to_with_margin(3.14, (2. * f32::EPSILON, 3)); +fn f32_is_positive() { + assert_that(4.2_f32).is_positive(); + assert_that(0.001_f32).is_positive(); +} + +#[test] +fn f64_is_positive() { + assert_that(4.2_f64).is_positive(); + assert_that(0.001_f64).is_positive(); +} + +#[test] +fn borrowed_f64_is_positive() { + assert_that!(&4.2_f64).is_positive(); + assert_that!(&0.001_f64).is_positive(); +} + +#[test] +fn mutable_borrowed_f64_is_positive() { + assert_that!(&mut 4.2_f64).is_positive(); + assert_that!(&mut 0.001_f64).is_positive(); } #[test] -fn verify_f32_is_close_to_another_f32_within_given_margin_fails() { - let failures = verify_that(6.28_f32 / 2.) - .named("tau / 2") - .is_close_to_with_margin(3.15, (2. * f32::EPSILON, 3)) +fn verify_f64_is_positive_fails() { + let failures = verify_that(0.0_f64) + .named("some_number") + .is_positive() .display_failures(); assert_eq!( failures, - &[r"assertion failed: expected tau / 2 is close to 3.15 - within a margin of epsilon=2.3841858e-7 and ulps=3 - but was: 3.14 - expected: 3.15 + &[r"assertion failed: expected some_number is positive + but was: 0.0 + expected: > 0 "] ); } #[test] -fn f32_is_not_close_to_another_f32_within_given_margin() { - assert_that(6.28_f32 / 2.).is_not_close_to_with_margin(3.15, (2. * f32::EPSILON, 3)); +fn f32_is_not_positive() { + assert_that(-4.2_f32).is_not_positive(); + assert_that(-0.001_f32).is_not_positive(); + assert_that(0.0_f32).is_not_positive(); +} + +#[test] +fn f64_is_not_positive() { + assert_that(-4.2_f64).is_not_positive(); + assert_that(-0.001_f64).is_not_positive(); + assert_that(0.0_f64).is_not_positive(); +} + +#[test] +fn borrowed_f64_is_not_positive() { + assert_that!(&-4.2_f64).is_not_positive(); + assert_that!(&-0.001_f64).is_not_positive(); + assert_that!(&0.0_f64).is_not_positive(); +} + +#[test] +fn mutable_borrowed_f64_is_not_positive() { + assert_that!(&mut -4.2_f64).is_not_positive(); + assert_that!(&mut -0.001_f64).is_not_positive(); + assert_that!(&mut 0.0_f64).is_not_positive(); } #[test] -fn verify_f32_is_not_close_to_another_f32_within_given_margin_fails() { - let failures = verify_that(6.28_f32 / 2.) - .named("tau / 2") - .is_not_close_to_with_margin(3.14, (2. * f32::EPSILON, 3)) +fn verify_f64_is_not_positive_fails() { + let failures = verify_that(0.001_f64) + .named("some_number") + .is_not_positive() .display_failures(); assert_eq!( failures, - &[r"assertion failed: expected tau / 2 is not close to 3.14 - within a margin of epsilon=2.3841858e-7 and ulps=3 - but was: 3.14 - expected: 3.14 + &[r"assertion failed: expected some_number is not positive + but was: 0.001 + expected: <= 0 "] ); } #[test] -fn f64_is_close_to_another_f64_within_default_margin() { - assert_that(6.28_f64 / 2.).is_close_to(3.14); +fn f32_is_zero() { + assert_that(0.0_f32).is_zero(); } #[test] -fn verify_f64_is_close_to_another_f64_within_default_margin_fails() { - let failures = verify_that(6.28_f64 / 2.) - .named("tau / 2") - .is_close_to(3.15) +fn f64_is_zero() { + assert_that(0.0_f64).is_zero(); +} + +#[test] +fn verify_f64_is_zero_fails() { + let failures = verify_that(1.0_f64) + .named("some_number") + .is_zero() .display_failures(); assert_eq!( failures, - &[r"assertion failed: expected tau / 2 is close to 3.15 - within a margin of epsilon=8.881784197001252e-16 and ulps=4 - but was: 3.14 - expected: 3.15 + &[r"assertion failed: expected some_number is zero + but was: 1.0 + expected: 0.0 "] ); } #[test] -fn f64_is_not_close_to_another_f64_within_default_margin() { - assert_that(6.28_f64 / 2.).is_not_close_to(3.15); +fn f32_is_one() { + assert_that(1.0_f32).is_one(); +} + +#[test] +fn f64_is_one() { + assert_that(1.0_f64).is_one(); } #[test] -fn verify_f64_is_not_close_to_another_f64_within_default_margin_fails() { - let failures = verify_that(6.28_f64 / 2.) - .named("tau / 2") - .is_not_close_to(3.14) +fn verify_f64_is_one_fails() { + let failures = verify_that(0.0_f64) + .named("some_number") + .is_one() .display_failures(); assert_eq!( failures, - &[r"assertion failed: expected tau / 2 is not close to 3.14 - within a margin of epsilon=8.881784197001252e-16 and ulps=4 - but was: 3.14 - expected: 3.14 + &[r"assertion failed: expected some_number is one + but was: 0.0 + expected: 1.0 "] ); } #[test] -fn f64_is_close_to_another_f64_within_given_margin() { - assert_that(6.28_f64 / 2.).is_close_to_with_margin(3.14, (2. * f64::EPSILON, 3)); +fn f32_is_finite() { + assert_that(0.123_f32).is_finite(); + assert_that(0.0_f32).is_finite(); + assert_that(-0.123_f32).is_finite(); } #[test] -fn verify_f64_is_close_to_another_f64_within_given_margin_fails() { - let failures = verify_that(6.28_f64 / 2.) - .named("tau / 2") - .is_close_to_with_margin(3.15, (2. * f64::EPSILON, 3)) +fn f32_is_infinite() { + assert_that(f32::INFINITY).is_infinite(); + assert_that(f32::NEG_INFINITY).is_infinite(); +} + +#[test] +fn f64_is_finite() { + assert_that(0.123_f64).is_finite(); + assert_that(0.0_f64).is_finite(); + assert_that(-0.123_f64).is_finite(); +} + +#[test] +fn borrowed_f64_is_finite() { + assert_that!(&0.123_f64).is_finite(); + assert_that!(&0.0_f64).is_finite(); + assert_that!(&-0.123_f64).is_finite(); +} + +#[test] +fn mutable_borrowed_f64_is_finite() { + assert_that!(&mut 0.123_f64).is_finite(); + assert_that!(&mut 0.0_f64).is_finite(); + assert_that!(&mut -0.123_f64).is_finite(); +} + +#[test] +fn verify_f64_is_finite_fails() { + let failures = verify_that(f64::NEG_INFINITY) + .named("some_number") + .is_finite() .display_failures(); assert_eq!( failures, - &[r"assertion failed: expected tau / 2 is close to 3.15 - within a margin of epsilon=4.440892098500626e-16 and ulps=3 - but was: 3.14 - expected: 3.15 + &[r"assertion failed: expected some_number is finite + but was: -inf + expected: a finite number "] ); } #[test] -fn f64_is_not_close_to_another_f64_within_given_margin() { - assert_that(6.28_f64 / 2.).is_not_close_to_with_margin(3.15, (2. * f64::EPSILON, 3)); +fn f64_is_infinite() { + assert_that(f64::INFINITY).is_infinite(); + assert_that(f64::NEG_INFINITY).is_infinite(); +} + +#[test] +fn borrowed_f64_is_infinite() { + assert_that!(&f64::INFINITY).is_infinite(); + assert_that!(&f64::NEG_INFINITY).is_infinite(); +} + +#[allow(const_item_mutation)] +#[test] +fn mutable_borrowed_f64_is_infinite() { + assert_that!(&mut f64::INFINITY).is_infinite(); + assert_that!(&mut f64::NEG_INFINITY).is_infinite(); } #[test] -fn verify_f64_is_not_close_to_another_f64_within_given_margin_fails() { - let failures = verify_that(6.28_f64 / 2.) - .named("tau / 2") - .is_not_close_to_with_margin(3.14, (2. * f64::EPSILON, 3)) +fn verify_f64_is_infinite_fails() { + let failures = verify_that(0.0_f64) + .named("some_number") + .is_infinite() .display_failures(); assert_eq!( failures, - &[r"assertion failed: expected tau / 2 is not close to 3.14 - within a margin of epsilon=4.440892098500626e-16 and ulps=3 - but was: 3.14 - expected: 3.14 + &[r"assertion failed: expected some_number is infinite + but was: 0.0 + expected: an infinite number "] ); } -#[cfg(feature = "colored")] -mod colored { +#[test] +fn f32_is_a_number() { + assert_that(0.1_f32).is_a_number(); + assert_that(0.0_f32).is_a_number(); + assert_that(-0.1_f32).is_a_number(); + assert_that(-0.1_f32).is_a_number(); + assert_that(f32::INFINITY).is_a_number(); + assert_that(f32::NEG_INFINITY).is_a_number(); +} + +#[test] +fn f64_is_a_number() { + assert_that(0.1_f64).is_a_number(); + assert_that(0.0_f64).is_a_number(); + assert_that(-0.1_f64).is_a_number(); + assert_that(f64::INFINITY).is_a_number(); + assert_that(f64::NEG_INFINITY).is_a_number(); +} + +#[test] +fn borrowed_f64_is_a_number() { + assert_that(&0.1_f64).is_a_number(); + assert_that(&0.0_f64).is_a_number(); + assert_that(&-0.1_f64).is_a_number(); + assert_that(&f64::INFINITY).is_a_number(); + assert_that(&f64::NEG_INFINITY).is_a_number(); +} + +#[allow(const_item_mutation)] +#[test] +fn mutable_borrowed_f64_is_a_number() { + assert_that(&mut 0.1_f64).is_a_number(); + assert_that(&mut 0.0_f64).is_a_number(); + assert_that(&mut -0.1_f64).is_a_number(); + assert_that(&mut f64::INFINITY).is_a_number(); + assert_that(&mut f64::NEG_INFINITY).is_a_number(); +} + +#[test] +fn verify_f64_is_a_number_fails() { + let failures = verify_that(f64::NAN) + .named("some_number") + .is_a_number() + .display_failures(); + + assert_eq!( + failures, + &[r"assertion failed: expected some_number is a number + but was: NaN + expected: a number +"] + ); +} + +#[test] +fn f32_is_not_a_number() { + assert_that(f32::NAN).is_not_a_number(); +} + +#[test] +fn verify_f32_is_not_a_number_fails() { + let failures = verify_that(0.0_f32) + .named("some_number") + .is_not_a_number() + .display_failures(); + + assert_eq!( + failures, + &[ + r"assertion failed: expected some_number is not a number (NaN) + but was: 0.0 + expected: NaN +" + ] + ); +} + +#[test] +fn f64_is_not_a_number() { + assert_that(f64::NAN).is_not_a_number(); +} + +#[test] +fn borrowed_f64_is_not_a_number() { + assert_that(&f64::NAN).is_not_a_number(); +} + +#[allow(const_item_mutation)] +#[test] +fn mutable_borrowed_f64_is_not_a_number() { + assert_that(&mut f64::NAN).is_not_a_number(); +} + +#[test] +fn verify_f64_is_not_a_number_fails() { + let failures = verify_that(0.0_f64) + .named("some_number") + .is_not_a_number() + .display_failures(); + + assert_eq!( + failures, + &[ + r"assertion failed: expected some_number is not a number (NaN) + but was: 0.0 + expected: NaN +" + ] + ); +} + +#[cfg(feature = "float")] +mod cmp { use crate::prelude::*; #[test] - fn highlight_diffs_f32_is_close_to() { - let failures = verify_that(6.28318_f32 / 2.) - .with_diff_format(DIFF_FORMAT_RED_BLUE) - .is_close_to_with_margin(3.15148, (2. * f32::EPSILON, 3)) + fn f32_is_close_to_another_f32_within_default_margin() { + assert_that(6.28_f32 / 2.).is_close_to(3.14); + } + + #[test] + fn verify_f32_is_close_to_another_f32_within_default_margin_fails() { + let failures = verify_that(6.28_f32 / 2.) + .named("tau / 2") + .is_close_to(3.15) .display_failures(); assert_eq!( failures, - &["assertion failed: expected subject is close to 3.15148\n \ - within a margin of epsilon=2.3841858e-7 and ulps=3\n \ - but was: 3.1\u{1b}[31m41\u{1b}[0m5\u{1b}[31m9\u{1b}[0m\n \ - expected: 3.15\u{1b}[34m148\u{1b}[0m\n\ - "] + &[r"assertion failed: expected tau / 2 is close to 3.15 + within a margin of epsilon=4.7683716e-7 and ulps=4 + but was: 3.14 + expected: 3.15 +"] + ); + } + + #[test] + fn f32_is_not_close_to_another_f32_within_default_margin() { + assert_that(6.28_f32 / 2.).is_not_close_to(3.15); + } + + #[test] + fn verify_f32_is_not_close_to_another_f32_within_default_margin_fails() { + let failures = verify_that(6.28_f32 / 2.) + .named("tau / 2") + .is_not_close_to(3.14) + .display_failures(); + + assert_eq!( + failures, + &[r"assertion failed: expected tau / 2 is not close to 3.14 + within a margin of epsilon=4.7683716e-7 and ulps=4 + but was: 3.14 + expected: 3.14 +"] + ); + } + + #[test] + fn f32_is_close_to_another_f32_within_given_margin() { + assert_that(6.28_f32 / 2.).is_close_to_with_margin(3.14, (2. * f32::EPSILON, 3)); + } + + #[test] + fn verify_f32_is_close_to_another_f32_within_given_margin_fails() { + let failures = verify_that(6.28_f32 / 2.) + .named("tau / 2") + .is_close_to_with_margin(3.15, (2. * f32::EPSILON, 3)) + .display_failures(); + + assert_eq!( + failures, + &[r"assertion failed: expected tau / 2 is close to 3.15 + within a margin of epsilon=2.3841858e-7 and ulps=3 + but was: 3.14 + expected: 3.15 +"] + ); + } + + #[test] + fn f32_is_not_close_to_another_f32_within_given_margin() { + assert_that(6.28_f32 / 2.).is_not_close_to_with_margin(3.15, (2. * f32::EPSILON, 3)); + } + + #[test] + fn verify_f32_is_not_close_to_another_f32_within_given_margin_fails() { + let failures = verify_that(6.28_f32 / 2.) + .named("tau / 2") + .is_not_close_to_with_margin(3.14, (2. * f32::EPSILON, 3)) + .display_failures(); + + assert_eq!( + failures, + &[r"assertion failed: expected tau / 2 is not close to 3.14 + within a margin of epsilon=2.3841858e-7 and ulps=3 + but was: 3.14 + expected: 3.14 +"] + ); + } + + #[test] + fn f64_is_close_to_another_f64_within_default_margin() { + assert_that(6.28_f64 / 2.).is_close_to(3.14); + } + + #[test] + fn verify_f64_is_close_to_another_f64_within_default_margin_fails() { + let failures = verify_that(6.28_f64 / 2.) + .named("tau / 2") + .is_close_to(3.15) + .display_failures(); + + assert_eq!( + failures, + &[r"assertion failed: expected tau / 2 is close to 3.15 + within a margin of epsilon=8.881784197001252e-16 and ulps=4 + but was: 3.14 + expected: 3.15 +"] + ); + } + + #[test] + fn f64_is_not_close_to_another_f64_within_default_margin() { + assert_that(6.28_f64 / 2.).is_not_close_to(3.15); + } + + #[test] + fn verify_f64_is_not_close_to_another_f64_within_default_margin_fails() { + let failures = verify_that(6.28_f64 / 2.) + .named("tau / 2") + .is_not_close_to(3.14) + .display_failures(); + + assert_eq!( + failures, + &[r"assertion failed: expected tau / 2 is not close to 3.14 + within a margin of epsilon=8.881784197001252e-16 and ulps=4 + but was: 3.14 + expected: 3.14 +"] ); } #[test] - fn highlight_diffs_f64_is_close_to() { - let failures = verify_that(6.28318_f64 / 2.) - .with_diff_format(DIFF_FORMAT_RED_BLUE) - .is_close_to_with_margin(3.15148, (2. * f64::EPSILON, 3)) + fn f64_is_close_to_another_f64_within_given_margin() { + assert_that(6.28_f64 / 2.).is_close_to_with_margin(3.14, (2. * f64::EPSILON, 3)); + } + + #[test] + fn verify_f64_is_close_to_another_f64_within_given_margin_fails() { + let failures = verify_that(6.28_f64 / 2.) + .named("tau / 2") + .is_close_to_with_margin(3.15, (2. * f64::EPSILON, 3)) .display_failures(); assert_eq!( failures, - &["assertion failed: expected subject is close to 3.15148\n \ + &[r"assertion failed: expected tau / 2 is close to 3.15 + within a margin of epsilon=4.440892098500626e-16 and ulps=3 + but was: 3.14 + expected: 3.15 +"] + ); + } + + #[test] + fn f64_is_not_close_to_another_f64_within_given_margin() { + assert_that(6.28_f64 / 2.).is_not_close_to_with_margin(3.15, (2. * f64::EPSILON, 3)); + } + + #[test] + fn verify_f64_is_not_close_to_another_f64_within_given_margin_fails() { + let failures = verify_that(6.28_f64 / 2.) + .named("tau / 2") + .is_not_close_to_with_margin(3.14, (2. * f64::EPSILON, 3)) + .display_failures(); + + assert_eq!( + failures, + &[r"assertion failed: expected tau / 2 is not close to 3.14 + within a margin of epsilon=4.440892098500626e-16 and ulps=3 + but was: 3.14 + expected: 3.14 +"] + ); + } + + #[cfg(feature = "colored")] + mod colored { + use crate::prelude::*; + + #[test] + fn highlight_diffs_f32_is_close_to() { + let failures = verify_that(6.28318_f32 / 2.) + .with_diff_format(DIFF_FORMAT_RED_BLUE) + .is_close_to_with_margin(3.15148, (2. * f32::EPSILON, 3)) + .display_failures(); + + assert_eq!( + failures, + &["assertion failed: expected subject is close to 3.15148\n \ + within a margin of epsilon=2.3841858e-7 and ulps=3\n \ + but was: 3.1\u{1b}[31m41\u{1b}[0m5\u{1b}[31m9\u{1b}[0m\n \ + expected: 3.15\u{1b}[34m148\u{1b}[0m\n\ + "] + ); + } + + #[test] + fn highlight_diffs_f64_is_close_to() { + let failures = verify_that(6.28318_f64 / 2.) + .with_diff_format(DIFF_FORMAT_RED_BLUE) + .is_close_to_with_margin(3.15148, (2. * f64::EPSILON, 3)) + .display_failures(); + + assert_eq!( + failures, + &["assertion failed: expected subject is close to 3.15148\n \ within a margin of epsilon=4.440892098500626e-16 and ulps=3\n \ but was: 3.1\u{1b}[31m41\u{1b}[0m5\u{1b}[31m9\u{1b}[0m\n \ expected: 3.15\u{1b}[34m148\u{1b}[0m\n\ "] - ); + ); + } } } diff --git a/src/integer/mod.rs b/src/integer/mod.rs index 20f6649..c542f17 100644 --- a/src/integer/mod.rs +++ b/src/integer/mod.rs @@ -1,4 +1,71 @@ //! Implementation of assertions for integer values. +use crate::properties::{AdditiveIdentityProperty, MultiplicativeIdentityProperty, SignumProperty}; + +macro_rules! impl_signum_property { + ($type:ty) => { + impl SignumProperty for $type { + fn is_negative_property(&self) -> bool { + self.is_negative() + } + + fn is_positive_property(&self) -> bool { + self.is_positive() + } + } + }; +} + +impl_signum_property!(i8); +impl_signum_property!(i16); +impl_signum_property!(i32); +impl_signum_property!(i64); +impl_signum_property!(i128); +impl_signum_property!(isize); + +macro_rules! impl_additive_identity_property { + ($type:ty) => { + impl AdditiveIdentityProperty for $type { + const ADDITIVE_IDENTITY: Self = 0; + } + }; +} + +impl_additive_identity_property!(i8); +impl_additive_identity_property!(i16); +impl_additive_identity_property!(i32); +impl_additive_identity_property!(i64); +impl_additive_identity_property!(i128); +impl_additive_identity_property!(isize); + +impl_additive_identity_property!(u8); +impl_additive_identity_property!(u16); +impl_additive_identity_property!(u32); +impl_additive_identity_property!(u64); +impl_additive_identity_property!(u128); +impl_additive_identity_property!(usize); + +macro_rules! impl_multiplicative_identity_property { + ($type:ty) => { + impl MultiplicativeIdentityProperty for $type { + const MULTIPLICATIVE_IDENTITY: Self = 1; + } + }; +} + +impl_multiplicative_identity_property!(i8); +impl_multiplicative_identity_property!(i16); +impl_multiplicative_identity_property!(i32); +impl_multiplicative_identity_property!(i64); +impl_multiplicative_identity_property!(i128); +impl_multiplicative_identity_property!(isize); + +impl_multiplicative_identity_property!(u8); +impl_multiplicative_identity_property!(u16); +impl_multiplicative_identity_property!(u32); +impl_multiplicative_identity_property!(u64); +impl_multiplicative_identity_property!(u128); +impl_multiplicative_identity_property!(usize); + #[cfg(test)] mod tests; diff --git a/src/integer/tests.rs b/src/integer/tests.rs index 329537f..479dedd 100644 --- a/src/integer/tests.rs +++ b/src/integer/tests.rs @@ -44,6 +44,430 @@ fn verify_i32_is_equal_to_i32_fails() { ); } +#[test] +fn i8_is_negative() { + assert_that(-42_i8).is_negative(); + assert_that(-1_i8).is_negative(); +} + +#[test] +fn i16_is_negative() { + assert_that(-42_i16).is_negative(); + assert_that(-1_i16).is_negative(); +} + +#[test] +fn i32_is_negative() { + assert_that(-42_i32).is_negative(); + assert_that(-1_i32).is_negative(); +} + +#[test] +fn i64_is_negative() { + assert_that(-42_i64).is_negative(); + assert_that(-1_i64).is_negative(); +} + +#[test] +fn i128_is_negative() { + assert_that(-42_i128).is_negative(); + assert_that(-1_i128).is_negative(); +} + +#[test] +fn isize_is_negative() { + assert_that(-42_isize).is_negative(); + assert_that(-1_isize).is_negative(); +} + +#[test] +fn borrowed_isize_is_negative() { + assert_that(&-42_isize).is_negative(); + assert_that(&-1_isize).is_negative(); +} + +#[test] +fn mutable_borrowed_isize_is_negative() { + assert_that(&mut -42_isize).is_negative(); + assert_that(&mut -1_isize).is_negative(); +} + +#[test] +fn verify_i32_is_negative_fails() { + let failures = verify_that(0_i32) + .named("some_number") + .is_negative() + .display_failures(); + + assert_eq!( + failures, + &[r"assertion failed: expected some_number is negative + but was: 0 + expected: < 0 +"] + ); +} + +#[test] +fn i8_is_not_negative() { + assert_that(42_i8).is_not_negative(); + assert_that(1_i8).is_not_negative(); + assert_that(0_i8).is_not_negative(); +} + +#[test] +fn i16_is_not_negative() { + assert_that(42_i16).is_not_negative(); + assert_that(1_i16).is_not_negative(); + assert_that(0_i16).is_not_negative(); +} + +#[test] +fn i32_is_not_negative() { + assert_that(42_i32).is_not_negative(); + assert_that(1_i32).is_not_negative(); + assert_that(0_i32).is_not_negative(); +} + +#[test] +fn i64_is_not_negative() { + assert_that(42_i64).is_not_negative(); + assert_that(1_i64).is_not_negative(); + assert_that(0_i64).is_not_negative(); +} + +#[test] +fn i128_is_not_negative() { + assert_that(42_i128).is_not_negative(); + assert_that(1_i128).is_not_negative(); + assert_that(0_i128).is_not_negative(); +} + +#[test] +fn isize_is_not_negative() { + assert_that(42_isize).is_not_negative(); + assert_that(1_isize).is_not_negative(); + assert_that(0_isize).is_not_negative(); +} + +#[test] +fn borrowed_isize_is_not_negative() { + assert_that(&42_isize).is_not_negative(); + assert_that(&1_isize).is_not_negative(); + assert_that(&0_isize).is_not_negative(); +} + +#[test] +fn mutable_borrowed_isize_is_not_negative() { + assert_that(&mut 42_isize).is_not_negative(); + assert_that(&mut 1_isize).is_not_negative(); + assert_that(&mut 0_isize).is_not_negative(); +} + +#[test] +fn verify_i32_is_not_negative_fails() { + let failures = verify_that(-1_i32) + .named("some_number") + .is_not_negative() + .display_failures(); + + assert_eq!( + failures, + &[r"assertion failed: expected some_number is not negative + but was: -1 + expected: >= 0 +"] + ); +} + +#[test] +fn i8_is_positive() { + assert_that(42_i8).is_positive(); + assert_that(1_i8).is_positive(); +} + +#[test] +fn i16_is_positive() { + assert_that(42_i16).is_positive(); + assert_that(1_i16).is_positive(); +} + +#[test] +fn i32_is_positive() { + assert_that(42_i32).is_positive(); + assert_that(1_i32).is_positive(); +} + +#[test] +fn i64_is_positive() { + assert_that(42_i64).is_positive(); + assert_that(1_i64).is_positive(); +} + +#[test] +fn i128_is_positive() { + assert_that(42_i128).is_positive(); + assert_that(1_i128).is_positive(); +} + +#[test] +fn isize_is_positive() { + assert_that(42_isize).is_positive(); + assert_that(1_isize).is_positive(); +} + +#[test] +fn borrowed_isize_is_positive() { + assert_that(&42_isize).is_positive(); + assert_that(&1_isize).is_positive(); +} + +#[test] +fn mutable_borrowed_isize_is_positive() { + assert_that(&mut 42_isize).is_positive(); + assert_that(&mut 1_isize).is_positive(); +} + +#[test] +fn verify_i32_is_positive_fails() { + let failures = verify_that(0_i32) + .named("some_number") + .is_positive() + .display_failures(); + + assert_eq!( + failures, + &[r"assertion failed: expected some_number is positive + but was: 0 + expected: > 0 +"] + ); +} + +#[test] +fn i8_is_not_positive() { + assert_that(-42_i8).is_not_positive(); + assert_that(-1_i8).is_not_positive(); + assert_that(0_i8).is_not_positive(); +} + +#[test] +fn i16_is_not_positive() { + assert_that(-42_i16).is_not_positive(); + assert_that(-1_i16).is_not_positive(); + assert_that(0_i16).is_not_positive(); +} + +#[test] +fn i32_is_not_positive() { + assert_that(-42_i32).is_not_positive(); + assert_that(-1_i32).is_not_positive(); + assert_that(0_i32).is_not_positive(); +} + +#[test] +fn i64_is_not_positive() { + assert_that(-42_i64).is_not_positive(); + assert_that(-1_i64).is_not_positive(); + assert_that(0_i64).is_not_positive(); +} + +#[test] +fn i128_is_not_positive() { + assert_that(-42_i128).is_not_positive(); + assert_that(-1_i128).is_not_positive(); + assert_that(0_i128).is_not_positive(); +} + +#[test] +fn isize_is_not_positive() { + assert_that(-42_isize).is_not_positive(); + assert_that(-1_isize).is_not_positive(); + assert_that(0_isize).is_not_positive(); +} + +#[test] +fn borrowed_isize_is_not_positive() { + assert_that(&-42_isize).is_not_positive(); + assert_that(&-1_isize).is_not_positive(); + assert_that(&0_isize).is_not_positive(); +} + +#[test] +fn mutable_borrowed_isize_is_not_positive() { + assert_that(&mut -42_isize).is_not_positive(); + assert_that(&mut -1_isize).is_not_positive(); + assert_that(&mut 0_isize).is_not_positive(); +} + +#[test] +fn verify_i32_is_not_positive_fails() { + let failures = verify_that(1_i32) + .named("some_number") + .is_not_positive() + .display_failures(); + + assert_eq!( + failures, + &[r"assertion failed: expected some_number is not positive + but was: 1 + expected: <= 0 +"] + ); +} + +#[test] +fn i8_is_zero() { + assert_that(0_i8).is_zero(); +} + +#[test] +fn i16_is_zero() { + assert_that(0_i16).is_zero(); +} + +#[test] +fn i32_is_zero() { + assert_that(0_i32).is_zero(); +} + +#[test] +fn i64_is_zero() { + assert_that(0_i64).is_zero(); +} + +#[test] +fn i128_is_zero() { + assert_that(0_i128).is_zero(); +} + +#[test] +fn isize_is_zero() { + assert_that(0_isize).is_zero(); +} + +#[test] +fn u8_is_zero() { + assert_that(0_u8).is_zero(); +} + +#[test] +fn u16_is_zero() { + assert_that(0_u16).is_zero(); +} + +#[test] +fn u32_is_zero() { + assert_that(0_u32).is_zero(); +} + +#[test] +fn u64_is_zero() { + assert_that(0_u64).is_zero(); +} + +#[test] +fn u128_is_zero() { + assert_that(0_u128).is_zero(); +} + +#[test] +fn usize_is_zero() { + assert_that(0_usize).is_zero(); +} + +#[test] +fn verify_u64_is_zero_fails() { + let failures = verify_that(1_u64) + .named("some_number") + .is_zero() + .display_failures(); + + assert_eq!( + failures, + &[r"assertion failed: expected some_number is zero + but was: 1 + expected: 0 +"] + ); +} + +#[test] +fn i8_is_one() { + assert_that(1_i8).is_one(); +} + +#[test] +fn i16_is_one() { + assert_that(1_i16).is_one(); +} + +#[test] +fn i32_is_one() { + assert_that(1_i32).is_one(); +} + +#[test] +fn i64_is_one() { + assert_that(1_i64).is_one(); +} + +#[test] +fn i128_is_one() { + assert_that(1_i128).is_one(); +} + +#[test] +fn isize_is_one() { + assert_that(1_isize).is_one(); +} + +#[test] +fn u8_is_one() { + assert_that(1_u8).is_one(); +} + +#[test] +fn u16_is_one() { + assert_that(1_u16).is_one(); +} + +#[test] +fn u32_is_one() { + assert_that(1_u32).is_one(); +} + +#[test] +fn u64_is_one() { + assert_that(1_u64).is_one(); +} + +#[test] +fn u128_is_one() { + assert_that(1_u128).is_one(); +} + +#[test] +fn usize_is_one() { + assert_that(1_usize).is_one(); +} + +#[test] +fn verify_u64_is_one_fails() { + let failures = verify_that(0_u64) + .named("some_number") + .is_one() + .display_failures(); + + assert_eq!( + failures, + &[r"assertion failed: expected some_number is one + but was: 0 + expected: 1 +"] + ); +} + #[cfg(feature = "colored")] mod colored { use crate::prelude::*; diff --git a/src/lib.rs b/src/lib.rs index 8711f84..55b0035 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -621,9 +621,11 @@ mod c_string; mod char_count; mod collection; mod equality; +mod float; mod integer; mod iterator; mod length; +mod number; mod option; mod order; #[cfg(feature = "std")] @@ -635,9 +637,6 @@ mod slice; mod string; mod vec; -#[cfg(feature = "float")] -mod float; - #[cfg(feature = "panic")] mod panic; diff --git a/src/number.rs b/src/number.rs new file mode 100644 index 0000000..cd59be4 --- /dev/null +++ b/src/number.rs @@ -0,0 +1,230 @@ +//! Implementations of assertions specific for numbers. + +use crate::assertions::{AssertInfinity, AssertNotANumber, AssertNumericIdentity, AssertSignum}; +use crate::colored::{mark_missing, mark_missing_substr, mark_unexpected}; +use crate::expectations::{ + IsANumber, IsFinite, IsInfinite, IsNegative, IsNotANumber, IsNotNegative, IsNotPositive, IsOne, + IsPositive, IsZero, +}; +use crate::properties::{ + AdditiveIdentityProperty, InfinityProperty, IsNanProperty, MultiplicativeIdentityProperty, + SignumProperty, +}; +use crate::spec::{DiffFormat, Expectation, Expression, FailingStrategy, Spec}; +use crate::std::fmt::Debug; +use crate::std::format; +use crate::std::string::String; + +impl AssertSignum for Spec<'_, S, R> +where + S: SignumProperty + Debug, + R: FailingStrategy, +{ + fn is_negative(self) -> Self { + self.expecting(IsNegative) + } + + fn is_not_negative(self) -> Self { + self.expecting(IsNotNegative) + } + + fn is_positive(self) -> Self { + self.expecting(IsPositive) + } + + fn is_not_positive(self) -> Self { + self.expecting(IsNotPositive) + } +} + +impl Expectation for IsNegative +where + S: SignumProperty + Debug, +{ + fn test(&mut self, subject: &S) -> bool { + subject.is_negative_property() + } + + fn message(&self, expression: Expression<'_>, actual: &S, format: &DiffFormat) -> String { + let marked_actual = mark_unexpected(actual, format); + let marked_expected = mark_missing_substr("< 0", format); + format!("expected {expression} is negative\n but was: {marked_actual}\n expected: {marked_expected}") + } +} + +impl Expectation for IsNotNegative +where + S: SignumProperty + Debug, +{ + fn test(&mut self, subject: &S) -> bool { + !subject.is_negative_property() + } + + fn message(&self, expression: Expression<'_>, actual: &S, format: &DiffFormat) -> String { + let marked_actual = mark_unexpected(actual, format); + let marked_expected = mark_missing_substr(">= 0", format); + format!("expected {expression} is not negative\n but was: {marked_actual}\n expected: {marked_expected}") + } +} + +impl Expectation for IsPositive +where + S: SignumProperty + Debug, +{ + fn test(&mut self, subject: &S) -> bool { + subject.is_positive_property() + } + + fn message(&self, expression: Expression<'_>, actual: &S, format: &DiffFormat) -> String { + let marked_actual = mark_unexpected(actual, format); + let marked_expected = mark_missing_substr("> 0", format); + format!("expected {expression} is positive\n but was: {marked_actual}\n expected: {marked_expected}") + } +} + +impl Expectation for IsNotPositive +where + S: SignumProperty + Debug, +{ + fn test(&mut self, subject: &S) -> bool { + !subject.is_positive_property() + } + + fn message(&self, expression: Expression<'_>, actual: &S, format: &DiffFormat) -> String { + let marked_actual = mark_unexpected(actual, format); + let marked_expected = mark_missing_substr("<= 0", format); + format!("expected {expression} is not positive\n but was: {marked_actual}\n expected: {marked_expected}") + } +} + +impl AssertNumericIdentity for Spec<'_, S, R> +where + S: AdditiveIdentityProperty + MultiplicativeIdentityProperty + PartialEq + Debug, + R: FailingStrategy, +{ + fn is_zero(self) -> Self { + self.expecting(IsZero) + } + + fn is_one(self) -> Self { + self.expecting(IsOne) + } +} + +impl Expectation for IsZero +where + S: AdditiveIdentityProperty + PartialEq + Debug, +{ + fn test(&mut self, subject: &S) -> bool { + *subject == ::ADDITIVE_IDENTITY + } + + fn message(&self, expression: Expression<'_>, actual: &S, format: &DiffFormat) -> String { + let marked_actual = mark_unexpected(actual, format); + let marked_expected = mark_missing(&S::ADDITIVE_IDENTITY, format); + format!("expected {expression} is zero\n but was: {marked_actual}\n expected: {marked_expected}") + } +} + +impl Expectation for IsOne +where + S: MultiplicativeIdentityProperty + PartialEq + Debug, +{ + fn test(&mut self, subject: &S) -> bool { + *subject == ::MULTIPLICATIVE_IDENTITY + } + + fn message(&self, expression: Expression<'_>, actual: &S, format: &DiffFormat) -> String { + let marked_actual = mark_unexpected(actual, format); + let marked_expected = mark_missing(&S::MULTIPLICATIVE_IDENTITY, format); + format!("expected {expression} is one\n but was: {marked_actual}\n expected: {marked_expected}") + } +} + +impl AssertInfinity for Spec<'_, S, R> +where + S: InfinityProperty + Debug, + R: FailingStrategy, +{ + fn is_infinite(self) -> Self { + self.expecting(IsInfinite) + } + + fn is_finite(self) -> Self { + self.expecting(IsFinite) + } +} + +impl Expectation for IsFinite +where + S: InfinityProperty + Debug, +{ + fn test(&mut self, subject: &S) -> bool { + subject.is_finite_property() + } + + fn message(&self, expression: Expression<'_>, actual: &S, format: &DiffFormat) -> String { + let marked_actual = mark_unexpected(actual, format); + let marked_expected = mark_missing_substr("a finite number", format); + format!("expected {expression} is finite\n but was: {marked_actual}\n expected: {marked_expected}") + } +} + +impl Expectation for IsInfinite +where + S: InfinityProperty + Debug, +{ + fn test(&mut self, subject: &S) -> bool { + subject.is_infinite_property() + } + + fn message(&self, expression: Expression<'_>, actual: &S, format: &DiffFormat) -> String { + let marked_actual = mark_unexpected(actual, format); + let marked_expected = mark_missing_substr("an infinite number", format); + format!("expected {expression} is infinite\n but was: {marked_actual}\n expected: {marked_expected}") + } +} + +impl AssertNotANumber for Spec<'_, S, R> +where + S: IsNanProperty + Debug, + R: FailingStrategy, +{ + fn is_not_a_number(self) -> Self { + self.expecting(IsNotANumber) + } + + fn is_a_number(self) -> Self { + self.expecting(IsANumber) + } +} + +impl Expectation for IsANumber +where + S: IsNanProperty + Debug, +{ + fn test(&mut self, subject: &S) -> bool { + !subject.is_nan_property() + } + + fn message(&self, expression: Expression<'_>, actual: &S, format: &DiffFormat) -> String { + let marked_actual = mark_unexpected(actual, format); + let marked_expected = mark_missing_substr("a number", format); + format!("expected {expression} is a number\n but was: {marked_actual}\n expected: {marked_expected}") + } +} + +impl Expectation for IsNotANumber +where + S: IsNanProperty + Debug, +{ + fn test(&mut self, subject: &S) -> bool { + subject.is_nan_property() + } + + fn message(&self, expression: Expression<'_>, actual: &S, format: &DiffFormat) -> String { + let marked_actual = mark_unexpected(actual, format); + let marked_expected = mark_missing_substr("NaN", format); + format!("expected {expression} is not a number (NaN)\n but was: {marked_actual}\n expected: {marked_expected}") + } +} diff --git a/src/properties.rs b/src/properties.rs index 579344b..060562b 100644 --- a/src/properties.rs +++ b/src/properties.rs @@ -22,11 +22,12 @@ //! specifies that a collection's iterator yields the items in a well-defined //! order. -/// Any type that implements this trait provides access to its `is_empty` method -/// to be used by the implementation of the +/// The "empty" property of a collection-like type. +/// +/// This property is used by the implementation of the /// [`AsssertEmptiness`](crate::assertions::AssertEmptiness) assertions. pub trait IsEmptyProperty { - /// Provides access to the `is_empty` property. + /// Returns whether the collection-like value is empty. fn is_empty_property(&self) -> bool; } @@ -48,11 +49,15 @@ where } } -/// Any type that implements this trait provides access to its `len` method -/// to be used by the implementation of the +/// The length property of a collection-like type. +/// +/// Collection-like types are, for example, `Vec`, slice, array, `HashSet`, +/// `HashMap` and strings. +/// +/// This property is used by the implementation of the /// [`AssertHasLength`](crate::assertions::AssertHasLength) assertion. pub trait LengthProperty { - /// Provides access to the `len` property. + /// Returns the length of a collection-like value. fn length_property(&self) -> usize; } @@ -104,3 +109,110 @@ where ::char_count_property(self) } } + +/// The additive identity property of a numeric type. +pub trait AdditiveIdentityProperty { + /// The additive identity (zero). + const ADDITIVE_IDENTITY: Self; +} + +/// The multiplicative identity property of a numeric type. +pub trait MultiplicativeIdentityProperty { + /// The multiplicative identity (one). + const MULTIPLICATIVE_IDENTITY: Self; +} + +/// A property of numeric types that can have negative and positive values. +pub trait SignumProperty { + /// Returns whether this value is negative. + fn is_negative_property(&self) -> bool; + + /// Returns whether this value is positive. + fn is_positive_property(&self) -> bool; +} + +impl SignumProperty for &T +where + T: SignumProperty + ?Sized, +{ + fn is_negative_property(&self) -> bool { + ::is_negative_property(self) + } + + fn is_positive_property(&self) -> bool { + ::is_positive_property(self) + } +} + +impl SignumProperty for &mut T +where + T: SignumProperty + ?Sized, +{ + fn is_negative_property(&self) -> bool { + ::is_negative_property(self) + } + + fn is_positive_property(&self) -> bool { + ::is_positive_property(self) + } +} + +/// A property of floating point numbers that may have infinite or finite +/// values. +pub trait InfinityProperty { + /// Returns whether this value is infinite. + fn is_infinite_property(&self) -> bool; + + /// Returns whether this value is finite. + fn is_finite_property(&self) -> bool; +} + +impl InfinityProperty for &T +where + T: InfinityProperty + ?Sized, +{ + fn is_infinite_property(&self) -> bool { + ::is_infinite_property(self) + } + + fn is_finite_property(&self) -> bool { + ::is_finite_property(self) + } +} + +impl InfinityProperty for &mut T +where + T: InfinityProperty + ?Sized, +{ + fn is_infinite_property(&self) -> bool { + ::is_infinite_property(self) + } + + fn is_finite_property(&self) -> bool { + ::is_finite_property(self) + } +} + +/// The not-a-number property of floating point numbers. +pub trait IsNanProperty { + /// Returns whether this value is not a number. + fn is_nan_property(&self) -> bool; +} + +impl IsNanProperty for &T +where + T: IsNanProperty + ?Sized, +{ + fn is_nan_property(&self) -> bool { + ::is_nan_property(self) + } +} + +impl IsNanProperty for &mut T +where + T: IsNanProperty + ?Sized, +{ + fn is_nan_property(&self) -> bool { + ::is_nan_property(self) + } +} From deb01cacccd64b21070ff305438dfe88bd90e1cc Mon Sep 17 00:00:00 2001 From: haraldmaida Date: Sun, 20 Apr 2025 17:44:33 +0200 Subject: [PATCH 2/2] doc: list added assertions for integers and floats under available assertions in the README --- README.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f3a0951..76953c4 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,37 @@ being the type of the expected value. | is_in_range | verify that the subject is in the expected range (closed range) | | is_not_in_range | verify that the subject is not in the specified range (closed range) | -### Float +### Integer and Float + +for integer numbers of type `i8`, `i16`, `i32`, `i64`, `i128` and `isize` as well as
+floating point numbers of type `f32` and `f64`: + +| assertion | description | +|-----------------|------------------------------------------------------| +| is_negative | verify that the subject is a negative number | +| is_not_negative | verify that the subject is a positive number or zero | +| is_positive | verify that the subject is a positive number | +| is_not_positive | verify that the subject is a finite number | + +for integer numbers of type `i8`, `i16`, `i32`, `i64`, `i128`, `isize`, `u8`, `u16`, `u32`, `u64`, +`u128` and `usize` as well as
+floating point numbers of type `f32` and `f64`: + +| assertion | description | +|-----------|--------------------------------------------------------------| +| is_zero | verify that the subject is the additive identity (zero) | +| is_one | verify that the subject is the multiplicative identity (one) | + +for floating point numbers of type `f32` and `f64`: + +| assertion | description | +|-----------------|-----------------------------------------------| +| is_infinite | verify that the subject is an infinite number | +| is_finite | verify that the subject is a finite number | +| is_not_a_number | verify that the subject is not a number | +| is_a_number | verify that the subject is a number | + +### Float comparison for floating point numbers of type `f32` and `f64`.