asserting/spec/mod.rs
1//! This is the core of the `asserting` crate.
2
3use crate::expectations::Predicate;
4use crate::std::error::Error as StdError;
5use crate::std::fmt::{self, Debug, Display};
6use crate::std::ops::Deref;
7#[cfg(not(feature = "std"))]
8use alloc::{
9 borrow::ToOwned,
10 string::{String, ToString},
11 vec,
12 vec::Vec,
13};
14
15/// Starts an assertion for the given subject or expression in the
16/// [`PanicOnFail`] mode.
17///
18/// It wraps the subject into a [`Spec`] and sets the name of the expression and
19/// the code location of the assertion in the [`Spec`]. On the [`Spec`] any
20/// assertion method that is implemented for the subject's type can be called.
21///
22/// Assertions started with `assert_that!` will panic on the first failing
23/// assertion.
24///
25/// # Example
26///
27/// ```
28/// use asserting::prelude::*;
29///
30/// assert_that!(7 * 6).is_equal_to(42);
31/// ```
32///
33/// This call of the macro expands to:
34///
35/// ```
36/// # use asserting::prelude::*;
37/// assert_that(7 * 6)
38/// .named("7 * 6")
39/// .located_at(Location { file: file!(), line: line!(), column: column!() })
40/// .is_equal_to(42);
41/// ```
42#[macro_export]
43macro_rules! assert_that {
44 ($subject:expr) => {
45 $crate::prelude::assert_that($subject)
46 .named(&stringify!($subject).replace("\n", " "))
47 .located_at($crate::prelude::Location {
48 file: file!(),
49 line: line!(),
50 column: column!(),
51 })
52 };
53}
54
55/// Starts an assertion for the given subject or expression in the
56/// [`CollectFailures`] mode.
57///
58/// It wraps the subject into a [`Spec`] and sets the name of the expression and
59/// the code location of the assertion in the [`Spec`]. On the [`Spec`] any
60/// assertion method that is implemented for the subject's type can be called.
61///
62/// Assertions started with `verify_that!` will collect [`AssertFailure`]s for
63/// all failing assertions. The collected failures can be queried by calling one
64/// of the methods [`failures`](Spec::failures) or
65/// [`display_failures`](Spec::display_failures) on the [`Spec`].
66///
67/// # Example
68///
69/// ```
70/// use asserting::prelude::*;
71/// let some_text = "vel tempor augue delenit".to_string();
72/// let failures = verify_that!(some_text)
73/// .starts_with("nibh")
74/// .ends_with("magna")
75/// .failures();
76///
77/// assert_that!(failures).has_length(2);
78/// ```
79///
80/// This call of the macro expands to:
81///
82/// ```
83/// # use asserting::prelude::*;
84/// # let some_text = "vel tempor augue delenit".to_string();
85/// let failures = verify_that(some_text)
86/// .named("some_text")
87/// .located_at(Location { file: file!(), line: line!(), column: column!() })
88/// .starts_with("nibh")
89/// .ends_with("magna")
90/// .failures();
91/// ```
92#[macro_export]
93macro_rules! verify_that {
94 ($subject:expr) => {
95 $crate::prelude::verify_that($subject)
96 .named(&stringify!($subject).replace("\n", " "))
97 .located_at($crate::prelude::Location {
98 file: file!(),
99 line: line!(),
100 column: column!(),
101 })
102 };
103}
104
105/// Starts an assertion for some piece of code in the [`PanicOnFail`] mode.
106///
107/// It takes a closure and wraps it into a [`Spec`]. On the [`Spec`] any
108/// assertion method that is implemented for closures can be called.
109///
110/// Assertions started with `assert_that_code!` will panic on the first failing
111/// assertion.
112///
113/// # Examples
114///
115/// ```
116/// use asserting::prelude::*;
117///
118/// fn divide(a: i32, b: i32) -> i32 {
119/// a / b
120/// }
121///
122/// assert_that_code!(|| { divide(7, 0); }).panics();
123///
124/// assert_that_code!(|| { divide(7, 0); })
125/// .panics_with_message("attempt to divide by zero");
126///
127/// assert_that_code!(|| { divide(7, 3); }).does_not_panic();
128/// ```
129#[cfg(feature = "panic")]
130#[cfg_attr(feature = "panic", macro_export)]
131macro_rules! assert_that_code {
132 ($subject:expr) => {
133 $crate::prelude::assert_that_code($subject)
134 .named(&stringify!($subject).replace("\n", " "))
135 .located_at($crate::prelude::Location {
136 file: file!(),
137 line: line!(),
138 column: column!(),
139 })
140 };
141}
142
143/// Starts an assertion for some piece of code in the [`CollectFailures`] mode.
144///
145/// It takes a closure and wraps it into a [`Spec`]. On the [`Spec`] any
146/// assertion method that is implemented for closures can be called.
147///
148/// Assertions started with `verify_that_code!` will collect [`AssertFailure`]s
149/// for all failing assertions. The collected failures can be queried by calling
150/// one of the methods [`failures`](Spec::failures) or
151/// [`display_failures`](Spec::display_failures) on the [`Spec`].
152///
153/// # Examples
154///
155/// ```
156/// use asserting::prelude::*;
157///
158/// fn divide(a: i32, b: i32) -> i32 {
159/// a / b
160/// }
161///
162/// let failures = verify_that_code!(|| { divide(7, 3); })
163/// .does_not_panic()
164/// .failures();
165///
166/// assert_that!(failures).is_empty();
167///
168/// let failures = verify_that_code!(|| { divide(7, 0); })
169/// .does_not_panic()
170/// .display_failures();
171///
172/// assert_that!(failures).contains_exactly([
173/// r#"assertion failed: expected || { divide(7, 0); } to not panic, but did panic
174/// with message: "attempt to divide by zero"
175/// "#
176/// ]);
177///
178/// let failures = verify_that_code!(|| { divide(7, 0); })
179/// .panics_with_message("division by zero")
180/// .display_failures();
181///
182/// assert_that!(failures).contains_exactly([
183/// r#"assertion failed: expected || { divide(7, 0); } to panic with message "division by zero"
184/// but was: "attempt to divide by zero"
185/// expected: "division by zero"
186/// "#
187/// ]);
188/// ```
189#[cfg(feature = "panic")]
190#[cfg_attr(feature = "panic", macro_export)]
191macro_rules! verify_that_code {
192 ($subject:expr) => {
193 $crate::prelude::verify_that_code($subject)
194 .named(&stringify!($subject).replace("\n", " "))
195 .located_at($crate::prelude::Location {
196 file: file!(),
197 line: line!(),
198 column: column!(),
199 })
200 };
201}
202
203/// Starts an assertion for the given subject or expression in the
204/// [`PanicOnFail`] mode.
205///
206/// It wraps the subject into a [`Spec`]. On the [`Spec`] any
207/// assertion method that is implemented for the subject's type can be called.
208///
209/// Assertions started with `assert_that()` will panic on the first failing
210/// assertion.
211///
212/// In comparison to using the macro [`assert_that!`](crate::assert_that)
213/// calling this function does not set a name for the expression and does not
214/// set the code location of the assertion. In failure messages the generic word
215/// "subject" is used. To set a specific text for the expression the method
216/// [`named`](Spec::named) must be called explicitly.
217///
218/// Note: It is not necessary to set the code location explicitly as this
219/// function is annotated with `#[track_caller]`.
220///
221/// # Examples
222///
223/// ```
224/// use asserting::prelude::*;
225///
226/// assert_that(7 * 6).is_equal_to(42);
227/// ```
228///
229/// or with setting a name for the expression:
230///
231/// ```
232/// use asserting::prelude::*;
233///
234/// assert_that(7 * 6)
235/// .named("7 * 6")
236/// .is_equal_to(42);
237/// ```
238#[track_caller]
239pub const fn assert_that<'a, S>(subject: S) -> Spec<'a, S, PanicOnFail> {
240 Spec::new(subject, PanicOnFail)
241}
242
243/// Starts an assertion for the given subject or expression in the
244/// [`CollectFailures`] mode.
245///
246/// It wraps the subject into a [`Spec`]. On the [`Spec`] any
247/// assertion method that is implemented for the subject's type can be called.
248///
249/// Assertions started with `verify_that()` will collect [`AssertFailure`]s
250/// for all failing assertions. The collected failures can be queried by calling
251/// one of the methods [`failures`](Spec::failures) or the
252/// [`display_failures`](Spec::display_failures) on the [`Spec`].
253///
254/// In comparison to using the macro [`verify_that!`](crate::verify_that) calling
255/// this function does not set a name for the expression and does not set the
256/// code location of the assertion. In failure messages the generic word
257/// "subject" is used. To set a specific text for the expression the method
258/// [`named`](Spec::named) must be called explicitly.
259///
260/// # Examples
261///
262/// ```
263/// use asserting::prelude::*;
264///
265/// let some_text = "vel tempor augue delenit".to_string();
266///
267/// let failures = verify_that(some_text).named("my_thing")
268/// .starts_with("nibh")
269/// .ends_with("magna")
270/// .failures();
271///
272/// assert_that!(failures).has_length(2);
273/// ```
274///
275/// or with querying the failures as formated text:
276///
277/// ```
278/// use asserting::prelude::*;
279///
280/// let some_text = "vel tempor augue delenit".to_string();
281///
282/// let failures = verify_that(some_text).named("my_thing")
283/// .starts_with("nibh")
284/// .ends_with("magna")
285/// .display_failures();
286///
287/// assert_that!(failures).contains_exactly([
288/// r#"assertion failed: expected my_thing to start with "nibh"
289/// but was: "vel tempor augue delenit"
290/// expected: "nibh"
291/// "#,
292/// r#"assertion failed: expected my_thing to end with "magna"
293/// but was: "vel tempor augue delenit"
294/// expected: "magna"
295/// "#,
296/// ]);
297/// ```
298#[track_caller]
299pub const fn verify_that<'a, S>(subject: S) -> Spec<'a, S, CollectFailures> {
300 Spec::new(subject, CollectFailures)
301}
302
303/// Starts an assertion for some piece of code in the [`PanicOnFail`] mode.
304///
305/// It takes a closure and wraps it into a [`Spec`]. On the [`Spec`] any
306/// assertion method that is implemented for closures can be called.
307///
308/// Assertions started with `assert_that_code()` will panic on the first failing
309/// assertion.
310///
311/// In comparison to using the macro [`assert_that_code!`](crate::assert_that_code)
312/// calling this function does not set a name for the expression and does not
313/// set the code location of the assertion. In failure messages the generic word
314/// "the closure" is used. To set a specific text for the expression the method
315/// [`named`](Spec::named) must be called explicitly.
316///
317/// # Examples
318///
319/// ```
320/// use asserting::prelude::*;
321///
322/// fn divide(a: i32, b: i32) -> i32 {
323/// a / b
324/// }
325///
326/// assert_that_code(|| { divide(7, 0); })
327/// .panics_with_message("attempt to divide by zero");
328///
329/// assert_that_code(|| { divide(7, 3); }).does_not_panic();
330/// ```
331#[cfg(feature = "panic")]
332pub fn assert_that_code<'a, S>(code: S) -> Spec<'a, Code<S>, PanicOnFail>
333where
334 S: FnOnce(),
335{
336 Spec::new(Code::from(code), PanicOnFail).named("the closure")
337}
338
339/// Starts an assertion for some piece of code in the [`CollectFailures`] mode.
340///
341/// It takes a closure and wraps it into a [`Spec`]. On the [`Spec`] any
342/// assertion method that is implemented for closures can be called.
343///
344/// Assertions started with `verify_that_code()` will collect [`AssertFailure`]s
345/// for all failing assertions. The collected failures can be queried by calling
346/// one of the methods [`failures`](Spec::failures) or
347/// [`display_failures`](Spec::display_failures) on the [`Spec`].
348///
349/// In comparison to using the macro [`verify_that_code!`](crate::verify_that_code)
350/// calling this function does not set a name for the expression and does not
351/// set the code location of the assertion. In failure messages the generic word
352/// "the closure" is used. To set a specific text for the expression the method
353/// [`named`](Spec::named) must be called explicitly.
354///
355/// # Examples
356///
357/// ```
358/// use asserting::prelude::*;
359///
360/// fn divide(a: i32, b: i32) -> i32 {
361/// a / b
362/// }
363///
364/// let failures = verify_that_code(|| { divide(7, 3); })
365/// .does_not_panic()
366/// .failures();
367///
368/// assert_that!(failures).is_empty();
369///
370/// let failures = verify_that_code(|| { divide(7, 0); })
371/// .does_not_panic()
372/// .display_failures();
373///
374/// assert_that!(failures).contains_exactly([
375/// r#"assertion failed: expected the closure to not panic, but did panic
376/// with message: "attempt to divide by zero"
377/// "#
378/// ]);
379///
380/// let failures = verify_that_code(|| { divide(7, 0); })
381/// .panics_with_message("division by zero")
382/// .display_failures();
383///
384/// assert_that!(failures).contains_exactly([
385/// r#"assertion failed: expected the closure to panic with message "division by zero"
386/// but was: "attempt to divide by zero"
387/// expected: "division by zero"
388/// "#
389/// ]);
390/// ```
391#[cfg(feature = "panic")]
392pub fn verify_that_code<'a, S>(code: S) -> Spec<'a, Code<S>, CollectFailures>
393where
394 S: FnOnce(),
395{
396 Spec::new(Code::from(code), CollectFailures).named("the closure")
397}
398
399/// An expectation defines a test for a property of the asserted subject.
400///
401/// It requires two methods: a `test()` method and a `message()` method.
402/// The `test()` method is called to verify whether an actual subject meets the
403/// expected property. In case the test of the expectation fails the `message()`
404/// method is called to form an expectation specific failure message.
405pub trait Expectation<S: ?Sized> {
406 /// Verifies whether the actual subject fulfills the expected property.
407 fn test(&mut self, subject: &S) -> bool;
408
409 /// Forms a failure message for this expectation.
410 fn message(&self, expression: Expression<'_>, actual: &S) -> String;
411}
412
413/// A textual representation of the expression or subject that is being
414/// asserted.
415#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
416pub struct Expression<'a>(pub &'a str);
417
418impl Default for Expression<'_> {
419 fn default() -> Self {
420 Self("subject")
421 }
422}
423
424impl Display for Expression<'_> {
425 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426 write!(f, "{}", self.0)
427 }
428}
429
430impl Deref for Expression<'_> {
431 type Target = str;
432
433 fn deref(&self) -> &Self::Target {
434 self.0
435 }
436}
437
438/// The location of an assertion in the source code respectively test code.
439///
440/// # Related
441/// - [`core::panic::Location`]
442#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
443pub struct Location<'a> {
444 /// The file path of the source file where the assertion is located.
445 pub file: &'a str,
446
447 /// The line number within the source file where the assertion is located.
448 pub line: u32,
449
450 /// The column number on the line within the source file where the assertion
451 /// is located.
452 pub column: u32,
453}
454
455impl Display for Location<'_> {
456 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
457 #[cfg(not(test))]
458 let file = self.file;
459 #[cfg(test)]
460 let file = self.file.replace('\\', "/");
461 write!(f, "{file}:{}:{}", self.line, self.column)
462 }
463}
464
465impl<'a> Location<'a> {
466 /// Constructs a new `Location` with the given file, line and column.
467 #[must_use]
468 pub const fn new(file: &'a str, line: u32, column: u32) -> Self {
469 Self { file, line, column }
470 }
471}
472
473impl Location<'_> {
474 /// Returns the file path of this location.
475 #[must_use]
476 pub const fn file(&self) -> &str {
477 self.file
478 }
479
480 /// Returns the line number of this location.
481 #[must_use]
482 pub const fn line(&self) -> u32 {
483 self.line
484 }
485
486 /// Returns the column number of this location.
487 #[must_use]
488 pub const fn column(&self) -> u32 {
489 self.column
490 }
491}
492
493/// An owned location in the source code respectively test code.
494///
495/// It is basically the same as [`Location`] but uses owned types instead of
496/// borrowed types for its fields.
497#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
498pub struct OwnedLocation {
499 /// The file path of the source file where the assertion is located.
500 pub file: String,
501
502 /// The line number within the source file where the assertion is located.
503 pub line: u32,
504
505 /// The column number on the line within the source file where the assertion
506 /// is located.
507 pub column: u32,
508}
509
510impl Display for OwnedLocation {
511 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
512 #[cfg(not(test))]
513 let file = self.file.clone();
514 #[cfg(test)]
515 let file = self.file.replace('\\', "/");
516 write!(f, "{file}:{}:{}", self.line, self.column)
517 }
518}
519
520impl OwnedLocation {
521 /// Constructs a new `OwnedLocation` with the given file, line and column.
522 #[must_use]
523 pub fn new(file: impl Into<String>, line: u32, column: u32) -> Self {
524 Self {
525 file: file.into(),
526 line,
527 column,
528 }
529 }
530}
531
532impl From<Location<'_>> for OwnedLocation {
533 fn from(value: Location<'_>) -> Self {
534 Self {
535 file: value.file.into(),
536 line: value.line,
537 column: value.column,
538 }
539 }
540}
541
542impl OwnedLocation {
543 /// Returns the file path of this location.
544 #[must_use]
545 pub fn file(&self) -> &str {
546 &self.file
547 }
548
549 #[must_use]
550 /// Returns the line number of this location.
551 pub const fn line(&self) -> u32 {
552 self.line
553 }
554
555 #[must_use]
556 /// Returns the column number of this location.
557 pub const fn column(&self) -> u32 {
558 self.column
559 }
560}
561
562/// Data of an actual assertion.
563///
564/// It holds the data needed to execute an assertion such as the subject,
565/// the name of the subject or expression, an optional description of the
566/// current assertion and the location of the assertion in the source code
567/// respectively test code.
568///
569/// It also holds the concrete [`FailingStrategy`] on how to behave in case
570/// an assertion fails.
571///
572/// In case of the [`CollectFailures`] failing strategy the [`AssertFailure`]s
573/// are collected in this struct.
574pub struct Spec<'a, S, R> {
575 subject: S,
576 expression: Option<Expression<'a>>,
577 description: Option<&'a str>,
578 location: Option<Location<'a>>,
579 failures: Vec<AssertFailure>,
580 failing_strategy: R,
581}
582
583impl<S, R> Spec<'_, S, R> {
584 /// Constructs a new `Spec` for the given subject and with the specified
585 /// failing strategy.
586 #[must_use = "a spec does nothing unless an assertion method is called"]
587 pub const fn new(subject: S, failing_strategy: R) -> Self {
588 Self {
589 subject,
590 expression: None,
591 description: None,
592 location: None,
593 failures: vec![],
594 failing_strategy,
595 }
596 }
597
598 /// Returns the subject.
599 pub const fn subject(&self) -> &S {
600 &self.subject
601 }
602
603 /// Returns the expression (or subject name) if one has been set.
604 pub const fn expression(&self) -> Option<Expression<'_>> {
605 self.expression
606 }
607
608 /// Returns the location in source code or test code if it has been set.
609 pub const fn location(&self) -> Option<Location<'_>> {
610 self.location
611 }
612
613 /// Returns the description or the assertion if it has been set.
614 pub const fn description(&self) -> Option<&str> {
615 self.description
616 }
617
618 /// Returns the failing strategy that is used in case an assertion fails.
619 pub const fn failing_strategy(&self) -> &R {
620 &self.failing_strategy
621 }
622
623 /// Returns the assertion failures that have been collected so far.
624 pub fn failures(&self) -> Vec<AssertFailure> {
625 self.failures.clone()
626 }
627
628 /// Returns the assertion failures collected so far as formatted text.
629 pub fn display_failures(&self) -> Vec<String> {
630 self.failures.iter().map(ToString::to_string).collect()
631 }
632}
633
634impl<'a, S, R> Spec<'a, S, R> {
635 /// Sets the subject name or expression for this assertion.
636 #[must_use = "a spec does nothing unless an assertion method is called"]
637 pub const fn named(mut self, subject_name: &'a str) -> Self {
638 self.expression = Some(Expression(subject_name));
639 self
640 }
641
642 /// Sets a custom description about what is being asserted.
643 #[must_use = "a spec does nothing unless an assertion method is called"]
644 pub const fn described_as(mut self, description: &'a str) -> Self {
645 self.description = Some(description);
646 self
647 }
648
649 /// Sets the location of the assertion in the source code respectively test
650 /// code.
651 #[must_use = "a spec does nothing unless an assertion method is called"]
652 pub const fn located_at(mut self, location: Location<'a>) -> Self {
653 self.location = Some(location);
654 self
655 }
656
657 /// Maps the current subject to some other value.
658 ///
659 /// It takes a closure that maps the current subject to a new subject and
660 /// returns a new `Spec` with the value returned by the closure as the new
661 /// subject. The new subject may have a different type than the original
662 /// subject. All other data like expression, description and location are
663 /// taken over from this `Spec` into the returned `Spec`.
664 ///
665 /// This function is useful when having a custom type and some specific
666 /// property of this type shall be asserted only.
667 ///
668 /// This is an alias function to the [`mapping()`](Spec::mapping) function.
669 /// Both functions do exactly the same. The idea is to provide different
670 /// names to be able to express the intent more clearly when used in
671 /// assertions.
672 ///
673 /// # Example
674 ///
675 /// ```
676 /// use asserting::prelude::*;
677 ///
678 /// struct MyStruct {
679 /// important_property: String,
680 /// other_property: f64,
681 /// }
682 ///
683 /// let some_thing = MyStruct {
684 /// important_property: "imperdiet aliqua zzril eiusmod".into(),
685 /// other_property: 99.9,
686 /// };
687 ///
688 /// assert_that!(some_thing).extracting(|s| s.important_property)
689 /// .is_equal_to("imperdiet aliqua zzril eiusmod");
690 ///
691 /// ```
692 pub fn extracting<F, U>(self, extractor: F) -> Spec<'a, U, R>
693 where
694 F: FnOnce(S) -> U,
695 {
696 self.mapping(extractor)
697 }
698
699 /// Maps the current subject to some other value.
700 ///
701 /// It takes a closure that maps the current subject to a new subject and
702 /// returns a new `Spec` with the value returned by the closure as the new
703 /// subject. The new subject may have a different type than the original
704 /// subject. All other data like expression, description and location are
705 /// taken over from this `Spec` into the returned `Spec`.
706 ///
707 /// This function is useful if some type does not implement a trait that is
708 /// required for an assertion.
709 ///
710 /// `Spec` also provides the [`extracting()`](Spec::extracting) function,
711 /// which is an alias to this function. Both functions do exactly the same.
712 /// Choose that function of which its name expresses the intent more
713 /// clearly.
714 ///
715 /// # Example
716 ///
717 /// ```
718 /// use asserting::prelude::*;
719 ///
720 /// struct Point {
721 /// x: i64,
722 /// y: i64,
723 /// }
724 ///
725 /// let target = Point { x: 12, y: -64 };
726 ///
727 /// assert_that!(target).mapping(|s| (s.x, s.y)).is_equal_to((12, -64));
728 /// ```
729 ///
730 /// The custom type `Point` does not implement the `PartialEq` trait nor
731 /// the `Debug` trait, which are both required for an `is_equal_to`
732 /// assertion. So we map the subject of the type `Point` to a tuple of its
733 /// fields.
734 pub fn mapping<F, U>(self, mapper: F) -> Spec<'a, U, R>
735 where
736 F: FnOnce(S) -> U,
737 {
738 Spec {
739 subject: mapper(self.subject),
740 expression: self.expression,
741 description: self.description,
742 location: self.location,
743 failures: self.failures,
744 failing_strategy: self.failing_strategy,
745 }
746 }
747}
748
749impl<S, R> Spec<'_, S, R>
750where
751 R: FailingStrategy,
752{
753 /// Asserts the given expectation.
754 ///
755 /// In case the expectation is not meet it does fail according the current
756 /// failing strategy of this `Spec`.
757 ///
758 /// This method is called from the implementations of the assertion traits
759 /// defined in the [`assertions`](crate::assertions) module. Implementations
760 /// of custom assertions will call this method with a proper expectation.
761 ///
762 /// # Examples
763 ///
764 /// ```
765 /// use asserting::expectations::{IsEmpty, IsEqualTo};
766 /// use asserting::prelude::*;
767 ///
768 /// assert_that!(7 * 6).expecting(IsEqualTo {expected: 42 });
769 ///
770 /// assert_that!("").expecting(IsEmpty);
771 /// ```
772 #[allow(clippy::needless_pass_by_value, clippy::return_self_not_must_use)]
773 #[track_caller]
774 pub fn expecting(mut self, mut expectation: impl Expectation<S>) -> Self {
775 if !expectation.test(&self.subject) {
776 let expression = self.expression.unwrap_or_default();
777 let message = expectation.message(expression, &self.subject);
778 self.do_fail_with_message(message);
779 }
780 self
781 }
782
783 /// Asserts whether the given predicate is meet.
784 ///
785 /// This method takes a predicate function and calls it as an expectation.
786 /// In case the predicate function returns false, it does fail with a
787 /// generic failure message and according to the current failing strategy of
788 /// this `Spec`.
789 ///
790 /// This method can be used to do simple custom assertions without
791 /// implementing an [`Expectation`] and an assertion trait.
792 ///
793 /// # Examples
794 ///
795 /// ```
796 /// use asserting::prelude::*;
797 ///
798 /// fn is_odd(value: &i32) -> bool {
799 /// value & 1 == 1
800 /// }
801 ///
802 /// assert_that!(37).satisfies(is_odd);
803 ///
804 /// let failures = verify_that!(22).satisfies(is_odd).display_failures();
805 ///
806 /// assert_that!(failures).contains_exactly([
807 /// "assertion failed: expected 22 to satisfy the given predicate, but returned false\n"
808 /// ]);
809 /// ```
810 ///
811 /// To assert a predicate with a custom failure message instead of the
812 /// generic one use the method
813 /// [`satisfies_with_message`](Spec::satisfies_with_message).
814 #[allow(clippy::return_self_not_must_use)]
815 #[track_caller]
816 pub fn satisfies<P>(self, predicate: P) -> Self
817 where
818 P: Fn(&S) -> bool,
819 {
820 self.expecting(Predicate {
821 predicate,
822 message: None,
823 })
824 }
825
826 #[allow(clippy::return_self_not_must_use)]
827 #[track_caller]
828 /// Asserts whether the given predicate is meet.
829 ///
830 /// This method takes a predicate function and calls it as an expectation.
831 /// In case the predicate function returns false, it does fail with the
832 /// provided failure message and according to the current failing strategy
833 /// of this `Spec`.
834 ///
835 /// This method can be used to do simple custom assertions without
836 /// implementing an [`Expectation`] and an assertion trait.
837 ///
838 /// # Examples
839 ///
840 /// ```
841 /// use asserting::prelude::*;
842 ///
843 /// fn is_odd(value: &i32) -> bool {
844 /// value & 1 == 1
845 /// }
846 ///
847 /// assert_that!(37).satisfies_with_message("expected my number to be odd", is_odd);
848 ///
849 /// let failures = verify_that!(22)
850 /// .satisfies_with_message("expected my number to be odd", is_odd)
851 /// .display_failures();
852 ///
853 /// assert_that!(failures).contains_exactly([
854 /// "assertion failed: expected my number to be odd\n"
855 /// ]);
856 /// ```
857 ///
858 /// To assert a predicate with a generic failure message instead of
859 /// providing one use the method
860 /// [`satisfies`](Spec::satisfies).
861 pub fn satisfies_with_message<P>(self, message: impl Into<String>, predicate: P) -> Self
862 where
863 P: Fn(&S) -> bool,
864 {
865 self.expecting(Predicate {
866 predicate,
867 message: Some(message.into()),
868 })
869 }
870
871 /// Fails the assertion according the current failing strategy of this
872 /// `Spec`.
873 #[track_caller]
874 fn do_fail_with_message(&mut self, message: impl Into<String>) {
875 let message = message.into();
876 let failure = AssertFailure {
877 description: self.description.map(ToOwned::to_owned),
878 message,
879 location: self.location.map(OwnedLocation::from),
880 };
881 self.failures.push(failure);
882 self.failing_strategy.do_fail_with(&self.failures);
883 }
884}
885
886/// An error describing a failed assertion.
887///
888/// This struct implements the [`std::error::Error`] trait.
889#[derive(Debug, Clone, PartialEq, Eq)]
890pub struct AssertFailure {
891 description: Option<String>,
892 message: String,
893 location: Option<OwnedLocation>,
894}
895
896impl Display for AssertFailure {
897 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
898 match &self.description {
899 None => {
900 writeln!(f, "assertion failed: {}", &self.message)?;
901 },
902 Some(description) => {
903 writeln!(f, "assertion failed: {description}\n{}", &self.message)?;
904 },
905 }
906 Ok(())
907 }
908}
909
910impl StdError for AssertFailure {}
911
912#[allow(clippy::must_use_candidate)]
913impl AssertFailure {
914 /// Returns the description of the assertion that failed.
915 pub const fn description(&self) -> Option<&String> {
916 self.description.as_ref()
917 }
918
919 /// Returns the failure message of the assertion that failed.
920 pub fn message(&self) -> &str {
921 &self.message
922 }
923
924 /// Returns the location of the assertion in the source code / test code if
925 /// it has been set in the [`Spec`].
926 pub const fn location(&self) -> Option<&OwnedLocation> {
927 self.location.as_ref()
928 }
929}
930
931/// Defines the behavior when an assertion fails.
932///
933/// This crate provides two implementations:
934///
935/// * [`PanicOnFail`] - panics when an assertion fails
936/// * [`CollectFailures`] - collects [`AssertFailure`]s of assertions that have failed.
937pub trait FailingStrategy {
938 /// Reacts to an assertion that has failed with the [`AssertFailure`]s given
939 /// as argument.
940 fn do_fail_with(&self, failures: &[AssertFailure]);
941}
942
943/// [`FailingStrategy`] that panics when an assertion fails.
944#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
945pub struct PanicOnFail;
946
947impl FailingStrategy for PanicOnFail {
948 #[track_caller]
949 fn do_fail_with(&self, failures: &[AssertFailure]) {
950 let message = failures
951 .iter()
952 .map(ToString::to_string)
953 .collect::<Vec<_>>()
954 .join("\n");
955 panic!("{}", message);
956 }
957}
958
959/// [`FailingStrategy`] that collects the failures from failing assertions.
960#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
961pub struct CollectFailures;
962
963impl FailingStrategy for CollectFailures {
964 fn do_fail_with(&self, _failures: &[AssertFailure]) {
965 // do nothing by design
966 }
967}
968
969/// Used with generic types in expectations where the concrete type is not
970/// relevant for the failure message.
971///
972/// This type implements the std format trait [`std::fmt::Debug`] and
973/// [`std::fmt::Display`] which both format the value as "_".
974///
975/// ```
976/// # use asserting::prelude::*;
977/// # use asserting::spec::Unknown;
978/// assert_that!(format!("{:?}", Unknown)).is_equal_to("_");
979/// assert_that!(format!("{}", Unknown)).is_equal_to("_");
980/// ```
981///
982/// # Examples
983///
984/// This type is used to implement the expectations
985///
986/// * [`IsSome`](crate::expectations::IsSome)
987/// * [`IsNone`](crate::expectations::IsNone)
988/// * [`IsOk`](crate::expectations::IsOk)
989/// * [`IsErr`](crate::expectations::IsErr)
990///
991/// For example for implementing the function [`Expectation::message()`] for the
992/// [`IsOk`](crate::expectations::IsOk) expectation for `Result<T, E>` the
993/// concrete types for `T` and `E` are not relevant. The implementation of the
994/// trait looks like this:
995///
996/// ```no_run
997/// # use std::fmt::Debug;
998/// # use asserting::spec::Expectation;
999/// # use asserting::spec::Expression;
1000/// # use asserting::spec::Unknown;
1001/// # struct IsOk;
1002/// impl<T, E> Expectation<Result<T, E>> for IsOk
1003/// where
1004/// T: Debug,
1005/// E: Debug,
1006/// {
1007/// fn test(&mut self, subject: &Result<T, E>) -> bool {
1008/// subject.is_ok()
1009/// }
1010///
1011/// fn message(&self, expression: Expression<'_>, actual: &Result<T, E>) -> String {
1012/// format!(
1013/// "expected {expression} is {:?}\n but was: {actual:?}\n expected: {:?}",
1014/// Ok::<_, Unknown>(Unknown),
1015/// Ok::<_, Unknown>(Unknown),
1016/// )
1017/// }
1018/// }
1019/// ```
1020#[derive(Default, Clone, Copy, PartialEq, Eq)]
1021pub struct Unknown;
1022
1023impl Debug for Unknown {
1024 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1025 write!(f, "{self}")
1026 }
1027}
1028
1029impl Display for Unknown {
1030 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1031 write!(f, "_")
1032 }
1033}
1034
1035#[cfg(feature = "panic")]
1036pub use code::Code;
1037
1038#[cfg(feature = "panic")]
1039mod code {
1040 use std::cell::RefCell;
1041 use std::rc::Rc;
1042
1043 /// Wrapper type that holds a closure as code snippet.
1044 pub struct Code<F>(Rc<RefCell<Option<F>>>);
1045
1046 impl<F> From<F> for Code<F>
1047 where
1048 F: FnOnce(),
1049 {
1050 fn from(value: F) -> Self {
1051 Self(Rc::new(RefCell::new(Some(value))))
1052 }
1053 }
1054
1055 impl<F> Code<F> {
1056 /// Takes the closure out of this `Code` leaving it empty.
1057 #[must_use]
1058 pub fn take(&self) -> Option<F> {
1059 self.0.borrow_mut().take()
1060 }
1061 }
1062}
1063
1064#[cfg(test)]
1065mod tests;