asserting/spec/mod.rs
1//! This is the core of the `asserting` crate.
2
3use crate::colored;
4use crate::expectations::satisfies;
5use crate::std::any;
6use crate::std::borrow::Cow;
7use crate::std::error::Error as StdError;
8use crate::std::fmt::{self, Debug, Display};
9use crate::std::format;
10use crate::std::ops::Deref;
11use crate::std::string::{String, ToString};
12use crate::std::vec;
13use crate::std::vec::Vec;
14#[cfg(feature = "panic")]
15use crate::std::{cell::RefCell, rc::Rc};
16
17/// Starts an assertion for the given subject or expression in the
18/// [`PanicOnFail`] mode.
19///
20/// It wraps the subject into a [`Spec`] and sets the name of the expression and
21/// the code location of the assertion in the [`Spec`]. On the [`Spec`] any
22/// assertion method that is implemented for the subject's type can be called.
23///
24/// Assertions started with `assert_that!` will panic on the first failing
25/// assertion.
26///
27/// # Example
28///
29/// ```
30/// use asserting::prelude::*;
31///
32/// assert_that!(7 * 6).is_equal_to(42);
33/// ```
34///
35/// This call of the macro expands to:
36///
37/// ```
38/// # use asserting::prelude::*;
39/// assert_that(7 * 6)
40/// .named("7 * 6")
41/// .located_at(Location { file: file!(), line: line!(), column: column!() })
42/// .is_equal_to(42);
43/// ```
44#[macro_export]
45macro_rules! assert_that {
46 ($subject:expr) => {
47 $crate::prelude::assert_that($subject)
48 .named(&stringify!($subject).replace("\n", " "))
49 .located_at($crate::prelude::Location {
50 file: file!(),
51 line: line!(),
52 column: column!(),
53 })
54 };
55}
56
57/// Starts an assertion for the given subject or expression in the
58/// [`CollectFailures`] mode.
59///
60/// It wraps the subject into a [`Spec`] and sets the name of the expression and
61/// the code location of the assertion in the [`Spec`]. On the [`Spec`] any
62/// assertion method that is implemented for the subject's type can be called.
63///
64/// Assertions started with `verify_that!` will collect [`AssertFailure`]s for
65/// all failing assertions. The collected failures can be queried by calling one
66/// of the methods [`failures`](Spec::failures) or
67/// [`display_failures`](Spec::display_failures) on the [`Spec`].
68///
69/// # Example
70///
71/// ```
72/// use asserting::prelude::*;
73/// let some_text = "vel tempor augue delenit".to_string();
74/// let failures = verify_that!(some_text)
75/// .starts_with("nibh")
76/// .ends_with("magna")
77/// .failures();
78///
79/// assert_that!(failures).has_length(2);
80/// ```
81///
82/// This call of the macro expands to:
83///
84/// ```
85/// # use asserting::prelude::*;
86/// # let some_text = "vel tempor augue delenit".to_string();
87/// let failures = verify_that(some_text)
88/// .named("some_text")
89/// .located_at(Location { file: file!(), line: line!(), column: column!() })
90/// .starts_with("nibh")
91/// .ends_with("magna")
92/// .failures();
93/// ```
94#[macro_export]
95macro_rules! verify_that {
96 ($subject:expr) => {
97 $crate::prelude::verify_that($subject)
98 .named(&stringify!($subject).replace("\n", " "))
99 .located_at($crate::prelude::Location {
100 file: file!(),
101 line: line!(),
102 column: column!(),
103 })
104 };
105}
106
107/// Starts an assertion for some piece of code in the [`PanicOnFail`] mode.
108///
109/// It takes a closure and wraps it into a [`Spec`]. On the [`Spec`] any
110/// assertion method that is implemented for closures can be called.
111///
112/// Assertions started with `assert_that_code!` will panic on the first failing
113/// assertion.
114///
115/// # Examples
116///
117/// ```
118/// use asserting::prelude::*;
119///
120/// fn divide(a: i32, b: i32) -> i32 {
121/// a / b
122/// }
123///
124/// assert_that_code!(|| { divide(7, 0); }).panics();
125///
126/// assert_that_code!(|| { divide(7, 0); })
127/// .panics_with_message("attempt to divide by zero");
128///
129/// assert_that_code!(|| { divide(7, 3); }).does_not_panic();
130/// ```
131#[cfg(feature = "panic")]
132#[cfg_attr(feature = "panic", macro_export)]
133#[cfg_attr(docsrs, doc(cfg(feature = "panic")))]
134macro_rules! assert_that_code {
135 ($subject:expr) => {
136 $crate::prelude::assert_that_code($subject)
137 .named(&stringify!($subject).replace("\n", " "))
138 .located_at($crate::prelude::Location {
139 file: file!(),
140 line: line!(),
141 column: column!(),
142 })
143 };
144}
145
146/// Starts an assertion for some piece of code in the [`CollectFailures`] mode.
147///
148/// It takes a closure and wraps it into a [`Spec`]. On the [`Spec`] any
149/// assertion method that is implemented for closures can be called.
150///
151/// Assertions started with `verify_that_code!` will collect [`AssertFailure`]s
152/// for all failing assertions. The collected failures can be queried by calling
153/// one of the methods [`failures`](Spec::failures) or
154/// [`display_failures`](Spec::display_failures) on the [`Spec`].
155///
156/// # Examples
157///
158/// ```
159/// use asserting::prelude::*;
160///
161/// fn divide(a: i32, b: i32) -> i32 {
162/// a / b
163/// }
164///
165/// let failures = verify_that_code!(|| { divide(7, 3); })
166/// .does_not_panic()
167/// .failures();
168///
169/// assert_that!(failures).is_empty();
170///
171/// let failures = verify_that_code!(|| { divide(7, 0); })
172/// .does_not_panic()
173/// .display_failures();
174///
175/// assert_that!(failures).contains_exactly([
176/// r#"expected || { divide(7, 0); } to not panic, but did panic
177/// with message: "attempt to divide by zero"
178/// "#
179/// ]);
180///
181/// let failures = verify_that_code!(|| { divide(7, 0); })
182/// .panics_with_message("division by zero")
183/// .display_failures();
184///
185/// assert_that!(failures).contains_exactly([
186/// r#"expected || { divide(7, 0); } to panic with message "division by zero"
187/// but was: "attempt to divide by zero"
188/// expected: "division by zero"
189/// "#
190/// ]);
191/// ```
192#[cfg(feature = "panic")]
193#[cfg_attr(feature = "panic", macro_export)]
194#[cfg_attr(docsrs, doc(cfg(feature = "panic")))]
195macro_rules! verify_that_code {
196 ($subject:expr) => {
197 $crate::prelude::verify_that_code($subject)
198 .named(&stringify!($subject).replace("\n", " "))
199 .located_at($crate::prelude::Location {
200 file: file!(),
201 line: line!(),
202 column: column!(),
203 })
204 };
205}
206
207/// Starts an assertion for the given subject or expression in the
208/// [`PanicOnFail`] mode.
209///
210/// It wraps the subject into a [`Spec`]. On the [`Spec`] any
211/// assertion method that is implemented for the subject's type can be called.
212///
213/// Assertions started with `assert_that()` will panic on the first failing
214/// assertion.
215///
216/// In comparison to using the macro [`assert_that!`](crate::assert_that)
217/// calling this function does not set a name for the expression and does not
218/// set the code location of the assertion. In failure messages, the generic word
219/// "subject" is used. To set a specific text for the expression, the method
220/// [`named`](Spec::named) must be called explicitly.
221///
222/// Note: It is not necessary to set the code location explicitly as this
223/// function is annotated with `#[track_caller]`.
224///
225/// # Examples
226///
227/// ```
228/// use asserting::prelude::*;
229///
230/// assert_that(7 * 6).is_equal_to(42);
231/// ```
232///
233/// or with setting a name for the expression:
234///
235/// ```
236/// use asserting::prelude::*;
237///
238/// assert_that(7 * 6)
239/// .named("7 * 6")
240/// .is_equal_to(42);
241/// ```
242#[track_caller]
243pub fn assert_that<'a, S>(subject: S) -> Spec<'a, S, PanicOnFail> {
244 #[cfg(not(feature = "colored"))]
245 {
246 Spec::new(subject, PanicOnFail)
247 }
248 #[cfg(feature = "colored")]
249 {
250 Spec::new(subject, PanicOnFail).with_configured_diff_format()
251 }
252}
253
254/// Starts an assertion for the given subject or expression in the
255/// [`CollectFailures`] mode.
256///
257/// It wraps the subject into a [`Spec`]. On the [`Spec`] any
258/// assertion method that is implemented for the subject's type can be called.
259///
260/// Assertions started with `verify_that()` will collect [`AssertFailure`]s
261/// for all failing assertions. The collected failures can be queried by calling
262/// one of the methods [`failures`](Spec::failures) or the
263/// [`display_failures`](Spec::display_failures) on the [`Spec`].
264///
265/// In comparison to using the macro [`verify_that!`](crate::verify_that) calling
266/// this function does not set a name for the expression and does not set the
267/// code location of the assertion. In failure messages, the generic word
268/// "subject" is used. To set a specific text for the expression, the method
269/// [`named`](Spec::named) must be called explicitly.
270///
271/// # Examples
272///
273/// ```
274/// use asserting::prelude::*;
275///
276/// let some_text = "vel tempor augue delenit".to_string();
277///
278/// let failures = verify_that(some_text).named("my_thing")
279/// .starts_with("nibh")
280/// .ends_with("magna")
281/// .failures();
282///
283/// assert_that!(failures).has_length(2);
284/// ```
285///
286/// or with querying the failures as formated text:
287///
288/// ```
289/// use asserting::prelude::*;
290///
291/// let some_text = "vel tempor augue delenit".to_string();
292///
293/// let failures = verify_that(some_text).named("my_thing")
294/// .starts_with("nibh")
295/// .ends_with("magna")
296/// .display_failures();
297///
298/// assert_that!(failures).contains_exactly([
299/// r#"expected my_thing to start with "nibh"
300/// but was: "vel tempor augue delenit"
301/// expected: "nibh"
302/// "#,
303/// r#"expected my_thing to end with "magna"
304/// but was: "vel tempor augue delenit"
305/// expected: "magna"
306/// "#,
307/// ]);
308/// ```
309#[track_caller]
310pub fn verify_that<'a, S>(subject: S) -> Spec<'a, S, CollectFailures> {
311 Spec::new(subject, CollectFailures)
312}
313
314/// Starts an assertion for some piece of code in the [`PanicOnFail`] mode.
315///
316/// It takes a closure and wraps it into a [`Spec`]. On the [`Spec`] any
317/// assertion method that is implemented for closures can be called.
318///
319/// Assertions started with `assert_that_code()` will panic on the first failing
320/// assertion.
321///
322/// In comparison to using the macro [`assert_that_code!`](crate::assert_that_code)
323/// calling this function does not set a name for the expression and does not
324/// set the code location of the assertion. In failure messages, the generic
325/// word "the closure" is used. To set a specific text for the expression, the
326/// method [`named`](Spec::named) must be called explicitly.
327///
328/// # Examples
329///
330/// ```
331/// use asserting::prelude::*;
332///
333/// fn divide(a: i32, b: i32) -> i32 {
334/// a / b
335/// }
336///
337/// assert_that_code(|| { divide(7, 0); })
338/// .panics_with_message("attempt to divide by zero");
339///
340/// assert_that_code(|| { divide(7, 3); }).does_not_panic();
341/// ```
342#[cfg(feature = "panic")]
343#[cfg_attr(docsrs, doc(cfg(feature = "panic")))]
344pub fn assert_that_code<'a, S>(code: S) -> Spec<'a, Code<S>, PanicOnFail>
345where
346 S: FnOnce(),
347{
348 #[cfg(not(feature = "colored"))]
349 {
350 Spec::new(Code::from(code), PanicOnFail).named("the closure")
351 }
352 #[cfg(feature = "colored")]
353 {
354 Spec::new(Code::from(code), PanicOnFail)
355 .named("the closure")
356 .with_configured_diff_format()
357 }
358}
359
360/// Starts an assertion for some piece of code in the [`CollectFailures`] mode.
361///
362/// It takes a closure and wraps it into a [`Spec`]. On the [`Spec`] any
363/// assertion method that is implemented for closures can be called.
364///
365/// Assertions started with `verify_that_code()` will collect [`AssertFailure`]s
366/// for all failing assertions. The collected failures can be queried by calling
367/// one of the methods [`failures`](Spec::failures) or
368/// [`display_failures`](Spec::display_failures) on the [`Spec`].
369///
370/// In comparison to using the macro [`verify_that_code!`](crate::verify_that_code)
371/// calling this function does not set a name for the expression and does not
372/// set the code location of the assertion. In failure messages, the generic
373/// word "the closure" is used. To set a specific text for the expression, the
374/// method [`named`](Spec::named) must be called explicitly.
375///
376/// # Examples
377///
378/// ```
379/// use asserting::prelude::*;
380///
381/// fn divide(a: i32, b: i32) -> i32 {
382/// a / b
383/// }
384///
385/// let failures = verify_that_code(|| { divide(7, 3); })
386/// .does_not_panic()
387/// .failures();
388///
389/// assert_that!(failures).is_empty();
390///
391/// let failures = verify_that_code(|| { divide(7, 0); })
392/// .does_not_panic()
393/// .display_failures();
394///
395/// assert_that!(failures).contains_exactly([
396/// r#"expected the closure to not panic, but did panic
397/// with message: "attempt to divide by zero"
398/// "#
399/// ]);
400///
401/// let failures = verify_that_code(|| { divide(7, 0); })
402/// .panics_with_message("division by zero")
403/// .display_failures();
404///
405/// assert_that!(failures).contains_exactly([
406/// r#"expected the closure to panic with message "division by zero"
407/// but was: "attempt to divide by zero"
408/// expected: "division by zero"
409/// "#
410/// ]);
411/// ```
412#[cfg(feature = "panic")]
413#[cfg_attr(docsrs, doc(cfg(feature = "panic")))]
414pub fn verify_that_code<'a, S>(code: S) -> Spec<'a, Code<S>, CollectFailures>
415where
416 S: FnOnce(),
417{
418 Spec::new(Code::from(code), CollectFailures).named("the closure")
419}
420
421/// An expectation defines a test for a property of the asserted subject.
422///
423/// It requires two methods: a `test()` method and a `message()` method.
424/// The `test()` method is called to verify whether an actual subject meets the
425/// expected property. In case the test of the expectation fails, the
426/// `message()` method is called to form an expectation-specific failure
427/// message.
428pub trait Expectation<S: ?Sized> {
429 /// Verifies whether the actual subject fulfills the expected property.
430 fn test(&mut self, subject: &S) -> bool;
431
432 /// Forms a failure message for this expectation.
433 fn message(
434 &self,
435 expression: &Expression<'_>,
436 actual: &S,
437 inverted: bool,
438 format: &DiffFormat,
439 ) -> String;
440}
441
442/// Marks an expectation that it can be inverted by using the [`Not`]
443/// combinator.
444///
445/// An expectation is any type that implements the [`Expectation`] trait.
446///
447/// This trait is meant to be implemented in combination with the
448/// [`Expectation`] trait. It should only be implemented for an expectation if
449/// the inverted test is unmistakably meaningful, and if the failure message
450/// clearly states whether the expectation has been inverted or not.
451///
452/// [`Not`]: crate::expectations::Not
453pub trait Invertible {}
454
455/// A textual representation of the expression or subject that is being
456/// asserted.
457#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
458pub struct Expression<'a>(pub Cow<'a, str>);
459
460impl Default for Expression<'_> {
461 fn default() -> Self {
462 Self("subject".into())
463 }
464}
465
466impl Display for Expression<'_> {
467 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
468 write!(f, "{}", self.0)
469 }
470}
471
472impl Deref for Expression<'_> {
473 type Target = str;
474
475 fn deref(&self) -> &Self::Target {
476 &self.0
477 }
478}
479
480impl<'a> From<&'a str> for Expression<'a> {
481 fn from(s: &'a str) -> Self {
482 Self(s.into())
483 }
484}
485
486impl From<String> for Expression<'_> {
487 fn from(s: String) -> Self {
488 Self(s.into())
489 }
490}
491
492/// The location of an assertion in the source code respectively test code.
493///
494/// # Related
495/// - [`core::panic::Location`]
496#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
497pub struct Location<'a> {
498 /// The file path of the source file where the assertion is located.
499 pub file: &'a str,
500
501 /// The line number within the source file where the assertion is located.
502 pub line: u32,
503
504 /// The column number on the line within the source file where the assertion
505 /// is located.
506 pub column: u32,
507}
508
509impl Display for Location<'_> {
510 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
511 #[cfg(not(test))]
512 let file = self.file;
513 #[cfg(test)]
514 let file = self.file.replace('\\', "/");
515 write!(f, "{file}:{}:{}", self.line, self.column)
516 }
517}
518
519impl<'a> Location<'a> {
520 /// Constructs a new `Location` with the given file, line and column.
521 #[must_use]
522 pub const fn new(file: &'a str, line: u32, column: u32) -> Self {
523 Self { file, line, column }
524 }
525}
526
527impl Location<'_> {
528 /// Returns the file path of this location.
529 pub fn file(&self) -> &str {
530 self.file
531 }
532
533 /// Returns the line number of this location.
534 pub fn line(&self) -> u32 {
535 self.line
536 }
537
538 /// Returns the column number of this location.
539 pub fn column(&self) -> u32 {
540 self.column
541 }
542}
543
544/// An owned location in the source code respectively test code.
545///
546/// It is basically the same as [`Location`] but uses owned types instead of
547/// borrowed types for its fields.
548#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
549pub struct OwnedLocation {
550 /// The file path of the source file where the assertion is located.
551 pub file: String,
552
553 /// The line number within the source file where the assertion is located.
554 pub line: u32,
555
556 /// The column number on the line within the source file where the assertion
557 /// is located.
558 pub column: u32,
559}
560
561impl Display for OwnedLocation {
562 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
563 #[cfg(not(test))]
564 let file = self.file.clone();
565 #[cfg(test)]
566 let file = self.file.replace('\\', "/");
567 write!(f, "{file}:{}:{}", self.line, self.column)
568 }
569}
570
571impl OwnedLocation {
572 /// Constructs a new `OwnedLocation` with the given file, line and column.
573 #[must_use]
574 pub fn new(file: impl Into<String>, line: u32, column: u32) -> Self {
575 Self {
576 file: file.into(),
577 line,
578 column,
579 }
580 }
581
582 /// Reference this [`OwnedLocation`] as a [`Location`].
583 pub fn as_location(&self) -> Location<'_> {
584 Location {
585 file: &self.file,
586 line: self.line,
587 column: self.column,
588 }
589 }
590}
591
592impl From<Location<'_>> for OwnedLocation {
593 fn from(value: Location<'_>) -> Self {
594 Self {
595 file: value.file.into(),
596 line: value.line,
597 column: value.column,
598 }
599 }
600}
601
602impl OwnedLocation {
603 /// Returns the file path of this location.
604 pub fn file(&self) -> &str {
605 &self.file
606 }
607
608 /// Returns the line number of this location.
609 pub fn line(&self) -> u32 {
610 self.line
611 }
612
613 /// Returns the column number of this location.
614 pub fn column(&self) -> u32 {
615 self.column
616 }
617}
618
619/// Data of an actual assertion.
620///
621/// It holds the data needed to execute an assertion such as the subject,
622/// the name of the subject or expression, an optional description of the
623/// current assertion and the location of the assertion in the source code
624/// respectively test code.
625///
626/// It also holds the concrete [`FailingStrategy`] on how to behave in case
627/// an assertion fails.
628///
629/// In case of the [`CollectFailures`] failing strategy, the [`AssertFailure`]s
630/// are collected in this struct.
631pub struct Spec<'a, S, R> {
632 subject: S,
633 expression: Option<Expression<'a>>,
634 description: Option<Cow<'a, str>>,
635 location: Option<Location<'a>>,
636 failures: Vec<AssertFailure>,
637 diff_format: DiffFormat,
638 failing_strategy: R,
639}
640
641impl<S, R> Spec<'_, S, R> {
642 /// Returns the subject.
643 pub fn subject(&self) -> &S {
644 &self.subject
645 }
646
647 /// Returns the expression (or subject name) if one has been set.
648 pub fn expression(&self) -> Option<&Expression<'_>> {
649 self.expression.as_ref()
650 }
651
652 /// Returns the location in source code or test code if it has been set.
653 pub fn location(&self) -> Option<Location<'_>> {
654 self.location
655 }
656
657 /// Returns the description or the assertion if it has been set.
658 pub fn description(&self) -> Option<&str> {
659 self.description.as_deref()
660 }
661
662 /// Returns the diff format used with this assertion.
663 pub const fn diff_format(&self) -> &DiffFormat {
664 &self.diff_format
665 }
666
667 /// Returns the failing strategy that is used in case an assertion fails.
668 pub fn failing_strategy(&self) -> &R {
669 &self.failing_strategy
670 }
671
672 /// Returns whether there are assertion failures collected so far.
673 pub fn has_failures(&self) -> bool {
674 !self.failures.is_empty()
675 }
676
677 /// Returns the assertion failures that have been collected so far.
678 pub fn failures(&self) -> Vec<AssertFailure> {
679 self.failures.clone()
680 }
681
682 /// Returns the assertion failures collected so far as formatted text.
683 pub fn display_failures(&self) -> Vec<String> {
684 self.failures.iter().map(ToString::to_string).collect()
685 }
686}
687
688impl<'a, S, R> Spec<'a, S, R> {
689 /// Constructs a new `Spec` for the given subject and with the specified
690 /// failing strategy.
691 ///
692 /// The diff format is set to "no highlighting". Failure messages will not
693 /// highlight differences between the actual and the expected value.
694 #[must_use = "a spec does nothing unless an assertion method is called"]
695 pub const fn new(subject: S, failing_strategy: R) -> Self {
696 Self {
697 subject,
698 expression: None,
699 description: None,
700 location: None,
701 failures: vec![],
702 diff_format: colored::DIFF_FORMAT_NO_HIGHLIGHT,
703 failing_strategy,
704 }
705 }
706
707 /// Sets the subject name or expression for this assertion.
708 #[must_use = "a spec does nothing unless an assertion method is called"]
709 pub fn named(mut self, subject_name: impl Into<Cow<'a, str>>) -> Self {
710 self.expression = Some(Expression(subject_name.into()));
711 self
712 }
713
714 /// Sets a custom description about what is being asserted.
715 #[must_use = "a spec does nothing unless an assertion method is called"]
716 pub fn described_as(mut self, description: impl Into<Cow<'a, str>>) -> Self {
717 self.description = Some(description.into());
718 self
719 }
720
721 /// Sets the location of the assertion in the source code respectively test
722 /// code.
723 #[must_use = "a spec does nothing unless an assertion method is called"]
724 pub const fn located_at(mut self, location: Location<'a>) -> Self {
725 self.location = Some(location);
726 self
727 }
728
729 /// Sets the diff format that is used to highlight differences between
730 /// the actual value and the expected value.
731 ///
732 /// Note: This method must be called before an assertion method is called to
733 /// have an effect on the failure message of the assertion as failure
734 /// messages are formatted immediately when an assertion is executed.
735 #[must_use = "a spec does nothing unless an assertion method is called"]
736 pub const fn with_diff_format(mut self, diff_format: DiffFormat) -> Self {
737 self.diff_format = diff_format;
738 self
739 }
740
741 /// Sets the diff format used to highlight differences between the actual
742 /// value and the expected value according the configured mode.
743 ///
744 /// The mode is configured via environment variables like described in the
745 /// module [colored].
746 #[cfg(feature = "colored")]
747 #[cfg_attr(docsrs, doc(cfg(feature = "colored")))]
748 #[must_use = "a spec does nothing unless an assertion method is called"]
749 pub fn with_configured_diff_format(self) -> Self {
750 use crate::colored::configured_diff_format;
751 #[cfg(not(feature = "std"))]
752 {
753 self.with_diff_format(configured_diff_format())
754 }
755 #[cfg(feature = "std")]
756 {
757 use crate::std::sync::OnceLock;
758 static DIFF_FORMAT: OnceLock<DiffFormat> = OnceLock::new();
759 let diff_format = DIFF_FORMAT.get_or_init(configured_diff_format);
760 self.with_diff_format(diff_format.clone())
761 }
762 }
763
764 /// Maps the current subject to some other value.
765 ///
766 /// It takes a closure that maps the current subject to a new subject and
767 /// returns a new `Spec` with the value returned by the closure as the new
768 /// subject. The new subject may have a different type than the original
769 /// subject. All other data like expression, description and location are
770 /// taken over from this `Spec` into the returned `Spec`.
771 ///
772 /// This function is useful when having a custom type and some specific
773 /// property of this type shall be asserted only.
774 ///
775 /// This is an alias function to the [`mapping()`](Spec::mapping) function.
776 /// Both functions do exactly the same. The idea is to provide different
777 /// names to be able to express the intent more clearly when used in
778 /// assertions.
779 ///
780 /// # Example
781 ///
782 /// ```
783 /// use asserting::prelude::*;
784 ///
785 /// struct MyStruct {
786 /// important_property: String,
787 /// other_property: f64,
788 /// }
789 ///
790 /// let some_thing = MyStruct {
791 /// important_property: "imperdiet aliqua zzril eiusmod".into(),
792 /// other_property: 99.9,
793 /// };
794 ///
795 /// assert_that!(some_thing).extracting(|s| s.important_property)
796 /// .is_equal_to("imperdiet aliqua zzril eiusmod");
797 ///
798 /// ```
799 #[must_use = "a spec does nothing unless an assertion method is called"]
800 pub fn extracting<F, U>(self, extractor: F) -> Spec<'a, U, R>
801 where
802 F: FnOnce(S) -> U,
803 {
804 self.mapping(extractor)
805 }
806
807 /// Maps the current subject to some other value.
808 ///
809 /// It takes a closure that maps the current subject to a new subject and
810 /// returns a new `Spec` with the value returned by the closure as the new
811 /// subject. The new subject may have a different type than the original
812 /// subject. All other data like expression, description and location are
813 /// taken over from this `Spec` into the returned `Spec`.
814 ///
815 /// This function is useful if some type does not implement a trait that is
816 /// required for an assertion.
817 ///
818 /// `Spec` also provides the [`extracting()`](Spec::extracting) function,
819 /// which is an alias to this function. Both functions do exactly the same.
820 /// Choose that function of which its name expresses the intent more
821 /// clearly.
822 ///
823 /// # Example
824 ///
825 /// ```
826 /// use asserting::prelude::*;
827 ///
828 /// struct Point {
829 /// x: i64,
830 /// y: i64,
831 /// }
832 ///
833 /// let target = Point { x: 12, y: -64 };
834 ///
835 /// assert_that!(target).mapping(|s| (s.x, s.y)).is_equal_to((12, -64));
836 /// ```
837 ///
838 /// The custom type `Point` does not implement the `PartialEq` trait nor
839 /// the `Debug` trait, which are both required for an `is_equal_to`
840 /// assertion. So we map the subject of the type `Point` to a tuple of its
841 /// fields.
842 #[must_use = "a spec does nothing unless an assertion method is called"]
843 pub fn mapping<F, U>(self, mapper: F) -> Spec<'a, U, R>
844 where
845 F: FnOnce(S) -> U,
846 {
847 Spec {
848 subject: mapper(self.subject),
849 expression: self.expression,
850 description: self.description,
851 location: self.location,
852 failures: self.failures,
853 diff_format: self.diff_format,
854 failing_strategy: self.failing_strategy,
855 }
856 }
857}
858
859impl<S, R> Spec<'_, S, R>
860where
861 R: FailingStrategy,
862{
863 /// Asserts the given expectation.
864 ///
865 /// In case the expectation is not meet, it does fail according the current
866 /// failing strategy of this `Spec`.
867 ///
868 /// This method is called from the implementations of the assertion traits
869 /// defined in the [`assertions`](crate::assertions) module. Implementations
870 /// of custom assertions will call this method with a proper expectation.
871 ///
872 /// # Examples
873 ///
874 /// ```
875 /// use asserting::expectations::{IsEmpty, IsEqualTo};
876 /// use asserting::prelude::*;
877 ///
878 /// assert_that!(7 * 6).expecting(IsEqualTo {expected: 42 });
879 ///
880 /// assert_that!("").expecting(IsEmpty);
881 /// ```
882 #[allow(clippy::needless_pass_by_value, clippy::return_self_not_must_use)]
883 #[track_caller]
884 pub fn expecting(mut self, mut expectation: impl Expectation<S>) -> Self {
885 if !expectation.test(&self.subject) {
886 let default_expression = Expression::default();
887 let expression = self.expression.as_ref().unwrap_or(&default_expression);
888 let message = expectation.message(expression, &self.subject, false, &self.diff_format);
889 self.do_fail_with_message(message);
890 }
891 self
892 }
893
894 /// Asserts whether the given predicate is meet.
895 ///
896 /// This method takes a predicate function and calls it as an expectation.
897 /// In case the predicate function returns false, it does fail with a
898 /// generic failure message and according to the current failing strategy of
899 /// this `Spec`.
900 ///
901 /// This method can be used to do simple custom assertions without
902 /// implementing an [`Expectation`] and an assertion trait.
903 ///
904 /// # Examples
905 ///
906 /// ```
907 /// use asserting::prelude::*;
908 ///
909 /// fn is_odd(value: &i32) -> bool {
910 /// value & 1 == 1
911 /// }
912 ///
913 /// assert_that!(37).satisfies(is_odd);
914 ///
915 /// let failures = verify_that!(22).satisfies(is_odd).display_failures();
916 ///
917 /// assert_that!(failures).contains_exactly([
918 /// "expected 22 to satisfy the given predicate, but returned false\n"
919 /// ]);
920 /// ```
921 ///
922 /// To assert a predicate with a custom failure message instead of the
923 /// generic one, use the method
924 /// [`satisfies_with_message`](Spec::satisfies_with_message).
925 #[allow(clippy::return_self_not_must_use)]
926 #[track_caller]
927 pub fn satisfies<P>(self, predicate: P) -> Self
928 where
929 P: Fn(&S) -> bool,
930 {
931 self.expecting(satisfies(predicate))
932 }
933
934 /// Asserts whether the given predicate is meet.
935 ///
936 /// This method takes a predicate function and calls it as an expectation.
937 /// In case the predicate function returns false, it does fail with the
938 /// provided failure message and according to the current failing strategy
939 /// of this `Spec`.
940 ///
941 /// This method can be used to do simple custom assertions without
942 /// implementing an [`Expectation`] and an assertion trait.
943 ///
944 /// # Examples
945 ///
946 /// ```
947 /// use asserting::prelude::*;
948 ///
949 /// fn is_odd(value: &i32) -> bool {
950 /// value & 1 == 1
951 /// }
952 ///
953 /// assert_that!(37).satisfies_with_message("expected my number to be odd", is_odd);
954 ///
955 /// let failures = verify_that!(22)
956 /// .satisfies_with_message("expected my number to be odd", is_odd)
957 /// .display_failures();
958 ///
959 /// assert_that!(failures).contains_exactly([
960 /// "expected my number to be odd\n"
961 /// ]);
962 /// ```
963 ///
964 /// To assert a predicate with a generic failure message instead of
965 /// providing one use the method
966 /// [`satisfies`](Spec::satisfies).
967 #[allow(clippy::return_self_not_must_use)]
968 #[track_caller]
969 pub fn satisfies_with_message<P>(self, message: impl Into<String>, predicate: P) -> Self
970 where
971 P: Fn(&S) -> bool,
972 {
973 self.expecting(satisfies(predicate).with_message(message))
974 }
975
976 /// Fails the assertion according the current failing strategy of this
977 /// `Spec`.
978 #[track_caller]
979 fn do_fail_with_message(&mut self, message: impl Into<String>) {
980 let message = message.into();
981 let failure = AssertFailure {
982 description: self.description.clone().map(String::from),
983 message,
984 location: self.location.map(OwnedLocation::from),
985 };
986 self.failures.push(failure);
987 self.failing_strategy.do_fail_with(&self.failures);
988 }
989}
990
991impl<S> Spec<'_, S, CollectFailures> {
992 /// Turns assertions into "soft assertions".
993 ///
994 /// It executes all specified assertions on a `Spec` and if at least one
995 /// assertion fails, it panics. The panic message contains the messages of
996 /// all assertions that have failed.
997 ///
998 /// This method is only available on `Spec`s with the
999 /// [`CollectFailures`]-[`FailingStrategy`]. That is any `Spec` contructed
1000 /// by the macros [`verify_that!`] and [`verify_that_code!`] or by the
1001 /// functions [`verify_that()`] and [`verify_that_code()`].
1002 ///
1003 /// On a `Spec` with the [`PanicOnFail`]-[`FailingStrategy`] it would not
1004 /// work as the very first failing assertion panics immediately, and later
1005 /// assertions never get executed.
1006 ///
1007 /// # Examples
1008 ///
1009 /// Running the following two assertions in "soft" mode:
1010 ///
1011 /// ```should_panic
1012 /// use asserting::prelude::*;
1013 ///
1014 /// verify_that!("the answer to all important questions is 42")
1015 /// .contains("unimportant")
1016 /// .has_at_most_length(41)
1017 /// .soft_panic();
1018 /// ```
1019 ///
1020 /// executes both assertions and prints the messages of both failing
1021 /// assertions in the panic message:
1022 ///
1023 /// ```console
1024 /// expected subject to contain "unimportant"
1025 /// but was: "the answer to all important questions is 42"
1026 /// expected: "unimportant"
1027 ///
1028 /// expected subject to have at most a length of 41
1029 /// but was: 43
1030 /// expected: <= 41
1031 /// ```
1032 ///
1033 /// To highlight differences in failure messages of soft assertions use
1034 /// the `with_configured_diff_format()` method, like so:
1035 ///
1036 /// ```
1037 /// # #[cfg(not(feature = "colored"))]
1038 /// # fn main() {}
1039 /// # #[cfg(feature = "colored")]
1040 /// # fn main() {
1041 /// use asserting::prelude::*;
1042 ///
1043 /// verify_that!("the answer to all important questions is 42")
1044 /// .with_configured_diff_format()
1045 /// .contains("important")
1046 /// .has_at_most_length(43)
1047 /// .soft_panic();
1048 /// # }
1049 /// ```
1050 pub fn soft_panic(&self) {
1051 if !self.failures.is_empty() {
1052 PanicOnFail.do_fail_with(&self.failures);
1053 }
1054 }
1055}
1056
1057impl<'a, I, R> Spec<'a, I, R> {
1058 /// Iterates over the elements of a collection or an iterator and executes
1059 /// the given assertions for each of those elements. If all elements are
1060 /// asserted successfully, the whole assertion succeeds.
1061 ///
1062 /// It iterates over all elements of the collection or iterator and collects
1063 /// the failure messages for those elements where the assertion fails. In
1064 /// other words, it does not stop iterating when the assertion for one
1065 /// element fails.
1066 ///
1067 /// The failure messages contain the position of the element within the
1068 /// collection or iterator. The position is 0-based. So a failure message
1069 /// for the first element contains `[0]`, the second `[1]`, and so on.
1070 ///
1071 /// # Example
1072 ///
1073 /// The following assertion:
1074 ///
1075 /// ```should_panic
1076 /// use asserting::prelude::*;
1077 ///
1078 /// let numbers = [2, 4, 6, 8, 10];
1079 ///
1080 /// assert_that!(numbers).each_element(|e|
1081 /// e.is_greater_than(2)
1082 /// .is_at_most(7)
1083 /// );
1084 /// ```
1085 ///
1086 /// will print:
1087 ///
1088 /// ```console
1089 /// expected numbers [0] to be greater than 2
1090 /// but was: 2
1091 /// expected: > 2
1092 ///
1093 /// expected numbers [3] to be at most 7
1094 /// but was: 8
1095 /// expected: <= 7
1096 ///
1097 /// expected numbers [4] to be at most 7
1098 /// but was: 10
1099 /// expected: <= 7
1100 /// ```
1101 #[allow(clippy::return_self_not_must_use)]
1102 #[track_caller]
1103 pub fn each_element<T, A, B>(mut self, assert: A) -> Spec<'a, (), R>
1104 where
1105 I: IntoIterator<Item = T>,
1106 A: Fn(Spec<'a, T, CollectFailures>) -> Spec<'a, B, CollectFailures>,
1107 {
1108 let default_expression = &Expression::default();
1109 let root_expression = self.expression.as_ref().unwrap_or(default_expression);
1110 let mut position = -1;
1111 for item in self.subject {
1112 position += 1;
1113 let element_spec = Spec {
1114 subject: item,
1115 expression: Some(format!("{root_expression} [{position}]").into()),
1116 description: None,
1117 location: self.location,
1118 failures: vec![],
1119 diff_format: self.diff_format.clone(),
1120 failing_strategy: CollectFailures,
1121 };
1122 let failures = assert(element_spec).failures;
1123 self.failures.extend(failures);
1124 }
1125 if !self.failures.is_empty()
1126 && any::type_name_of_val(&self.failing_strategy) == any::type_name::<PanicOnFail>()
1127 {
1128 PanicOnFail.do_fail_with(&self.failures);
1129 }
1130 Spec {
1131 subject: (),
1132 expression: self.expression,
1133 description: self.description,
1134 location: self.location,
1135 failures: self.failures,
1136 diff_format: self.diff_format,
1137 failing_strategy: self.failing_strategy,
1138 }
1139 }
1140
1141 /// Iterates over the elements of a collection or an iterator and executes
1142 /// the given assertions for each of those elements. If the assertion of any
1143 /// element is successful, the iteration stops and the whole assertion
1144 /// succeeds.
1145 ///
1146 /// If the assertion fails for all elements, the failures of the assertion
1147 /// for all elements are collected.
1148 ///
1149 /// The failure messages contain the position of the element within the
1150 /// collection or iterator. The position is 0-based. So a failure message
1151 /// for the first element contains `[0]`, the second `[1]`, and so on.
1152 ///
1153 /// # Example
1154 ///
1155 /// The following assertion:
1156 ///
1157 /// ```should_panic
1158 /// use asserting::prelude::*;
1159 ///
1160 /// let digit_names = ["one", "two", "three"];
1161 ///
1162 /// assert_that!(digit_names).any_element(|e|
1163 /// e.contains('x')
1164 /// );
1165 /// ```
1166 ///
1167 /// will print:
1168 ///
1169 /// ```console
1170 /// expected digit_names [0] to contain 'x'
1171 /// but was: "one"
1172 /// expected: 'x'
1173 ///
1174 /// expected digit_names [1] to contain 'x'
1175 /// but was: "two"
1176 /// expected: 'x'
1177 ///
1178 /// expected digit_names [2] to contain 'x'
1179 /// but was: "three"
1180 /// expected: 'x'
1181 /// ```
1182 #[track_caller]
1183 pub fn any_element<T, A, B>(mut self, assert: A) -> Spec<'a, (), R>
1184 where
1185 I: IntoIterator<Item = T>,
1186 A: Fn(Spec<'a, T, CollectFailures>) -> Spec<'a, B, CollectFailures>,
1187 {
1188 let default_expression = &Expression::default();
1189 let root_expression = self.expression.as_ref().unwrap_or(default_expression);
1190 let mut any_success = false;
1191 let mut position = -1;
1192 for item in self.subject {
1193 position += 1;
1194 let element_spec = Spec {
1195 subject: item,
1196 expression: Some(format!("{root_expression} [{position}]").into()),
1197 description: None,
1198 location: self.location,
1199 failures: vec![],
1200 diff_format: self.diff_format.clone(),
1201 failing_strategy: CollectFailures,
1202 };
1203 let failures = assert(element_spec).failures;
1204 if failures.is_empty() {
1205 any_success = true;
1206 break;
1207 }
1208 self.failures.extend(failures);
1209 }
1210 if !any_success
1211 && any::type_name_of_val(&self.failing_strategy) == any::type_name::<PanicOnFail>()
1212 {
1213 PanicOnFail.do_fail_with(&self.failures);
1214 }
1215 Spec {
1216 subject: (),
1217 expression: self.expression,
1218 description: self.description,
1219 location: self.location,
1220 failures: self.failures,
1221 diff_format: self.diff_format,
1222 failing_strategy: self.failing_strategy,
1223 }
1224 }
1225}
1226
1227/// An error describing a failed assertion.
1228///
1229/// This struct implements the [`std::error::Error`] trait.
1230#[derive(Debug, Clone, PartialEq, Eq)]
1231pub struct AssertFailure {
1232 description: Option<String>,
1233 message: String,
1234 location: Option<OwnedLocation>,
1235}
1236
1237impl Display for AssertFailure {
1238 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1239 match &self.description {
1240 None => {
1241 writeln!(f, "{}", &self.message)?;
1242 },
1243 Some(description) => {
1244 writeln!(f, "{description}\n{}", &self.message)?;
1245 },
1246 }
1247 Ok(())
1248 }
1249}
1250
1251impl StdError for AssertFailure {}
1252
1253#[allow(clippy::must_use_candidate)]
1254impl AssertFailure {
1255 /// Returns the description of the assertion that failed.
1256 pub fn description(&self) -> Option<&String> {
1257 self.description.as_ref()
1258 }
1259
1260 /// Returns the failure message of the assertion that failed.
1261 #[allow(clippy::missing_const_for_fn)]
1262 pub fn message(&self) -> &str {
1263 &self.message
1264 }
1265
1266 /// Returns the location of the assertion in the source code / test code if
1267 /// it has been set in the [`Spec`].
1268 pub fn location(&self) -> Option<&OwnedLocation> {
1269 self.location.as_ref()
1270 }
1271}
1272
1273/// Start and end tag that marks a highlighted part of a string.
1274#[derive(Debug, Clone, PartialEq, Eq)]
1275pub(crate) struct Highlight {
1276 pub(crate) start: &'static str,
1277 pub(crate) end: &'static str,
1278}
1279
1280/// Definition of format properties for highlighting differences between two
1281/// values.
1282#[derive(Debug, Clone, PartialEq, Eq)]
1283pub struct DiffFormat {
1284 pub(crate) unexpected: Highlight,
1285 pub(crate) missing: Highlight,
1286}
1287
1288/// Defines the behavior when an assertion fails.
1289///
1290/// This crate provides two implementations:
1291///
1292/// * [`PanicOnFail`] - panics when an assertion fails
1293/// * [`CollectFailures`] - collects [`AssertFailure`]s of assertions that have failed.
1294pub trait FailingStrategy {
1295 /// Reacts to an assertion that has failed with the [`AssertFailure`]s given
1296 /// as argument.
1297 fn do_fail_with(&self, failures: &[AssertFailure]);
1298}
1299
1300/// [`FailingStrategy`] that panics when an assertion fails.
1301#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1302pub struct PanicOnFail;
1303
1304impl FailingStrategy for PanicOnFail {
1305 #[track_caller]
1306 fn do_fail_with(&self, failures: &[AssertFailure]) {
1307 let message = failures
1308 .iter()
1309 .map(ToString::to_string)
1310 .collect::<Vec<_>>()
1311 .join("\n");
1312 panic!("{}", message);
1313 }
1314}
1315
1316/// [`FailingStrategy`] that collects the failures from failing assertions.
1317#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1318pub struct CollectFailures;
1319
1320impl FailingStrategy for CollectFailures {
1321 fn do_fail_with(&self, _failures: &[AssertFailure]) {
1322 // do nothing by design
1323 }
1324}
1325
1326/// Used with generic types in expectations where the concrete type is not
1327/// relevant for the failure message.
1328///
1329/// This type implements the std format trait [`std::fmt::Debug`] and
1330/// [`std::fmt::Display`] which both format the value as "_".
1331///
1332/// ```
1333/// # use asserting::prelude::*;
1334/// # use asserting::spec::Unknown;
1335/// assert_that!(format!("{:?}", Unknown)).is_equal_to("_");
1336/// assert_that!(format!("{}", Unknown)).is_equal_to("_");
1337/// ```
1338///
1339/// # Examples
1340///
1341/// This type is used to implement the expectations
1342///
1343/// * [`IsSome`](crate::expectations::IsSome)
1344/// * [`IsNone`](crate::expectations::IsNone)
1345/// * [`IsOk`](crate::expectations::IsOk)
1346/// * [`IsErr`](crate::expectations::IsErr)
1347///
1348/// For example for implementing the function [`Expectation::message()`] for the
1349/// [`IsOk`](crate::expectations::IsOk) expectation for `Result<T, E>` the
1350/// concrete types for `T` and `E` are not relevant. The implementation of the
1351/// trait looks like this:
1352///
1353/// ```no_run
1354/// # use std::fmt::Debug;
1355/// # use asserting::spec::{DiffFormat, Expectation, Expression, Unknown};
1356/// # struct IsOk;
1357/// impl<T, E> Expectation<Result<T, E>> for IsOk
1358/// where
1359/// T: Debug,
1360/// E: Debug,
1361/// {
1362/// fn test(&mut self, subject: &Result<T, E>) -> bool {
1363/// subject.is_ok()
1364/// }
1365///
1366/// fn message(&self, expression: &Expression<'_>, actual: &Result<T, E>, _inverted: bool, _format: &DiffFormat) -> String {
1367/// format!(
1368/// "expected {expression} is {:?}\n but was: {actual:?}\n expected: {:?}",
1369/// Ok::<_, Unknown>(Unknown),
1370/// Ok::<_, Unknown>(Unknown),
1371/// )
1372/// }
1373/// }
1374/// ```
1375#[derive(Default, Clone, Copy, PartialEq, Eq)]
1376pub struct Unknown;
1377
1378impl Debug for Unknown {
1379 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1380 write!(f, "{self}")
1381 }
1382}
1383
1384impl Display for Unknown {
1385 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1386 write!(f, "_")
1387 }
1388}
1389
1390/// Wrapper type that holds a closure as code snippet.
1391#[cfg(feature = "panic")]
1392#[cfg_attr(docsrs, doc(cfg(feature = "panic")))]
1393pub struct Code<F>(Rc<RefCell<Option<F>>>);
1394
1395#[cfg(feature = "panic")]
1396mod code {
1397 use super::Code;
1398 use std::cell::RefCell;
1399 use std::rc::Rc;
1400
1401 impl<F> From<F> for Code<F>
1402 where
1403 F: FnOnce(),
1404 {
1405 fn from(value: F) -> Self {
1406 Self(Rc::new(RefCell::new(Some(value))))
1407 }
1408 }
1409
1410 impl<F> Code<F> {
1411 /// Takes the closure out of this `Code` leaving it empty.
1412 #[must_use]
1413 pub fn take(&self) -> Option<F> {
1414 self.0.borrow_mut().take()
1415 }
1416 }
1417}
1418
1419#[cfg(test)]
1420mod tests;