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 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 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 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 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 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 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 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 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: 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) -> &Expression<'_> {
649 &self.expression
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 fn new(subject: S, failing_strategy: R) -> Self {
696 Self {
697 subject,
698 expression: Expression::default(),
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 = 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 used to highlight differences between the actual
730 /// 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 to the configured mode.
743 ///
744 /// The mode is configured via environment variables as 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 a 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
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, the assertion fails according to
866 /// the current 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 message =
887 expectation.message(&self.expression, &self.subject, false, &self.diff_format);
888 self.do_fail_with_message(message);
889 }
890 self
891 }
892
893 /// Asserts whether the given predicate is meet.
894 ///
895 /// This method takes a predicate function and calls it as an expectation.
896 /// In case the predicate function returns false, it does fail with a
897 /// generic failure message and according to the current failing strategy of
898 /// this `Spec`.
899 ///
900 /// This method can be used to do simple custom assertions without
901 /// implementing an [`Expectation`] and an assertion trait.
902 ///
903 /// # Examples
904 ///
905 /// ```
906 /// use asserting::prelude::*;
907 ///
908 /// fn is_odd(value: &i32) -> bool {
909 /// value & 1 == 1
910 /// }
911 ///
912 /// assert_that!(37).satisfies(is_odd);
913 ///
914 /// let failures = verify_that!(22).satisfies(is_odd).display_failures();
915 ///
916 /// assert_that!(failures).contains_exactly([
917 /// "expected 22 to satisfy the given predicate, but returned false\n"
918 /// ]);
919 /// ```
920 ///
921 /// To assert a predicate with a custom failure message instead of the
922 /// generic one, use the method
923 /// [`satisfies_with_message`](Spec::satisfies_with_message).
924 #[allow(clippy::return_self_not_must_use)]
925 #[track_caller]
926 pub fn satisfies<P>(self, predicate: P) -> Self
927 where
928 P: Fn(&S) -> bool,
929 {
930 self.expecting(satisfies(predicate))
931 }
932
933 /// Asserts whether the given predicate is meet.
934 ///
935 /// This method takes a predicate function and calls it as an expectation.
936 /// In case the predicate function returns false, it does fail with the
937 /// provided failure message and according to the current failing strategy
938 /// of this `Spec`.
939 ///
940 /// This method can be used to do simple custom assertions without
941 /// implementing an [`Expectation`] and an assertion trait.
942 ///
943 /// # Examples
944 ///
945 /// ```
946 /// use asserting::prelude::*;
947 ///
948 /// fn is_odd(value: &i32) -> bool {
949 /// value & 1 == 1
950 /// }
951 ///
952 /// assert_that!(37).satisfies_with_message("expected my number to be odd", is_odd);
953 ///
954 /// let failures = verify_that!(22)
955 /// .satisfies_with_message("expected my number to be odd", is_odd)
956 /// .display_failures();
957 ///
958 /// assert_that!(failures).contains_exactly([
959 /// "expected my number to be odd\n"
960 /// ]);
961 /// ```
962 ///
963 /// To assert a predicate with a generic failure message instead of
964 /// providing one use the method
965 /// [`satisfies`](Spec::satisfies).
966 #[allow(clippy::return_self_not_must_use)]
967 #[track_caller]
968 pub fn satisfies_with_message<P>(self, message: impl Into<String>, predicate: P) -> Self
969 where
970 P: Fn(&S) -> bool,
971 {
972 self.expecting(satisfies(predicate).with_message(message))
973 }
974
975 /// Fails the assertion according to the current failing strategy of this
976 /// `Spec`.
977 #[track_caller]
978 pub fn do_fail_with_message(&mut self, message: impl Into<String>) {
979 let message = message.into();
980 let failure = AssertFailure {
981 description: self.description.clone().map(String::from),
982 message,
983 location: self.location.map(OwnedLocation::from),
984 };
985 self.failures.push(failure);
986 self.failing_strategy.do_fail_with(&self.failures);
987 }
988}
989
990impl<S> Spec<'_, S, CollectFailures> {
991 /// Turns assertions into "soft assertions".
992 ///
993 /// It executes all specified assertions on a `Spec` and if at least one
994 /// assertion fails, it panics. The panic message contains the messages of
995 /// all assertions that have failed.
996 ///
997 /// This method is only available on `Spec`s with the
998 /// [`CollectFailures`]-[`FailingStrategy`]. That is any `Spec` contructed
999 /// by the macros [`verify_that!`] and [`verify_that_code!`] or by the
1000 /// functions [`verify_that()`] and [`verify_that_code()`].
1001 ///
1002 /// On a `Spec` with the [`PanicOnFail`]-[`FailingStrategy`] it would not
1003 /// work as the very first failing assertion panics immediately, and later
1004 /// assertions never get executed.
1005 ///
1006 /// # Examples
1007 ///
1008 /// Running the following two assertions in "soft" mode:
1009 ///
1010 /// ```should_panic
1011 /// use asserting::prelude::*;
1012 ///
1013 /// verify_that!("the answer to all important questions is 42")
1014 /// .contains("unimportant")
1015 /// .has_at_most_length(41)
1016 /// .soft_panic();
1017 /// ```
1018 ///
1019 /// executes both assertions and prints the messages of both failing
1020 /// assertions in the panic message:
1021 ///
1022 /// ```console
1023 /// expected subject to contain "unimportant"
1024 /// but was: "the answer to all important questions is 42"
1025 /// expected: "unimportant"
1026 ///
1027 /// expected subject to have at most a length of 41
1028 /// but was: 43
1029 /// expected: <= 41
1030 /// ```
1031 ///
1032 /// To highlight differences in failure messages of soft assertions use
1033 /// the `with_configured_diff_format()` method, like so:
1034 ///
1035 /// ```
1036 /// # #[cfg(not(feature = "colored"))]
1037 /// # fn main() {}
1038 /// # #[cfg(feature = "colored")]
1039 /// # fn main() {
1040 /// use asserting::prelude::*;
1041 ///
1042 /// verify_that!("the answer to all important questions is 42")
1043 /// .with_configured_diff_format()
1044 /// .contains("important")
1045 /// .has_at_most_length(43)
1046 /// .soft_panic();
1047 /// # }
1048 /// ```
1049 pub fn soft_panic(&self) {
1050 if !self.failures.is_empty() {
1051 PanicOnFail.do_fail_with(&self.failures);
1052 }
1053 }
1054}
1055
1056impl<'a, I, R> Spec<'a, I, R> {
1057 /// Iterates over the elements of a collection or an iterator and executes
1058 /// the given assertions for each of those elements. If all elements are
1059 /// asserted successfully, the whole assertion succeeds.
1060 ///
1061 /// It iterates over all elements of the collection or iterator and collects
1062 /// the failure messages for those elements where the assertion fails. In
1063 /// other words, it does not stop iterating when the assertion for one
1064 /// element fails.
1065 ///
1066 /// The failure messages contain the position of the element within the
1067 /// collection or iterator. The position is 0-based. So a failure message
1068 /// for the first element contains `[0]`, the second `[1]`, and so on.
1069 ///
1070 /// # Example
1071 ///
1072 /// The following assertion:
1073 ///
1074 /// ```should_panic
1075 /// use asserting::prelude::*;
1076 ///
1077 /// let numbers = [2, 4, 6, 8, 10];
1078 ///
1079 /// assert_that!(numbers).each_element(|e|
1080 /// e.is_greater_than(2)
1081 /// .is_at_most(7)
1082 /// );
1083 /// ```
1084 ///
1085 /// will print:
1086 ///
1087 /// ```console
1088 /// expected numbers [0] to be greater than 2
1089 /// but was: 2
1090 /// expected: > 2
1091 ///
1092 /// expected numbers [3] to be at most 7
1093 /// but was: 8
1094 /// expected: <= 7
1095 ///
1096 /// expected numbers [4] to be at most 7
1097 /// but was: 10
1098 /// expected: <= 7
1099 /// ```
1100 #[allow(clippy::return_self_not_must_use)]
1101 #[track_caller]
1102 pub fn each_element<T, A, B>(mut self, assert: A) -> Spec<'a, (), R>
1103 where
1104 I: IntoIterator<Item = T>,
1105 A: Fn(Spec<'a, T, CollectFailures>) -> Spec<'a, B, CollectFailures>,
1106 {
1107 let root_expression = &self.expression;
1108 let mut position = -1;
1109 for item in self.subject {
1110 position += 1;
1111 let element_spec = Spec {
1112 subject: item,
1113 expression: format!("{root_expression} [{position}]").into(),
1114 description: None,
1115 location: self.location,
1116 failures: vec![],
1117 diff_format: self.diff_format.clone(),
1118 failing_strategy: CollectFailures,
1119 };
1120 let failures = assert(element_spec).failures;
1121 self.failures.extend(failures);
1122 }
1123 if !self.failures.is_empty()
1124 && any::type_name_of_val(&self.failing_strategy) == any::type_name::<PanicOnFail>()
1125 {
1126 PanicOnFail.do_fail_with(&self.failures);
1127 }
1128 Spec {
1129 subject: (),
1130 expression: self.expression,
1131 description: self.description,
1132 location: self.location,
1133 failures: self.failures,
1134 diff_format: self.diff_format,
1135 failing_strategy: self.failing_strategy,
1136 }
1137 }
1138
1139 /// Iterates over the elements of a collection or an iterator and executes
1140 /// the given assertions for each of those elements. If the assertion of any
1141 /// element is successful, the iteration stops and the whole assertion
1142 /// succeeds.
1143 ///
1144 /// If the assertion fails for all elements, the failures of the assertion
1145 /// for all elements are collected.
1146 ///
1147 /// The failure messages contain the position of the element within the
1148 /// collection or iterator. The position is 0-based. So a failure message
1149 /// for the first element contains `[0]`, the second `[1]`, and so on.
1150 ///
1151 /// # Example
1152 ///
1153 /// The following assertion:
1154 ///
1155 /// ```should_panic
1156 /// use asserting::prelude::*;
1157 ///
1158 /// let digit_names = ["one", "two", "three"];
1159 ///
1160 /// assert_that!(digit_names).any_element(|e|
1161 /// e.contains('x')
1162 /// );
1163 /// ```
1164 ///
1165 /// will print:
1166 ///
1167 /// ```console
1168 /// expected digit_names [0] to contain 'x'
1169 /// but was: "one"
1170 /// expected: 'x'
1171 ///
1172 /// expected digit_names [1] to contain 'x'
1173 /// but was: "two"
1174 /// expected: 'x'
1175 ///
1176 /// expected digit_names [2] to contain 'x'
1177 /// but was: "three"
1178 /// expected: 'x'
1179 /// ```
1180 #[track_caller]
1181 pub fn any_element<T, A, B>(mut self, assert: A) -> Spec<'a, (), R>
1182 where
1183 I: IntoIterator<Item = T>,
1184 A: Fn(Spec<'a, T, CollectFailures>) -> Spec<'a, B, CollectFailures>,
1185 {
1186 let root_expression = &self.expression;
1187 let mut any_success = false;
1188 let mut position = -1;
1189 for item in self.subject {
1190 position += 1;
1191 let element_spec = Spec {
1192 subject: item,
1193 expression: format!("{root_expression} [{position}]").into(),
1194 description: None,
1195 location: self.location,
1196 failures: vec![],
1197 diff_format: self.diff_format.clone(),
1198 failing_strategy: CollectFailures,
1199 };
1200 let failures = assert(element_spec).failures;
1201 if failures.is_empty() {
1202 any_success = true;
1203 break;
1204 }
1205 self.failures.extend(failures);
1206 }
1207 if !any_success
1208 && any::type_name_of_val(&self.failing_strategy) == any::type_name::<PanicOnFail>()
1209 {
1210 PanicOnFail.do_fail_with(&self.failures);
1211 }
1212 Spec {
1213 subject: (),
1214 expression: self.expression,
1215 description: self.description,
1216 location: self.location,
1217 failures: self.failures,
1218 diff_format: self.diff_format,
1219 failing_strategy: self.failing_strategy,
1220 }
1221 }
1222}
1223
1224/// An error describing a failed assertion.
1225///
1226/// This struct implements the [`std::error::Error`] trait.
1227#[derive(Debug, Clone, PartialEq, Eq)]
1228pub struct AssertFailure {
1229 description: Option<String>,
1230 message: String,
1231 location: Option<OwnedLocation>,
1232}
1233
1234impl Display for AssertFailure {
1235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1236 match &self.description {
1237 None => {
1238 writeln!(f, "{}", &self.message)?;
1239 },
1240 Some(description) => {
1241 writeln!(f, "{description}\n{}", &self.message)?;
1242 },
1243 }
1244 Ok(())
1245 }
1246}
1247
1248impl StdError for AssertFailure {}
1249
1250#[allow(clippy::must_use_candidate)]
1251impl AssertFailure {
1252 /// Returns the description of the assertion that failed.
1253 pub fn description(&self) -> Option<&String> {
1254 self.description.as_ref()
1255 }
1256
1257 /// Returns the failure message of the assertion that failed.
1258 #[allow(clippy::missing_const_for_fn)]
1259 pub fn message(&self) -> &str {
1260 &self.message
1261 }
1262
1263 /// Returns the location of the assertion in the source code / test code if
1264 /// it has been set in the [`Spec`].
1265 pub fn location(&self) -> Option<&OwnedLocation> {
1266 self.location.as_ref()
1267 }
1268}
1269
1270/// Start and end tag that marks a highlighted part of a string.
1271#[derive(Debug, Clone, PartialEq, Eq)]
1272pub(crate) struct Highlight {
1273 pub(crate) start: &'static str,
1274 pub(crate) end: &'static str,
1275}
1276
1277/// Definition of format properties for highlighting differences between two
1278/// values.
1279#[derive(Debug, Clone, PartialEq, Eq)]
1280pub struct DiffFormat {
1281 pub(crate) unexpected: Highlight,
1282 pub(crate) missing: Highlight,
1283}
1284
1285/// Defines the behavior when an assertion fails.
1286///
1287/// This crate provides two implementations:
1288///
1289/// * [`PanicOnFail`] - panics when an assertion fails
1290/// * [`CollectFailures`] - collects [`AssertFailure`]s of assertions that have failed.
1291pub trait FailingStrategy {
1292 /// Reacts to an assertion that has failed with the [`AssertFailure`]s given
1293 /// as argument.
1294 fn do_fail_with(&self, failures: &[AssertFailure]);
1295}
1296
1297/// [`FailingStrategy`] that panics when an assertion fails.
1298#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1299pub struct PanicOnFail;
1300
1301impl FailingStrategy for PanicOnFail {
1302 #[track_caller]
1303 fn do_fail_with(&self, failures: &[AssertFailure]) {
1304 let message = failures
1305 .iter()
1306 .map(ToString::to_string)
1307 .collect::<Vec<_>>()
1308 .join("\n");
1309 panic!("{}", message);
1310 }
1311}
1312
1313/// [`FailingStrategy`] that collects the failures from failing assertions.
1314#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1315pub struct CollectFailures;
1316
1317impl FailingStrategy for CollectFailures {
1318 fn do_fail_with(&self, _failures: &[AssertFailure]) {
1319 // do nothing by design
1320 }
1321}
1322
1323/// Used with generic types in expectations where the concrete type is not
1324/// relevant for the failure message.
1325///
1326/// This type implements the std format trait [`std::fmt::Debug`] and
1327/// [`std::fmt::Display`] which both format the value as "_".
1328///
1329/// ```
1330/// # use asserting::prelude::*;
1331/// # use asserting::spec::Unknown;
1332/// assert_that!(format!("{:?}", Unknown)).is_equal_to("_");
1333/// assert_that!(format!("{}", Unknown)).is_equal_to("_");
1334/// ```
1335///
1336/// # Examples
1337///
1338/// This type is used to implement the expectations
1339///
1340/// * [`IsSome`](crate::expectations::IsSome)
1341/// * [`IsNone`](crate::expectations::IsNone)
1342/// * [`IsOk`](crate::expectations::IsOk)
1343/// * [`IsErr`](crate::expectations::IsErr)
1344///
1345/// For example, for implementing the function [`Expectation::message()`] for
1346/// the [`IsOk`](crate::expectations::IsOk) expectation for `Result<T, E>` the
1347/// concrete types for `T` and `E` are not relevant. The implementation of the
1348/// trait looks like this:
1349///
1350/// ```no_run
1351/// # use std::fmt::Debug;
1352/// # use asserting::spec::{DiffFormat, Expectation, Expression, Unknown};
1353/// # struct IsOk;
1354/// impl<T, E> Expectation<Result<T, E>> for IsOk
1355/// where
1356/// T: Debug,
1357/// E: Debug,
1358/// {
1359/// fn test(&mut self, subject: &Result<T, E>) -> bool {
1360/// subject.is_ok()
1361/// }
1362///
1363/// fn message(&self, expression: &Expression<'_>, actual: &Result<T, E>, _inverted: bool, _format: &DiffFormat) -> String {
1364/// format!(
1365/// "expected {expression} is {:?}\n but was: {actual:?}\n expected: {:?}",
1366/// Ok::<_, Unknown>(Unknown),
1367/// Ok::<_, Unknown>(Unknown),
1368/// )
1369/// }
1370/// }
1371/// ```
1372#[derive(Default, Clone, Copy, PartialEq, Eq)]
1373pub struct Unknown;
1374
1375impl Debug for Unknown {
1376 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1377 write!(f, "{self}")
1378 }
1379}
1380
1381impl Display for Unknown {
1382 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1383 write!(f, "_")
1384 }
1385}
1386
1387/// Wrapper type that holds a closure as code snippet.
1388#[cfg(feature = "panic")]
1389#[cfg_attr(docsrs, doc(cfg(feature = "panic")))]
1390pub struct Code<F>(Rc<RefCell<Option<F>>>);
1391
1392#[cfg(feature = "panic")]
1393mod code {
1394 use super::Code;
1395 use std::cell::RefCell;
1396 use std::rc::Rc;
1397
1398 impl<F> From<F> for Code<F>
1399 where
1400 F: FnOnce(),
1401 {
1402 fn from(value: F) -> Self {
1403 Self(Rc::new(RefCell::new(Some(value))))
1404 }
1405 }
1406
1407 impl<F> Code<F> {
1408 /// Takes the closure out of this `Code` leaving it empty.
1409 #[must_use]
1410 pub fn take(&self) -> Option<F> {
1411 self.0.borrow_mut().take()
1412 }
1413 }
1414}
1415
1416#[cfg(test)]
1417mod tests;