assertr/assertions/num/
mod.rs

1use crate::AssertThat;
2use crate::mode::Mode;
3use crate::tracking::AssertionTracking;
4use core::fmt::Debug;
5use core::fmt::Write;
6use indoc::writedoc;
7use num::{Float, Num, Signed};
8
9/// Assertions for numeric values not already handled by
10/// [crate::prelude::PartialEqAssertions] and [crate::prelude::PartialOrdAssertions].
11pub trait NumAssertions<T: Num> {
12    /// Fails if actual is not equal to the additive identity.
13    fn is_zero(self) -> Self;
14
15    fn is_additive_identity(self) -> Self;
16
17    /// Fails if actual is not equal to the multiplicative identity.
18    fn is_one(self) -> Self;
19
20    fn is_multiplicative_identity(self) -> Self;
21
22    fn is_negative(self) -> Self
23    where
24        T: Signed;
25
26    fn is_positive(self) -> Self
27    where
28        T: Signed;
29
30    /// Fails if actual is not in the range
31    /// `[expected - allowed_deviation, expected + allowed_deviation]`.
32    fn is_close_to(self, expected: T, allowed_deviation: T) -> Self
33    where
34        T: PartialOrd,
35        T: Clone;
36
37    #[cfg(any(feature = "std", feature = "libm"))]
38    fn is_nan(self) -> Self
39    where
40        T: Float;
41
42    #[cfg(any(feature = "std", feature = "libm"))]
43    fn is_finite(self) -> Self
44    where
45        T: Float;
46
47    #[cfg(any(feature = "std", feature = "libm"))]
48    fn is_infinite(self) -> Self
49    where
50        T: Float;
51
52    // TODO: is_normal
53    // TODO: is_subnormal
54}
55
56impl<T: Num + Debug, M: Mode> NumAssertions<T> for AssertThat<'_, T, M> {
57    #[track_caller]
58    fn is_zero(self) -> Self {
59        self.track_assertion();
60        let actual = self.actual();
61        if !actual.is_zero() {
62            self.add_detail_message(format!(
63                "Expecting additive identity of type '{}'",
64                core::any::type_name::<T>()
65            ));
66            let expected = T::zero();
67            self.fail(|w: &mut String| {
68                writedoc! {w, r#"
69                    Expected: {expected:#?}
70
71                      Actual: {actual:#?}
72                "#}
73            });
74        }
75        self
76    }
77
78    #[track_caller]
79    fn is_additive_identity(self) -> Self {
80        self.is_zero()
81    }
82
83    #[track_caller]
84    fn is_one(self) -> Self {
85        self.track_assertion();
86        let actual = self.actual();
87        if !actual.is_one() {
88            self.add_detail_message(format!(
89                "Expecting multiplicative identity of type '{}'",
90                core::any::type_name::<T>()
91            ));
92            let expected = T::one();
93            self.fail(|w: &mut String| {
94                writedoc! {w, r#"
95                    Expected: {expected:#?}
96
97                      Actual: {actual:#?}
98                "#}
99            });
100        }
101        self
102    }
103
104    #[track_caller]
105    fn is_multiplicative_identity(self) -> Self {
106        self.is_one()
107    }
108
109    #[track_caller]
110    fn is_negative(self) -> Self
111    where
112        T: Signed,
113    {
114        self.track_assertion();
115        let actual = self.actual();
116        if !actual.is_negative() {
117            self.fail(|w: &mut String| {
118                writedoc! {w, r#"
119                    Expected value to be negative. But was
120
121                      Actual: {actual:#?}
122                "#}
123            });
124        }
125        self
126    }
127
128    #[track_caller]
129    fn is_positive(self) -> Self
130    where
131        T: Signed,
132    {
133        self.track_assertion();
134        let actual = self.actual();
135        if !actual.is_positive() {
136            self.fail(|w: &mut String| {
137                writedoc! {w, r#"
138                    Expected value to be positive. But was
139
140                      Actual: {actual:#?}
141                "#}
142            });
143        }
144        self
145    }
146
147    #[track_caller]
148    fn is_close_to(self, expected: T, allowed_deviation: T) -> Self
149    where
150        T: PartialOrd,
151        T: Clone,
152    {
153        self.track_assertion();
154        let actual = self.actual();
155        let min = expected.clone() - allowed_deviation.clone();
156        let max = expected.clone() + allowed_deviation.clone();
157        if !(actual >= &min && actual <= &max) {
158            self.fail(|w: &mut String| {
159                writedoc! {w, r#"
160                    Expected value to be close to: {expected:#?},
161                     with allowed deviation being: {allowed_deviation:#?},
162                      but value was outside range: [{min:?}, {max:?}]
163
164                      Actual: {actual:#?}
165                "#}
166            });
167        }
168        self
169    }
170
171    #[track_caller]
172    #[cfg(any(feature = "std", feature = "libm"))]
173    fn is_nan(self) -> Self
174    where
175        T: Float,
176    {
177        self.track_assertion();
178        let actual = self.actual();
179        if !actual.is_nan() {
180            let nan = T::nan();
181            self.fail(|w: &mut String| {
182                writedoc! {w, r#"
183                    Expected: {nan:#?}
184
185                      Actual: {actual:#?}
186                "#}
187            });
188        }
189        self
190    }
191
192    #[track_caller]
193    #[cfg(any(feature = "std", feature = "libm"))]
194    fn is_finite(self) -> Self
195    where
196        T: Float,
197    {
198        self.track_assertion();
199        let actual = self.actual();
200        if !actual.is_finite() {
201            self.fail(|w: &mut String| {
202                writedoc! {w, r#"
203                    Expected a finite value, but was
204
205                      Actual: {actual:#?}
206                "#}
207            });
208        }
209        self
210    }
211
212    #[track_caller]
213    #[cfg(any(feature = "std", feature = "libm"))]
214    fn is_infinite(self) -> Self
215    where
216        T: Float,
217    {
218        self.track_assertion();
219        let actual = self.actual();
220        if !actual.is_infinite() {
221            let inf = T::infinity();
222            self.fail(|w: &mut String| {
223                writedoc! {w, r#"
224                    Expected: +/- {inf:#?}
225
226                      Actual: {actual:#?}
227                "#}
228            });
229        }
230        self
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    #[test]
237    fn quick_type_check() {
238        use crate::prelude::*;
239        use ::num::Float;
240
241        assert_that(0u8).is_zero();
242        assert_that(0i8).is_zero();
243        assert_that(0u16).is_zero();
244        assert_that(0i16).is_zero();
245        assert_that(0u32).is_zero();
246        assert_that(0i32).is_zero();
247        assert_that(0u64).is_zero();
248        assert_that(0i64).is_zero();
249        assert_that(0u128).is_zero();
250        assert_that(0i128).is_zero();
251        assert_that(0.0f32).is_zero();
252        assert_that(0.0f64).is_zero();
253
254        assert_that(1u8).is_one();
255        assert_that(1i8).is_one();
256        assert_that(1u16).is_one();
257        assert_that(1i16).is_one();
258        assert_that(1u32).is_one();
259        assert_that(1i32).is_one();
260        assert_that(1u64).is_one();
261        assert_that(1i64).is_one();
262        assert_that(1u128).is_one();
263        assert_that(1i128).is_one();
264        assert_that(1.0f32).is_one();
265        assert_that(1.0f64).is_one();
266
267        assert_that(42u8).is_close_to(42, 0);
268        assert_that(42i8).is_close_to(42, 0);
269        assert_that(42u16).is_close_to(42, 0);
270        assert_that(42i16).is_close_to(42, 0);
271        assert_that(42u32).is_close_to(42, 0);
272        assert_that(42i32).is_close_to(42, 0);
273        assert_that(42u64).is_close_to(42, 0);
274        assert_that(42i64).is_close_to(42, 0);
275        assert_that(42u128).is_close_to(42, 0);
276        assert_that(42i128).is_close_to(42, 0);
277        assert_that(0.2f32 + 0.1f32).is_close_to(0.3, 0.0001);
278        assert_that(0.2f64 + 0.1f64).is_close_to(0.3, 0.0001);
279
280        assert_that(f32::nan()).is_nan();
281        assert_that(f64::nan()).is_nan();
282
283        assert_that(f32::infinity()).is_infinite();
284        assert_that(f64::infinity()).is_infinite();
285    }
286
287    mod is_zero {
288        use crate::prelude::*;
289        use indoc::formatdoc;
290
291        #[test]
292        fn succeeds_when_zero() {
293            assert_that(0).is_zero();
294        }
295
296        #[test]
297        fn panics_when_not_zero() {
298            assert_that_panic_by(|| assert_that(3).with_location(false).is_zero())
299                .has_type::<String>()
300                .is_equal_to(formatdoc! {r#"
301                    -------- assertr --------
302                    Expected: 0
303
304                      Actual: 3
305                    
306                    Details: [
307                        Expecting additive identity of type 'i32',
308                    ]
309                    -------- assertr --------
310                "#});
311        }
312    }
313
314    mod is_one {
315        use crate::prelude::*;
316        use indoc::formatdoc;
317
318        #[test]
319        fn succeeds_when_one() {
320            assert_that(1).is_one();
321        }
322
323        #[test]
324        fn panics_when_not_one() {
325            assert_that_panic_by(|| assert_that(3).with_location(false).is_one())
326                .has_type::<String>()
327                .is_equal_to(formatdoc! {r#"
328                    -------- assertr --------
329                    Expected: 1
330
331                      Actual: 3
332                    
333                    Details: [
334                        Expecting multiplicative identity of type 'i32',
335                    ]
336                    -------- assertr --------
337                "#});
338        }
339    }
340
341    mod is_negative {
342        use crate::prelude::*;
343        use indoc::formatdoc;
344
345        #[test]
346        fn succeeds_when_zero() {
347            assert_that(-0.01).is_negative();
348        }
349
350        #[test]
351        fn panics_when_zero() {
352            assert_that_panic_by(|| assert_that(0.0).with_location(false).is_negative())
353                .has_type::<String>()
354                .is_equal_to(formatdoc! {r#"
355                    -------- assertr --------
356                    Expected value to be negative. But was
357
358                      Actual: 0.0
359                    -------- assertr --------
360                "#});
361        }
362
363        #[test]
364        fn panics_when_positive() {
365            assert_that_panic_by(|| assert_that(1.23).with_location(false).is_negative())
366                .has_type::<String>()
367                .is_equal_to(formatdoc! {r#"
368                    -------- assertr --------
369                    Expected value to be negative. But was
370
371                      Actual: 1.23
372                    -------- assertr --------
373                "#});
374        }
375    }
376
377    mod is_positive {
378        use crate::prelude::*;
379        use indoc::formatdoc;
380
381        #[test]
382        fn succeeds_when_positive() {
383            assert_that(0.01).is_positive();
384        }
385
386        #[test]
387        fn succeeds_when_zero() {
388            assert_that(0.0).is_positive();
389        }
390
391        #[test]
392        fn panics_when_negative() {
393            assert_that_panic_by(|| assert_that(-1.23).with_location(false).is_positive())
394                .has_type::<String>()
395                .is_equal_to(formatdoc! {r#"
396                    -------- assertr --------
397                    Expected value to be positive. But was
398
399                      Actual: -1.23
400                    -------- assertr --------
401                "#});
402        }
403    }
404
405    mod is_close_to {
406        use crate::prelude::*;
407        use indoc::formatdoc;
408
409        #[test]
410        fn panics_when_below_allowed_range() {
411            assert_that_panic_by(|| {
412                assert_that(0.3319)
413                    .with_location(false)
414                    .is_close_to(0.333, 0.001)
415            })
416            .has_type::<String>()
417            .is_equal_to(formatdoc! {r#"
418                    -------- assertr --------
419                    Expected value to be close to: 0.333,
420                     with allowed deviation being: 0.001,
421                      but value was outside range: [0.332, 0.334]
422
423                      Actual: 0.3319
424                    -------- assertr --------
425                "#});
426        }
427
428        #[test]
429        fn succeeds_when_actual_is_in_allowed_range() {
430            assert_that(0.332).is_close_to(0.333, 0.001);
431            assert_that(0.333).is_close_to(0.333, 0.001);
432            assert_that(0.334).is_close_to(0.333, 0.001);
433        }
434
435        #[test]
436        fn panics_when_above_allowed_range() {
437            assert_that_panic_by(|| {
438                assert_that(0.3341)
439                    .with_location(false)
440                    .is_close_to(0.333, 0.001)
441            })
442            .has_type::<String>()
443            .is_equal_to(formatdoc! {r#"
444                    -------- assertr --------
445                    Expected value to be close to: 0.333,
446                     with allowed deviation being: 0.001,
447                      but value was outside range: [0.332, 0.334]
448
449                      Actual: 0.3341
450                    -------- assertr --------
451                "#});
452        }
453    }
454
455    mod is_nan {
456        use crate::prelude::*;
457        use ::num::Float;
458        use indoc::formatdoc;
459
460        #[test]
461        fn succeeds_when_nan() {
462            assert_that(f32::nan()).is_nan();
463        }
464
465        #[test]
466        fn panics_when_not_nan() {
467            assert_that_panic_by(|| assert_that(1.23).with_location(false).is_nan())
468                .has_type::<String>()
469                .is_equal_to(formatdoc! {r#"
470                    -------- assertr --------
471                    Expected: NaN
472
473                      Actual: 1.23
474                    -------- assertr --------
475                "#});
476        }
477    }
478
479    mod is_finite {
480        use crate::prelude::*;
481        use indoc::formatdoc;
482        use num::Float;
483
484        #[test]
485        fn succeeds_when_finite() {
486            assert_that(0.3f32).is_finite();
487        }
488
489        #[test]
490        fn panics_when_positive_infinity() {
491            assert_that_panic_by(|| {
492                assert_that(f32::infinity())
493                    .with_location(false)
494                    .is_finite()
495            })
496            .has_type::<String>()
497            .is_equal_to(formatdoc! {r#"
498                    -------- assertr --------
499                    Expected a finite value, but was
500
501                      Actual: inf
502                    -------- assertr --------
503                "#});
504        }
505
506        #[test]
507        fn panics_when_negative_infinity() {
508            assert_that_panic_by(|| {
509                assert_that(f32::neg_infinity())
510                    .with_location(false)
511                    .is_finite()
512            })
513            .has_type::<String>()
514            .is_equal_to(formatdoc! {r#"
515                    -------- assertr --------
516                    Expected a finite value, but was
517
518                      Actual: -inf
519                    -------- assertr --------
520                "#});
521        }
522    }
523
524    mod is_infinite {
525        use crate::prelude::*;
526        use ::num::Float;
527        use indoc::formatdoc;
528
529        #[test]
530        fn succeeds_when_positive_infinity() {
531            assert_that(f32::infinity()).is_infinite();
532        }
533
534        #[test]
535        fn succeeds_when_negative_infinity() {
536            assert_that(f32::neg_infinity()).is_infinite();
537        }
538
539        #[test]
540        fn panics_when_not_infinity() {
541            assert_that_panic_by(|| assert_that(1.23).with_location(false).is_infinite())
542                .has_type::<String>()
543                .is_equal_to(formatdoc! {r#"
544                    -------- assertr --------
545                    Expected: +/- inf
546
547                      Actual: 1.23
548                    -------- assertr --------
549                "#});
550        }
551    }
552}