Skip to main content

assert_rs/
assertion.rs

1use core::fmt::{Debug, Display};
2
3use crate::AssertInfo;
4
5#[cfg(feature = "std")]
6#[must_use = "assertions do not fire unless returned from a test"]
7/// An assertion that has been made about some object.
8pub trait Assertion: Debug + Display + std::process::Termination {
9    /// `true` if the assertion was true.
10    fn test(&self) -> bool;
11
12    /// Wrap the assertion in [`ShouldFail`], inverting its condition.
13    fn should_fail(self) -> ShouldFail<Self>
14    where
15        Self: Sized,
16    {
17        ShouldFail(self)
18    }
19
20    /// Panic if the assertion was false.
21    ///
22    /// # Panics
23    ///
24    /// See above.
25    #[allow(
26        clippy::panic,
27        reason = "I agree with you clippy, but some people don't care about panics."
28    )]
29    fn unwrap(&self) {
30        assert!(self.test(), "failed assertion was unwrapped:\n{self}");
31    }
32}
33
34#[cfg(not(feature = "std"))]
35#[must_use = "assertions do not fire unless returned from a test"]
36/// An assertion that has been made about some object.
37pub trait Assertion: Debug + Display {
38    /// `true` if the assertion was true.
39    fn test(&self) -> bool;
40
41    /// Wrap the assertion in [`ShouldFail`], inverting its condition.
42    fn should_fail(self) -> ShouldFail<Self>
43    where
44        Self: Sized,
45    {
46        ShouldFail(self)
47    }
48
49    /// Panic if the assertion was false.
50    ///
51    /// # Panics
52    ///
53    /// See above.
54    #[allow(
55        clippy::panic,
56        reason = "I agree with you clippy, but some people don't care about panics."
57    )]
58    fn unwrap(&self) {
59        assert!(self.test(), "failed assertion was unwrapped:\n{self}");
60    }
61}
62
63/// An assertion about two objects `lhs` and `rhs`.
64#[must_use = "assertions do not fire unless returned from a test"]
65pub struct BinaryAssertion<T, U> {
66    lhs: T,
67    rhs: U,
68    test_result: bool,
69    test_repr: &'static str,
70    info: AssertInfo,
71}
72
73impl<T, U> Display for BinaryAssertion<T, U>
74where
75    T: Debug,
76    U: Debug,
77{
78    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
79        let Self {
80            lhs,
81            rhs,
82            test_result: _,
83            test_repr,
84            info: AssertInfo { file, line, column },
85        } = self;
86
87        write!(
88            f,
89            "assertion starting at {file}:{line}:{column} failed: `{test_repr}`\n lhs: {lhs:?}\n rhs: {rhs:?}"
90        )
91    }
92}
93
94impl<T, U> Debug for BinaryAssertion<T, U>
95where
96    T: Debug,
97    U: Debug,
98{
99    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
100        Display::fmt(self, f)
101    }
102}
103
104impl<T, U> BinaryAssertion<T, U> {
105    pub(crate) const fn new(
106        lhs: T,
107        rhs: U,
108        test_result: bool,
109        test_repr: &'static str,
110        info: AssertInfo,
111    ) -> Self {
112        Self {
113            lhs,
114            rhs,
115            test_result,
116            test_repr,
117            info,
118        }
119    }
120}
121
122#[cfg(feature = "std")]
123impl<T, U> std::process::Termination for BinaryAssertion<T, U>
124where
125    T: Debug,
126    U: Debug,
127{
128    fn report(self) -> std::process::ExitCode {
129        if self.test() {
130            std::process::ExitCode::SUCCESS
131        } else {
132            println!("{self}");
133            std::process::ExitCode::FAILURE
134        }
135    }
136}
137
138impl<T, U> Assertion for BinaryAssertion<T, U>
139where
140    T: Debug,
141    U: Debug,
142{
143    fn test(&self) -> bool {
144        self.test_result
145    }
146}
147
148/// An assertion about an object `this`.
149#[must_use = "assertions do not fire unless returned from a test"]
150pub struct UnaryAssertion<T> {
151    this: T,
152    test_result: bool,
153    test_repr: &'static str,
154    info: AssertInfo,
155}
156
157impl<T> Display for UnaryAssertion<T>
158where
159    T: Debug,
160{
161    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
162        let Self {
163            this,
164            test_result: _,
165            test_repr,
166            info: AssertInfo { file, line, column },
167        } = self;
168
169        write!(
170            f,
171            "assertion starting at {file}:{line}:{column} failed: `{test_repr}`\nthis: {this:?}"
172        )
173    }
174}
175
176impl<T> Debug for UnaryAssertion<T>
177where
178    T: Debug,
179{
180    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
181        Display::fmt(self, f)
182    }
183}
184
185impl<T> UnaryAssertion<T> {
186    pub(crate) const fn new(
187        this: T,
188        test_result: bool,
189        test_repr: &'static str,
190        info: AssertInfo,
191    ) -> Self {
192        Self {
193            this,
194            test_result,
195            test_repr,
196            info,
197        }
198    }
199}
200
201#[cfg(feature = "std")]
202impl<T> std::process::Termination for UnaryAssertion<T>
203where
204    T: Debug,
205{
206    fn report(self) -> std::process::ExitCode {
207        if self.test() {
208            std::process::ExitCode::SUCCESS
209        } else {
210            println!("{self}");
211            std::process::ExitCode::FAILURE
212        }
213    }
214}
215
216impl<T> Assertion for UnaryAssertion<T>
217where
218    T: Debug,
219{
220    fn test(&self) -> bool {
221        self.test_result
222    }
223}
224
225/// An assertion that will unconditionally succeed.
226#[must_use = "assertions do not fire unless returned from a test"]
227pub struct Succeed {
228    info: AssertInfo,
229}
230
231impl Display for Succeed {
232    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
233        let AssertInfo { file, line, column } = self.info;
234
235        write!(
236            f,
237            "assertion starting at {file}:{line}:{column} was forced success"
238        )
239    }
240}
241
242impl Debug for Succeed {
243    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
244        Display::fmt(self, f)
245    }
246}
247
248impl Succeed {
249    #[doc(hidden)]
250    /// Don't use this constructor. Use [`succeed!`] instead.
251    pub const fn manual_constructor(file: &'static str, line: u32, column: u32) -> Self {
252        Self {
253            info: AssertInfo { file, line, column },
254        }
255    }
256}
257
258#[cfg(feature = "std")]
259impl std::process::Termination for Succeed {
260    fn report(self) -> std::process::ExitCode {
261        std::process::ExitCode::SUCCESS
262    }
263}
264
265impl Assertion for Succeed {
266    fn test(&self) -> bool {
267        true
268    }
269}
270
271/// Construct an [`Assertion`] that will always succeed.
272///
273/// ```no_run
274/// #[test]
275/// fn succeed() -> impl Assertion {
276///     succeed!()
277/// }
278/// ```
279#[macro_export]
280macro_rules! succeed {
281    () => {
282        $crate::assertion::Succeed::manual_constructor(
283            ::core::file!(),
284            ::core::line!(),
285            ::core::column!(),
286        )
287    };
288}
289
290/// An assertion that will unconditionally fail.
291#[must_use = "assertions do not fire unless returned from a test"]
292pub struct Fail {
293    info: AssertInfo,
294}
295
296impl Display for Fail {
297    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
298        let AssertInfo { file, line, column } = self.info;
299
300        write!(
301            f,
302            "assertion starting at {file}:{line}:{column} was forced fail"
303        )
304    }
305}
306
307impl Debug for Fail {
308    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
309        Display::fmt(self, f)
310    }
311}
312
313impl Fail {
314    #[doc(hidden)]
315    /// Don't use this constructor. Use [`fail!`] instead.
316    pub const fn manual_constructor(file: &'static str, line: u32, column: u32) -> Self {
317        Self {
318            info: AssertInfo { file, line, column },
319        }
320    }
321}
322
323#[cfg(feature = "std")]
324impl std::process::Termination for Fail {
325    fn report(self) -> std::process::ExitCode {
326        std::process::ExitCode::FAILURE
327    }
328}
329
330impl Assertion for Fail {
331    fn test(&self) -> bool {
332        false
333    }
334}
335
336/// Construct an [`Assertion`] that will always fail.
337///
338/// ```no_run
339/// #[test]
340/// fn fail() -> impl Assertion {
341///     fail!().should_fail()
342/// }
343/// ```
344#[macro_export]
345macro_rules! fail {
346    () => {
347        $crate::assertion::Fail::manual_constructor(
348            ::core::file!(),
349            ::core::line!(),
350            ::core::column!(),
351        )
352    };
353}
354
355/// An assertion that will fail if the given assertion was true
356/// and succeed if the given assertion was false.
357pub struct ShouldFail<A: Assertion>(A);
358
359impl<A: Assertion> Display for ShouldFail<A> {
360    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
361        let inner = &self.0;
362        write!(f, "assertion expected to fail succeeded:\n{inner}")
363    }
364}
365
366#[cfg(feature = "std")]
367impl<A: Assertion> std::process::Termination for ShouldFail<A> {
368    fn report(self) -> std::process::ExitCode {
369        if self.0.test() {
370            println!("{self}");
371            std::process::ExitCode::FAILURE
372        } else {
373            std::process::ExitCode::SUCCESS
374        }
375    }
376}
377
378impl<A: Assertion> Assertion for ShouldFail<A> {
379    fn test(&self) -> bool {
380        !self.0.test()
381    }
382}
383
384impl<A: Assertion> Debug for ShouldFail<A> {
385    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
386        Display::fmt(self, f)
387    }
388}
389
390#[cfg(feature = "alloc")]
391pub use multi_dyn_assertion::MultiDynAssertion;
392
393#[cfg(feature = "alloc")]
394mod multi_dyn_assertion {
395    use super::{Assertion, Debug, Display};
396
397    extern crate alloc;
398    use alloc::boxed::Box;
399
400    /// A group of assertions, possibly of different types, all tested simultaneously.
401    ///
402    /// These can be created using the [`multi_assert!`](crate::multi_assert) macro.
403    /// See its docs for more.
404    pub struct MultiDynAssertion<const N: usize>([Box<dyn Assertion>; N]);
405
406    impl<const N: usize> Display for MultiDynAssertion<N> {
407        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
408            write!(f, "assertion failures in a multi-assert:")?;
409            for (i, assertion) in self.0.iter().enumerate() {
410                if assertion.test() {
411                    write!(f, "\n{i}. [ok]")?;
412                } else {
413                    writeln!(f, "\n{i}. {assertion}")?;
414                }
415            }
416            Ok(())
417        }
418    }
419
420    impl<const N: usize> MultiDynAssertion<N> {
421        #[must_use]
422        #[doc(hidden)]
423        /// You can use [`multi_assert!`](crate::multi_assert) with `#[dyn]` instead, but it's not required.
424        pub fn new(assertions: [Box<dyn Assertion>; N]) -> Self {
425            Self(assertions)
426        }
427    }
428
429    #[cfg(feature = "std")]
430    impl<const N: usize> std::process::Termination for MultiDynAssertion<N> {
431        fn report(self) -> std::process::ExitCode {
432            if self.test() {
433                std::process::ExitCode::SUCCESS
434            } else {
435                println!("{self}");
436                std::process::ExitCode::FAILURE
437            }
438        }
439    }
440
441    impl<const N: usize> Assertion for MultiDynAssertion<N> {
442        fn test(&self) -> bool {
443            self.0.iter().all(|a| a.test())
444        }
445    }
446
447    impl<const N: usize> Debug for MultiDynAssertion<N> {
448        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
449            Display::fmt(self, f)
450        }
451    }
452}
453
454/// A group of assertions, all tested simultaneously.
455///
456/// These can be created using the [`multi_assert!`](crate::multi_assert) macro.
457/// See its docs for more.
458pub struct MultiAssertion<A: Assertion, const N: usize>([A; N]);
459
460impl<A: Assertion, const N: usize> Display for MultiAssertion<A, N> {
461    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
462        write!(f, "assertion failures in a multi-assert:")?;
463        for (i, assertion) in self.0.iter().enumerate() {
464            if assertion.test() {
465                write!(f, "\n{i}. [ok]")?;
466            } else {
467                writeln!(f, "\n{i}. {assertion}")?;
468            }
469        }
470        Ok(())
471    }
472}
473
474impl<A: Assertion, const N: usize> MultiAssertion<A, N> {
475    #[must_use]
476    /// You can use [`multi_assert!`](crate::multi_assert) instead, but it's not required.
477    pub const fn new(assertions: [A; N]) -> Self {
478        Self(assertions)
479    }
480}
481
482#[cfg(feature = "std")]
483impl<A: Assertion, const N: usize> std::process::Termination for MultiAssertion<A, N> {
484    fn report(self) -> std::process::ExitCode {
485        if self.test() {
486            std::process::ExitCode::SUCCESS
487        } else {
488            println!("{self}");
489            std::process::ExitCode::FAILURE
490        }
491    }
492}
493
494impl<A: Assertion, const N: usize> Assertion for MultiAssertion<A, N> {
495    fn test(&self) -> bool {
496        self.0.iter().all(Assertion::test)
497    }
498}
499
500impl<A: Assertion, const N: usize> Debug for MultiAssertion<A, N> {
501    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
502        Display::fmt(self, f)
503    }
504}
505
506#[cfg(feature = "alloc")]
507#[cfg_attr(doc, doc = include_str!("multi_assert_doc.md"))]
508#[macro_export]
509macro_rules! multi_assert {
510    [#[dyn] $($this:expr),+ $(,)?] => {
511        $crate::assertion::MultiDynAssertion::new([
512            $(Box::new($this),)+
513        ])
514    };
515    [$($this:expr),+ $(,)?] => {
516        $crate::assertion::MultiAssertion::new([
517            $($this,)+
518        ])
519    };
520}
521
522#[cfg(not(feature = "alloc"))]
523#[cfg_attr(doc, doc = include_str!("multi_assert_doc.md"))]
524#[macro_export]
525macro_rules! multi_assert {
526    [#[dyn] $($this:expr),+ $(,)?] => {
527        compile_error!("cannot use #[dyn] if the no_alloc feature is enabled")
528    };
529    [$($this:expr),+ $(,)?] => {
530        $crate::assertion::MultiAssertion::new([
531            $($this,)+
532        ])
533    };
534}